Best way to do picking? Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
ninja9578
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 236
Joined: Thu Jan 29, 2009 3:33 pm

Best way to do picking?

Post by ninja9578 »

I'm implementing a program that has lots of objects on the screens, sometime rectangles, but sometimes very complex structures of varying colours.

What is the best way to take a bitmap that has a mask and convert everything that isn't the mask to a colour of my choice (each object requires it's own colour in picking)

The way that I thought of is.

convert bitmap to image
converttomono
create another image that's all one colour(the picking one)
setmaskfromimage, using the white of the monoimage as the mask colour
then converting it to a bitmap
drawing the bitmap to the picking bitmap using a memorydc

Seems like a lot of conversions :(

Perhaps there is an easier way?
User avatar
Disch
Experienced Solver
Experienced Solver
Posts: 99
Joined: Wed Oct 17, 2007 2:01 am

Post by Disch »

Code: Select all

void MyClass::DrawBitmapToPickerDC(const wxBitmap& bmp,const wxColour& pickercolor,int x,int y)
{
  // duplicate the bitmap
  wxBitmap pickerbmp(bmp);

  // slap it in a DC
  wxMemoryDC dc;
  dc.SelectObjectAsSource(pickerbmp);

  // clear the image to be a solid color -- but this retains
  //  the selected mask.
  dc.SetBackground(wxBrush(pickercolor));
  dc.Clear();

  // Do a transparent blit of this DC to the picker DC.
  //  mask will ensure only the desired pixels are drawn.
  m_pickerdc.Blit(x,y,bmp.GetWidth(),bmp.GetHeight(),&dc,0,0,wxCOPY,true);
}
Once you draw all the stuff to your m_pickerdc object, you can pull out the bitmap and convert it to a wxImage to get per-pixel data.

m_pickerdc would be a wxMemoryDC with a selected bitmap large enough to hold the entire surface area.
ninja9578
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 236
Joined: Thu Jan 29, 2009 3:33 pm

Post by ninja9578 »

Hmm... there is no :bowdown emoticon :D Thanks

Just a question about your code, why are you creating a new bitmap everytime, shouldn't you just dereference?

Also, is SelectObjectAsSource() something new? It's not in the wiki, nor is it recognized by my compiler. Did you mean SelectObject()?
Last edited by ninja9578 on Thu Feb 05, 2009 9:32 pm, edited 1 time in total.
User avatar
Disch
Experienced Solver
Experienced Solver
Posts: 99
Joined: Wed Oct 17, 2007 2:01 am

Post by Disch »

Glad I can help :)
ninja9578 wrote: Just a question about your code, why are you creating a new bitmap everytime, shouldn't you pass by reference?
I am passing the original bitmap by reference (wxBitmap&), but I'm creating a copy for the 'picker' image because I'd assume you wouldn't want this code to clear your original image (which it would). Drawing (or in this case clearing) the DC would modify the selected wxBitmap.

If you don't need the wxBitmap anymore and it's not selected into any other DC, then yeah, by all means remove my bitmap copy and use 'bmp' directly rather than 'pickerbmp'.
Also, is SelectObjectAsSource() something new? It's not in the wiki, nor is it recognized by my compiler. Did you mean SelectObject()?
I'm using wx 2.8.9 and it's included in the documentation here. I don't know whether or not it's a recent addition. SelectObject should work as well, though I think it doesn't reference count so it might require an additional copy. That's a bit of speculation though.
ninja9578
Filthy Rich wx Solver
Filthy Rich wx Solver
Posts: 236
Joined: Thu Jan 29, 2009 3:33 pm

Post by ninja9578 »

Uh oh, seems that it's not solved.

Duplicating the bitmap doesn't prevent clear from changing the original bitmap as well. Why is this happening? I changed your code a little bit, but not much.

Code: Select all

void Picker::DrawToPicker(const wxBitmap & bmp, int ID, int x, int y){
    // duplicate the bitmap 
   wxBitmap pickerbmp(bmp); 
    
    // slap it in a DC 
    wxMemoryDC dc; 
    dc.SelectObject(pickerbmp); 
    
    //create a colour of the identifier
    int r = ID % 255;
    int g = (ID >> 8) % 255;
    int b = (ID >> 16) % 255;
    
    wxColour pickercolour(r, g, b);
    
    // clear the image to be a solid color -- but this retains 
    //  the selected mask. 
    dc.SetBackground(wxBrush(pickercolour)); 
    dc.Clear(); 
    
    // Do a transparent blit of this DC to the picker DC. 
    //  mask will ensure only the desired pixels are drawn. 
    PickerDC -> Blit(x,y,bmp.GetWidth(),bmp.GetHeight(),&dc,0,0,wxCOPY,true); 
}
This is the code that is calling it (one of many)

Code: Select all

     //The bitmap which is the main rendering plane
     wxBitmap bitmap;
     bitmap = wxBitmap((int)AbsWidth.Value, (int)AbsHeight.Value);      
    
     //The device contexts for drawing to the bitmap and finally placing the bitmap on the scree
     wxClientDC dc(drawingArea);
     wxMemoryDC memdc;
     memdc.SelectObject(bitmap);
    if (!Transparent){
         //Draw the background
         wxc = wxColour(appearance -> BackgroundColour[0], appearance -> BackgroundColour[1], appearance -> BackgroundColour[2]);     
         memdc.SetBrush(wxBrush(wxc));
         memdc.SetPen(wxPen(wxc, 1));
         memdc.DrawRectangle(offset, offset, (int)AbsWidth.Value, (int)AbsHeight.Value);
     } else {  //make it transparent
         wxc = wxColour(255, 0, 255);
         memdc.SetBrush(wxBrush(wxc));
         memdc.SetPen(wxPen(wxc, 1));
         memdc.DrawRectangle(0, 0, (int)AbsWidth.Value, (int)AbsHeight.Value);
     }  
  memdc.DrawText(wxString::FromAscii(part.c_str()), wxPoint((int)((long int)AbsWidth.Value - x + offset),ypos + offset));

     //convert the bitmap to an image and set the mask for rotating (hot pink)     
     wxImage image = bitmap.ConvertToImage();
     
     image.SetMaskColour(255,0,255);
     image.SetMask();
    

     //rotate the image
     image = image.Rotate(-Rotation, wxPoint(image.GetWidth() / 2 + offset / 2, image.GetHeight() / 2 + offset / 2), !DebugMode);
     
     //set it back to another bitmap and draw
     wxBitmap bit2(&image);
     Picker::DrawToPicker(bit2, UniqueIdentifier, (int)AbsLeft.Value, (int)AbsTop.Value);
     dc.DrawBitmap(bit2, wxPoint((int)AbsLeft.Value - offset, (int)AbsTop.Value - offset), true);
I took a lot of fluff out of that method, but that's all of the drawing stuff.
User avatar
Disch
Experienced Solver
Experienced Solver
Posts: 99
Joined: Wed Oct 17, 2007 2:01 am

Post by Disch »

Most likely a reference counting problem. Now that I tried it I get the same problem with SelectObjectAsSource -- which I thought would prevent this problem.

Oh well.

wxBitmap bmppicker(bmp); <-- doesn't make a direct copy right away, it simply increases the reference count.

So the solution here is to manually create a copy of the bitmap to prevent it from reference counting. Try this instead:

Code: Select all

wxBitmap pickerbmp = bmp.GetSubBitmap(wxRect(0, 0, bmp.GetWidth(), bmp.GetHeight()));
Also for your ID conversion you'd probably want % 256 (or "& 0xFF"), not % 255. With %255, both IDs 0 and 255 will result in pure black.