Custom drawing on glass (1)

As I noted in a previous post, large parts of the classic GDI (and therefore, TCanvas and its associated classes) are not alpha-channel aware, being as a result not suitable for drawing on glass. Because of this, if you actually want to make use of the GlassFrame property of TForm, it is frequently advisable to use another drawing API, specifically GDI+ or Direct2D – at present, GDI+ can be used via various header translations and open source wrappers (just Google ‘Delphi GDI+’), and Delphi 2010 gets you Direct2D support (for earlier versions, you can grab an API translation by John Bladen here). Nonetheless, there are some glass-friendly aspects to the classic GDI, and if you’re just looking to make some custom VCL controls paint nicely on glass, they can be all you actually need.

Now in summary, ‘what works’ is drawing 32 bit bitmaps, icons and image lists, together with the visual theming API, notwithstanding the fact that many native controls (which, one would assume, internally use said API) frequently don’t work too well. Given the theming API includes a function for outputting text, and filled boxes can be drawn with brushes that use a 32 bit bitmap, you’ve got a fair bit. I’ll take each in turn – bitmaps in this post, the rest next time.

Bitmaps

While this is hopefully quite an obvious point to make, the storage format of a 32 bit colour image need have nothing to do with its in-memory format, which for a GDI application will have to be a particular variant of the Windows Device Independent Bitmap (DIB) format. That said, PNG resources make for a sensible storage format, and the VCL’s TBitmap class largely frees you from having to know about DIBs explicitly.

Use of PNG images does of course require a PNG support library though. Nonetheless, D2009 and greater includes one in the box, and for D2007, you can use Mike Lische’s MPL-licensed GraphicEx – in either case, support is implemented in the form of a handy TGraphic descendant, which you can either draw directly or by assigning to a TBitmap first and then drawing that.

For the actual painting, the key API function is AlphaBlend. Whilst this is called as appropriate by the D2009+ VCL I believe, you’ll definitely need to call it explicitly in D2007. It is pretty easy to use though, excepting one small quirk (if you can call it that) – namely, it requires the source bitmap to have had its pixels ‘pre-multiplied’. For a proper explanation of this, check out Anders Melander’s excellent two part article on writing an alpha-blended splash screen. The short version is that the red, blue and green component values of each pixel need to have been factored by the pixel’s opaqueness before AlphaBlend is called –

procedure PreMultiplyBitmap(Bitmap: TBitmap);
var
  X, Y: Integer;
  Pixel: PRGBQuad;
begin
  Assert(Bitmap.PixelFormat = pf32Bit);
  with Bitmap do
    for Y := Height - 1 downto 0 do
    begin
      Pixel := ScanLine[Y];
      for X := Width - 1 downto 0 do
      begin
        Pixel.rgbBlue := MulDiv(Pixel.rgbBlue, Pixel.rgbReserved, 255);
        Pixel.rgbGreen := MulDiv(Pixel.rgbGreen, Pixel.rgbReserved, 255);
        Pixel.rgbRed := MulDiv(Pixel.rgbRed, Pixel.rgbReserved, 255);
        Inc(Pixel);
      end;
    end;
end;

While in D2009+ you probably won’t have to worry about this, the 32 bit bitmaps produced by the PNG support in GraphicEx will need pre-multiplying.

With a pre-multiplied bitmap at the ready however, an alpha channel aware version of TCanvas.CopyRect goes like this:

procedure CopyRectAlpha(DestCanvas: TCanvas; const DestRect: TRect;
  SourceCanvas: TCanvas; const SourceRect: TRect);
const
  MergeFunc: TBlendFunction = (BlendOp: AC_SRC_OVER; BlendFlags: 0;
  SourceConstantAlpha: 255; AlphaFormat: AC_SRC_ALPHA);
begin
  AlphaBlend(DestCanvas.Canvas.Handle, DestRect.Left, DestRect.Top,
    DestRect.Right - DestRect.Left, DestRect.Bottom - DestRect.Top,
    SourceCanvas.Handle, SourceRect.Left, SourceRect.Top, SourceRect.Right -
    SourceRect.Left, SourceRect.Bottom - SourceRect.Top, MergeFunc);
end;

Pretty simple, eh?

That said, you might not actually be needing to use AlphaBlend at all, since BitBlt, StretchBlt (and therefore, their VCL wrappers such as TCanvas.CopyRect) work with 32 bit bitmaps as well. There is a key difference here with AlphaBlend however: for when blitting (copying) a 32 bit bitmap to glass, a transparent pixel of the bitmap will reset the corresponding glass pixel. In contrast, when blending a 32 bit bitmap to glass, a transparent pixel of the bitmap will leave the glass pixel as it was. With respect to only partially transparent pixels, both functions will ‘blend’ the pixels concerned in a sense. Where AlphaBlend will merge the bitmap’s pixels with what was painted to the glass before, however, BitBlt/StretchBitBlt will in effect ‘reset’ the glass first then merge the pixels.

Put in more practical terms, say you wanted to create a simple animation that paints to a potentially glassy area of the form. When painting a new frame, and using traditional GDI practice, you would typically create a buffer bitmap, drawing the new frame image onto it, before painting the buffer to the form’s Canvas, overwriting what was there before. Essentially, revising such code to be glass-friendly should not take much at all – the only difference should be in initialising the buffer to be all ‘black’ (i.e., 100% transparent in alpha-land) rather than the colour of the form or whatever. In particular, the fact that you may now be painting to glass does not change the fact that the last stage is one of overwriting, and therefore, one for which BitBlt, StretchBlt or one of their VCL equivalents remains appropriate, not AlphaBlend:

Buffer := TBitmap.Create;
try
  if GlassFrame.FrameExtended then
    Buffer.Canvas.Brush.Color := clBlack //i.e., 100% 'transparent'
  else
    Buffer.Canvas.Brush.Assign(Self.Brush);
  Buffer.SetSize(BufferRect.Right - BufferRect.Left, BufferRect.Bottom - BufferRect.Top);
  //
  Canvas.StretchDraw(BufferRect, Buffer);  //overwrite, not blend!
finally
  Buffer.Free;
end;

I’ve demonstrated this in more detail in a small demo, which you can download from here. Basically, it’s just a form with its GlassFrame.SheetOfGlass property set to True, and a spinning Delphi 2010 icon that is moveable using the arrow keys (hold down the Control key to speed it up). Pressing ‘G’ will toggle extended glass on/off, demonstrating how there’s nothing particularly special going on in the drawing code to support glass. Please note that when compiling under D2007, GraphicEx is used, so if you're a D2007 user, download that first (alternative download site here - scroll down a bit), if you haven't a copy on your library path already.

Advertisements

4 thoughts on “Custom drawing on glass (1)

  1. Thank you very much for your article. I was looking for loading a png into a bmp and to draw it on a glass with alpha-channell (with Delphi 2007). And your article explain it very well. Thank you again.

  2. Pingback: Custom drawing on glass (2) « Delphi Haven

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s