Preserving PNG transparency with TPNGObject.Draw
  1. 1. The solution
  2. 2. Blank transparent PNG? Easy!
  3. 3. PNG speed button

A few days ago I needed to create a component which would take 3 PNG images, glue them together and output. Nothing complex, really, Delphi can handle much more complicated tasks :D
However, for this matter I was using an excellent class TPNGObject and its Draw methodwas what had thrown me into the sea of confusion.

First off, most obvious way of merging pictures together is doing like: pascalFirst.Draw(Second.Canvas); And this indeed works flawlessly with TBitmap and other classes of this kind (e.g. standard TJPEGImage) that don't have bit alpha transparency (like PNG RGBA format).

However, when I did this with my TPNGObjects I noticed strange black noise around them. After some digging and googling around I found out that it was due to some transparency mess.
Using some logics I noticed that there's an AlphaScanLine prop of TPNGObject classwhich might be used to reset the transparency. And just when I thought pascalZeroFill()ing would do the job I stumbled upon another conclusion…

TPNGObject.Draw does not copy transparency! It only copies color data but alpha bit values remain the same as they were of original image (the image being copied onto). So when I filled alpha bits with 0 (0 = 100% transparent) it remained this way, i.e. all invisible, and I indeed wasn't seeing any picture on my screen even though its color data has apparenty being copied there by means of Draw.

The solution

Then I came up with this handy function that draws a PNG image onto another one, summing transparency bytes of both:

pascalprocedure DrawPngWithAlpha(Src, Dest: TPNGObject; const R: TRect);
var
  X, Y: Integer;
  Alpha: PByte;
begin
  Src.Draw(Dest.Canvas, R);

  // I have no idea why standard implementation of TPNGObject.Draw doesn't apply transparency.
  for Y := R.Top to R.Bottom - 1 do
    for X := R.Left to R.Right - 1 do
    begin
      Alpha := @Dest.AlphaScanline[Y]^[X];
      Alpha^ := Min(255, Alpha^ + Src.AlphaScanline[Y - R.Top]^[X - R.Left]);
    end;
end;

The above function only works for non-scaled source (Src) image – meaning that X and Y coords have the same distances for both source and destination images.

Blank transparent PNG? Easy!

Judging from above info you can probably guess how you create a blank PNG image filled with transparent (100%-alpha) pixels – simply by creating a blank COLOR_RGBALPHA image and resetting all alpha bytes to 0 (the value of 255 will make pixel 0% transparent, i.e. totally opaque).

I've heard some people on the net asking why they don't see anything after creating a COLOR_RGBALPHA PNG – I guess this happens because they have a transparent PNG now but standard Draw doesn't change pixels' alpha when something is painted on it.

pascalprocedure CleanTransparentPngTo(var PNG: TPNGObject; NewWidth, NewHeight: Integer);
var
  BasePtr: Pointer;
begin
  PNG.Free;
  PNG := TPNGObject.CreateBlank(COLOR_RGBALPHA, 16, NewWidth, NewHeight);

  BasePtr := PNG.AlphaScanline[0];
  ZeroMemory(BasePtr, PNG.Header.Width * PNG.Header.Height);
end;

You can avoid Freeing the PNG and use Resize method instead, although you might still need to change the image's format so it would support transparency.

PNG speed button

I've always had passion for (reasonably) large buttons and Delphi with its one-or-zero transparency, in addition flowing from pic's left-bottom corner was giving me a sort of uneasy feeling. PNGs are so beautiful especially when they are large! :)

So here's the class that inherits from standard TSpeedButton providing it with a PNG glyph and in addition extending it by a few useful features:

  1. It has a WideString caption, meaning it is capable of displaying any Unicode character out there.
  2. It allows you to specify 3 optional images to be combined together in fashion like «first + second» where image in the middle ( pascalCombineWith: TPNGObject) is overlaid over first and second (Combine1 and Combine2).

It also conforms to Layout meaning that you can create buttons with glyphs shown on their left, right, top and bottom sides – just like usual TSpeedButton.

Download TPNGSpeedButton component (it has no external dependencies except for TPNGObject itself).

If you haven't installed any custom component yet simply go to Component → Install component menu. Note that this class was written and tested on Delphi of 7th version only.