wxBitmap: premultiply alpha?

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.
delt
Knows some wx things
Knows some wx things
Posts: 27
Joined: Mon Dec 26, 2016 11:17 pm

wxBitmap: premultiply alpha?

Post by delt »

Hello everyone,

I'm having trouble with this, and can't find *any* documentation on it, other than web pages/archives from like 12 years ago. I barely remember the concept of alpha premultiply from working on a QT project a few years ago, but i guess the way wxWidgets handles it is different anyway.

On Linux, there seems to be no premultiply going on. On osx and windows, i get graphic glitches, but different on both, like it works differently on one and the other.

Is there some *reasonably recent* documentation about this? If not, can someone give me a quick briefing about how it's supposed to work? Any help would be greatly appreciated.
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBitmap: premultiply alpha?

Post by doublemax »

AFAIK this detail is not officially mentioned anywhere in the documentation. If it were, it should be here:
http://docs.wxwidgets.org/trunk/classwx_pixel_data.html

However, the "image" sample is the only that uses raw bitmap access and it does use premultiplied alpha. So that's the correct way, but only if you use raw bitmap access (using wxPixelData), because that's the only way to manipulate the pixel values directly.
Use the source, Luke!
delt
Knows some wx things
Knows some wx things
Posts: 27
Joined: Mon Dec 26, 2016 11:17 pm

Re: wxBitmap: premultiply alpha?

Post by delt »

Thanks for the quick reply Doublemax.

I got it to work correctly in linux/gtk and osx by premultiplying the color values by alpha, windows is still giving me several problems. My image is drawn incorrectly (alpha on wxBrush/wxPen seems to be ignored when drawing lines/rectangles), and a straight wxBitmap::GetSubBitmap () of this whole image (after drawing it) gives a blank image.
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBitmap: premultiply alpha?

Post by doublemax »

Which wxWidgets version? There were several fixes regarding bitmaps with alpha under Windows over the last few months. Unless you're already using the latest version from GIT, try that one.
My image is drawn incorrectly (alpha on wxBrush/wxPen seems to be ignored when drawing lines/rectangles)
Drawing bitmaps with alpha should be fine when using wxDCs. But for drawing operations with alpha you need to use wxGraphicsContext under Windows.
Use the source, Luke!
delt
Knows some wx things
Knows some wx things
Posts: 27
Joined: Mon Dec 26, 2016 11:17 pm

Re: wxBitmap: premultiply alpha?

Post by delt »

doublemax wrote:Which wxWidgets version? There were several fixes regarding bitmaps with alpha under Windows over the last few months. Unless you're already using the latest version from GIT, try that one.
Oh, that's great news! I'm using 3.1.0 now, i will build and test the latest git tonight. Thanks again!
Drawing bitmaps with alpha should be fine when using wxDCs. But for drawing operations with alpha you need to use wxGraphicsContext under Windows.
Ah i see. I will read the documentation on that. The same code should also work on osx and linux?
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBitmap: premultiply alpha?

Post by doublemax »

The same code should also work on osx and linux?
Yes
Use the source, Luke!
delt
Knows some wx things
Knows some wx things
Posts: 27
Joined: Mon Dec 26, 2016 11:17 pm

Re: wxBitmap: premultiply alpha?

Post by delt »

Ok, i corrected a few problems i was having with the windows version, but i have a few types of widgets where i render a base image that changes only on resize, or when the data changes etc. and then an overlay image that is updated at each mouse movement, paint event and so on. So i do something like this: (base_image is a member of my class, and overlay_image is local to the redraw function)

Code: Select all

myclass::render_base_image () {
  base_image = wxBitmap (width, height, 32);

  /* draw base image */
}

myclass::redraw (wxDC &paintdc) {
  if (some_data_has_changed)
    base_image_valid = false;
  
  if (!base_image_valid)
    render_base_image ();

  wxBitmap overlay_image = wxBitmap (base_image.GetWidth (), base_image.GetHeight (), 32);
  
  wxMemoryDC dc;
  dc.SelectObject (overlay_image);
  dc.DrawBitmap (base_image, 0, 0); /* also tried with graphiccontext->DrawBitmap (base_image, 0, 0, ...); */

  /* draw overlay image */
  
  paintdc.DrawBitmap (overlay_image, 0, 0);
}
This works on linux and osx, but on windows this just gives me a white rectangle :/ I know the base image is drawn (now it's drawn correctly, thanks to the changes with wxGraphicsContext) because if i draw the base image to the paintdc instead of the overlay image, i see what the rendered base image is supposed to look like.

I bump the base image to the overlay at each redraw to avoid flicker caused by drawing both successively to the paintdc. I'd like to do the same on windows if possible.

Any idea what could be causing the blank rectangle?
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBitmap: premultiply alpha?

Post by doublemax »

If drawing base_image gives a black rectangle, the problem is probably in render_base_image() which you didn't show. That could be tested by loading a bitmap with alpha, e.g. the "toucan.png" from the wx samples. Also, you need to set the "useMask" parameter to true (its meaning is more like useMaskOrAlpha ).

BTW: You don't need to manage a buffer bitmap to avoid flickering yourself, you can just replace wxPaintDC with wx[Auto]BufferedPaintDC.
Use the source, Luke!
delt
Knows some wx things
Knows some wx things
Posts: 27
Joined: Mon Dec 26, 2016 11:17 pm

Re: wxBitmap: premultiply alpha?

Post by delt »

doublemax wrote:the problem is probably in render_base_image() which you didn't show.
No, because as i mentioned, if i draw the base image instead of the overlay image (at the end of my redraw function) i see what the base image is supposed to look like.

/* EDIT */

After a bit more debugging, it seems the base image gets copied correctly to the overlay image if i use GetSubBitmap (), but then gets erased to a white rectangle immediately when i call dc.SelectObject (overlay_image); In this case i see a copy of the base image, as expected, but if i add that single call to wxMemoryDC::SelectObject (), it gets erased to a white rectangle on windows. The same code works fine on linux and osx.
BTW: You don't need to manage a buffer bitmap to avoid flickering yourself, you can just replace wxPaintDC with wx[Auto]BufferedPaintDC.
Would this have a performance improvement compared to simply bumping the pixel data from the base image to the overlay before rendering the overlay?
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBitmap: premultiply alpha?

Post by doublemax »

Try this:

Code: Select all

dc.DrawBitmap (base_image, 0, 0);
dc.SelectObject(wxNullBitmap);
Would this have a performance improvement compared to simply bumping the pixel data from the base image to the overlay before rendering the overlay?
If the only purpose of overlay_image is to reduce flicker, then yes. If you use wxAutoBufferedPaintDC it won't use a buffer bitmap under Linux and OSX because screen output is double buffered by the OS anyway.

For performance reasons you should use a static bitmap here and only recreate it when the size changes.

However, optimization comes later, first get the output right.
Use the source, Luke!
delt
Knows some wx things
Knows some wx things
Posts: 27
Joined: Mon Dec 26, 2016 11:17 pm

Re: wxBitmap: premultiply alpha?

Post by delt »

I'm having another similar problem with setting my pixel values with a wxAlphaPixelData::Iterator ...for some reason windows refuses to make them transparent, even if i set the alpha value to 0. It will always show up as opaque when i draw that image on another image with a wxMemoryDC. I tried all combinations of premultiplying color values with alpha, selecting the image in a temporary wxMemoryDC and clearing it with a "transparent" brush, etc etc..... again, works fine on osx and linux, but winpieceofsh!#%t is causing trouble as usual.
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBitmap: premultiply alpha?

Post by doublemax »

Can you show a minimal, compilable piece of code that shows the problem? (no pseudo code please)
Use the source, Luke!
delt
Knows some wx things
Knows some wx things
Posts: 27
Joined: Mon Dec 26, 2016 11:17 pm

Re: wxBitmap: premultiply alpha?

Post by delt »

Should display a blue background with a red square that gradients in opacity from transparent (top) to opaque (bottom).
Fails on windows (fully opaque red square)

Code: Select all

/*
 * compile & run:
 * g++ wxtest.cpp `wx-config --cflags --libs` -o wxtest && ./wxtest
 * 
 * Should display a blue background with a red square that gradients in
 * opacity from transparent (top) to opaque (bottom)
 */

#include <wx/wx.h>
#include <wx/rawbmp.h>

class c_panel : public wxPanel {
public:
  
  void paint_event (wxPaintEvent &ev) {
    wxPaintDC dc (this);
    int x, y;
    
    /* blue bacbkground */
    dc.SetBackground (wxBrush (wxColour (0, 0, 255, 255)));
    dc.Clear ();
    
    wxBitmap bitmap (128, 128, 32);
    
    wxAlphaPixelData pixeldata (bitmap);
    wxAlphaPixelData::Iterator p (pixeldata);
    p.MoveTo (pixeldata, 0, 0);
    
    /* red square, with alpha gradient */
    for (y = 0; y < 128; ++y) {
      wxAlphaPixelData::Iterator rowstart = p;
      for (x = 0; x < 128; ++x, ++p) {
        p.Red () = 255;
        p.Green () = 0;
        p.Blue () = 0;
        p.Alpha () = y * 2;
      }
      p = rowstart;
      p.OffsetY (pixeldata, 1);
    }
    
    dc.DrawBitmap (bitmap, 128, 64);
  }
  
  /* ctor */
  c_panel (wxWindow *parent, int id) : wxPanel (parent, id) {
    SetMinClientSize (wxSize (384, 256));
    SetMaxClientSize (wxSize (384, 256));
    Bind (wxEVT_PAINT, &c_panel::paint_event, this);
  }
};

class c_testapp : public wxApp {
public:
  
  wxFrame *frame;
  wxSizer *sizer;
  c_panel *panel;
  
  bool OnInit () {
    sizer = new wxBoxSizer (wxHORIZONTAL);
    frame = new wxFrame (NULL, wxID_ANY, "Test");
    frame->SetSizer (sizer);
    panel = new c_panel (frame, wxID_ANY);
    sizer->Add (panel);
    frame->Show ();
    frame->SetClientSize (wxSize (384, 256));
    return true;
  }
};

IMPLEMENT_APP (c_testapp);
User avatar
doublemax
Moderator
Moderator
Posts: 19164
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxBitmap: premultiply alpha?

Post by doublemax »

1) You have to premultiply the RGB values with alpha
2) The wxAlphaPixelData object must be destroyed before you draw the bitmap

Code: Select all

wxPaintDC dc (this);
    int x, y;
   
    /* blue bacbkground */
    dc.SetBackground (wxBrush (wxColour (0, 0, 255)));
    dc.Clear ();
   
    wxBitmap bitmap (128, 128, 32);
   
    {
      wxAlphaPixelData pixeldata (bitmap);
      wxAlphaPixelData::Iterator p (pixeldata);
      p.MoveTo (pixeldata, 0, 0);
   
      /* red square, with alpha gradient */
      for (y = 0; y < 128; ++y)
      {
        wxAlphaPixelData::Iterator rowstart = p;
        for (x = 0; x < 128; ++x, ++p)
        {
          unsigned char alpha = y * 2;
          p.Red () = 255 * alpha / 255;
          p.Green () = 0 * alpha / 255;
          p.Blue () = 0 * alpha / 255;
          p.Alpha () = alpha;
        }
        p = rowstart;
        p.OffsetY (pixeldata, 1);
      }
    }

    dc.DrawBitmap (bitmap, 128, 64);
Use the source, Luke!