6

Background

I have written a function, which creates a custom cursor, based on the bitmap associated with a given Device Context. I use this to create drag-and-drop cursors that appear as "tear-offs" - a bit like they are used in "Trello".

I've been using the function for a while without problems, but when I use it with a new tree component I'm working on it started creating partially blank cursors.

I have verified that the problem occur both in Delphi 2010 and Delphi Berlin, and I have also verified that it's broken both in Windows 7 and Windows 10.

Here is a photo that shows what the cursor should look like (Sorry - couldn't find a quick way to screen-grab a cursor):

enter image description here

And here is what it looks like when it's partially blank (well, it's more than partially blank - it's practically invisible):

enter image description here

Troubleshooting

After troubleshooting I've found, that if a PNG-image is written to the bitmap associated with the DC before a call to GetDragCursor, the cursor is messed up.

Here is the simplest code I can think of that demonstrate the problem:

A form with two TPaintBox components: MyPaintBoxWorks and MyPaintBoxBroken.

  • When you click on MyPaintBoxWorks, you get the expected cursor.
  • When you click on MyPaintBoxBroken you just get the png-image.

In the name of making it easy to read (I hope), I've excluded all error- and resource-handling. This has no impact on the problem. In order for it to work, you need to have access to a Png-image. Any png-image will do. Then update the code to load your image instead.

uses
  Types,
  pngimage;

//////////////////////////////////////////////////////////////////////
procedure TMyForm.FormPaint(Sender: TObject);
begin
  MyPaintBoxWorks.Canvas.Brush.Color := clGreen;
  MyPaintBoxWorks.Canvas.Rectangle( 0, 0,
                                    MyPaintBoxWorks.Width, MyPaintBoxWorks.Height );
  MyPaintBoxBroken.Canvas.Brush.Color := clRed;
  MyPaintBoxBroken.Canvas.Rectangle( 0, 0,
                                     MyPaintBoxBroken.Width, MyPaintBoxBroken.Height );
end;

function GetDragCursor( Handle: HDC;
                        Width, Height: integer;
                        CursorX, CursorY: integer ): TCursor; forward;


//////////////////////////////////////////////////////////////////////
procedure TMyForm.MyPaintBoxWorksMouseDown( Sender: TObject;
                                            Button: TMouseButton;
                                            Shift: TShiftState;
                                            X, Y: Integer);
begin
  Screen.Cursor := GetDragCursor( MyPaintBoxWorks.Canvas.Handle,
                                  MyPaintBoxWorks.Width, MyPaintBoxWorks.Height,
                                  X, Y );
end;


//////////////////////////////////////////////////////////////////////
procedure TMyForm.MyPaintBoxBrokenMouseDown( Sender: TObject;
                                             Button: TMouseButton;
                                             Shift: TShiftState;
                                             X, Y: Integer );
var
  Img: TPngImage;

begin
  Img := TPngImage.Create;
  Img.LoadFromFile( 'D:\TestImage.png' );
  Img.Draw( MyPaintBoxBroken.Canvas, Rect( 20, 20, 40, 40 ) );
  Screen.Cursor := GetDragCursor( MyPaintBoxBroken.Canvas.Handle,
                                  MyPaintBoxBroken.Width, MyPaintBoxBroken.Height,
                                  X, Y );
end;


//////////////////////////////////////////////////////////////////////
function GetDragCursor( Handle: HDC;
                        Width, Height: integer;
                        CursorX, CursorY: integer ): TCursor;
var
  MaskDC           : HDC;
  OrgMaskBmp       : HBITMAP;
  MaskBmp          : HBITMAP;
  ColourDC         : HDC;
  OrgColourBmp     : HBITMAP;
  ColourBmp        : HBITMAP;
  IconInfo         : TIconInfo;
  Brush            : HBRUSH;

begin
  // Create Colour bitmap
  // ====================
  ColourDC := CreateCompatibleDC( Handle );
  ColourBmp := CreateCompatibleBitmap( Handle, Width, Height );
  OrgColourBmp := SelectObject( ColourDC, ColourBmp );

  BitBlt( ColourDC, 0, 0, Width, Height, Handle, 0, 0, SRCCOPY );

  SelectObject( ColourDC, OrgColourBmp );

  // Create Mask bitmap
  // ==================
  MaskDC := CreateCompatibleDC( Handle );
  MaskBmp := CreateCompatibleBitmap( Handle, Width, Height );
  OrgMaskBmp := SelectObject( MaskDC, MaskBmp );

  // Fill with white
  Brush := CreateSolidBrush( $FFFFFF );
  FillRect( MaskDC, Rect( 0, 0, Width, Height ), Brush );
  DeleteObject( Brush );

  // Fill masked area with black
  Brush := CreateSolidBrush( $000000 );
  FillRect( MaskDC, Rect( 0, 0, Width, Height ), Brush );
  DeleteObject( Brush );

  SelectObject( MaskDC, OrgMaskBmp );

  // Create and set cursor
  // =====================
  with iconInfo do
  begin
    fIcon :=    FALSE;
    xHotspot := CursorX;
    yHotspot := CursorY;
    hbmMask :=  MaskBmp;
    hbmColor := ColourBmp;
  end;
  Screen.Cursors[1] := CreateIconIndirect( iconInfo );
  Result := 1;
end;

I have studied the function and Microsofts documentation at length, and I cannot find anything wrong with the function.

I have also studied TPngImage.Draw and cannot see anything obvious wrong with it (I shouldn't hope so). The function:

  • Calls TPngImage.DrawPartialTrans, which in turn
  • Creates a bitmap via CreateDIBSection
  • Scans through the pixels and computes alpha-blended RGB values
  • Use pointer-arithmetics to move through the pixel-buffer
  • Makes a call to BitBlt to copy the final image into the DC

(I've included the code for the function at the end of the question for reference)

The cursors are always generated correctly if I:

  • Comment out the code that writes to the pixel-buffer, or
  • Only scan the first couple of rows in the image, or
  • Comment out the final call to BitBlt

This looks like a buffer-overrun, but there is nothing in the code that seems to support this. Also, it's more likely that it is my code that is at fault.

Question

Is there anything in either my function GetDragCursor or DrawPartialTrans that is wrong or looks suspicious?

procedure TPngImage.DrawPartialTrans(DC: HDC; Rect: TRect);
  {Adjust the rectangle structure}
  procedure AdjustRect(var Rect: TRect);
  var
    t: Integer;
  begin
    if Rect.Right < Rect.Left then
    begin
      t := Rect.Right;
      Rect.Right := Rect.Left;
      Rect.Left := t;
    end;
    if Rect.Bottom < Rect.Top then
    begin
      t := Rect.Bottom;
      Rect.Bottom := Rect.Top;
      Rect.Top := t;
    end
  end;

type
  {Access to pixels}
  TPixelLine = Array[Word] of TRGBQuad;
  pPixelLine = ^TPixelLine;
const
  {Structure used to create the bitmap}
  BitmapInfoHeader: TBitmapInfoHeader =
    (biSize: sizeof(TBitmapInfoHeader);
     biWidth: 100;
     biHeight: 100;
     biPlanes: 1;
     biBitCount: 32;
     biCompression: BI_RGB;
     biSizeImage: 0;
     biXPelsPerMeter: 0;
     biYPelsPerMeter: 0;
     biClrUsed: 0;
     biClrImportant: 0);
var
  {Buffer bitmap creation}
  BitmapInfo  : TBitmapInfo;
  BufferDC    : HDC;
  BufferBits  : Pointer;
  OldBitmap,
  BufferBitmap: HBitmap;
  Header: TChunkIHDR;

  {Transparency/palette chunks}
  TransparencyChunk: TChunktRNS;
  PaletteChunk: TChunkPLTE;
  TransValue, PaletteIndex: Byte;
  CurBit: Integer;
  Data: PByte;

  {Buffer bitmap modification}
  BytesPerRowDest,
  BytesPerRowSrc,
  BytesPerRowAlpha: Integer;
  ImageSource, ImageSourceOrg,
  AlphaSource     : pByteArray;
  ImageData       : pPixelLine;
  i, j, i2, j2    : Integer;

  {For bitmap stretching}
  W, H            : Cardinal;
  Stretch         : Boolean;
  FactorX, FactorY: Double;

begin
  {Prepares the rectangle structure to stretch draw}
  if (Rect.Right = Rect.Left) or (Rect.Bottom = Rect.Top) then exit;
  AdjustRect(Rect);
  {Gets the width and height}
  W := Rect.Right - Rect.Left;
  H := Rect.Bottom - Rect.Top;
  Header := Self.Header; {Fast access to header}
  Stretch := (W <> Header.Width) or (H <> Header.Height);
  if Stretch then FactorX := W / Header.Width else FactorX := 1;
  if Stretch then FactorY := H / Header.Height else FactorY := 1;

  {Prepare to create the bitmap}
  Fillchar(BitmapInfo, sizeof(BitmapInfo), #0);
  BitmapInfoHeader.biWidth := W;
  BitmapInfoHeader.biHeight := -Integer(H);
  BitmapInfo.bmiHeader := BitmapInfoHeader;

  {Create the bitmap which will receive the background, the applied}
  {alpha blending and then will be painted on the background}
  BufferDC := CreateCompatibleDC(0);
  {In case BufferDC could not be created}
  if (BufferDC = 0) then RaiseError(EPNGOutMemory, EPNGOutMemoryText);
  BufferBitmap := CreateDIBSection(BufferDC, BitmapInfo, DIB_RGB_COLORS, BufferBits, 0, 0);
  {In case buffer bitmap could not be created}
  if (BufferBitmap = 0) or (BufferBits = Nil) then
  begin
    if BufferBitmap <> 0 then DeleteObject(BufferBitmap);
    DeleteDC(BufferDC);
    RaiseError(EPNGOutMemory, EPNGOutMemoryText);
  end;

  {Selects new bitmap and release old bitmap}
  OldBitmap := SelectObject(BufferDC, BufferBitmap);

  {Draws the background on the buffer image}
  BitBlt(BufferDC, 0, 0, W, H, DC, Rect.Left, Rect.Top, SRCCOPY);

  {Obtain number of bytes for each row}
  BytesPerRowAlpha := Header.Width;
  BytesPerRowDest := (((BitmapInfo.bmiHeader.biBitCount * W) + 31)
    and not 31) div 8; {Number of bytes for each image row in destination}
  BytesPerRowSrc := (((Header.BitmapInfo.bmiHeader.biBitCount * Header.Width) +
    31) and not 31) div 8; {Number of bytes for each image row in source}

  {Obtains image pointers}
  ImageData := BufferBits;
  AlphaSource := Header.ImageAlpha;
  Longint(ImageSource) := Longint(Header.ImageData) +
    Header.BytesPerRow * Longint(Header.Height - 1);
  ImageSourceOrg := ImageSource;

  case Header.BitmapInfo.bmiHeader.biBitCount of
    {R, G, B images}
    24:
      FOR j := 1 TO H DO
      begin
        {Process all the pixels in this line}
        FOR i := 0 TO W - 1 DO
        begin
          if Stretch then i2 := trunc(i / FactorX) else i2 := i;
          {Optmize when we don´t have transparency}
          if (AlphaSource[i2] <> 0) then
            if (AlphaSource[i2] = 255) then
            begin
              pRGBTriple(@ImageData[i])^ := pRGBTriple(@ImageSource[i2 * 3])^;
              ImageData[i].rgbReserved := 255;
            end
            else
              with ImageData[i] do
              begin
                rgbRed := ($7F + ImageSource[2+i2*3] * AlphaSource[i2] + rgbRed *
                  (not AlphaSource[i2])) div $FF;
                rgbGreen := ($7F + ImageSource[1+i2*3] * AlphaSource[i2] +
                  rgbGreen * (not AlphaSource[i2])) div $FF;
                rgbBlue := ($7F + ImageSource[i2*3] * AlphaSource[i2] + rgbBlue *
                 (not AlphaSource[i2])) div $FF;
                rgbReserved := not (($7F + (not rgbReserved) * (not AlphaSource[i2])) div $FF);
            end;
          end;

        {Move pointers}
        inc(Longint(ImageData), BytesPerRowDest);
        if Stretch then j2 := trunc(j / FactorY) else j2 := j;
        Longint(ImageSource) := Longint(ImageSourceOrg) - BytesPerRowSrc * j2;
        Longint(AlphaSource) := Longint(Header.ImageAlpha) +
          BytesPerRowAlpha * j2;
      end;
    {Palette images with 1 byte for each pixel}
    1,4,8: if Header.ColorType = COLOR_GRAYSCALEALPHA then
      FOR j := 1 TO H DO
      begin
        {Process all the pixels in this line}
        FOR i := 0 TO W - 1 DO
          with ImageData[i], Header.BitmapInfo do begin
            if Stretch then i2 := trunc(i / FactorX) else i2 := i;
            rgbRed := ($7F + ImageSource[i2] * AlphaSource[i2] +
              rgbRed * (not AlphaSource[i2])) div $FF;
            rgbGreen := ($7F + ImageSource[i2] * AlphaSource[i2] +
              rgbGreen * (not AlphaSource[i2])) div $FF;
            rgbBlue := ($7F + ImageSource[i2] * AlphaSource[i2] +
              rgbBlue * (not AlphaSource[i2])) div $FF;
            rgbReserved := not (($7F + (not rgbReserved) * (not AlphaSource[i2])) div $FF);
          end;

        {Move pointers}
        Longint(ImageData) := Longint(ImageData) + BytesPerRowDest;
        if Stretch then j2 := trunc(j / FactorY) else j2 := j;
        Longint(ImageSource) := Longint(ImageSourceOrg) - BytesPerRowSrc * j2;
        Longint(AlphaSource) := Longint(Header.ImageAlpha) +
          BytesPerRowAlpha * j2;
      end
    else {Palette images}
    begin
      {Obtain pointer to the transparency chunk}
      TransparencyChunk := TChunktRNS(Chunks.ItemFromClass(TChunktRNS));
      PaletteChunk := TChunkPLTE(Chunks.ItemFromClass(TChunkPLTE));

      FOR j := 1 TO H DO
      begin
        {Process all the pixels in this line}
        i := 0;
        repeat
          CurBit := 0;
          if Stretch then i2 := trunc(i / FactorX) else i2 := i;
          Data := @ImageSource[i2];

          repeat
            {Obtains the palette index}
            case Header.BitDepth of
              1: PaletteIndex := (Data^ shr (7-(I Mod 8))) and 1;
            2,4: PaletteIndex := (Data^ shr ((1-(I Mod 2))*4)) and $0F;
             else PaletteIndex := Data^;
            end;

            {Updates the image with the new pixel}
            with ImageData[i] do
            begin
              TransValue := TransparencyChunk.PaletteValues[PaletteIndex];
              rgbRed := (255 + PaletteChunk.Item[PaletteIndex].rgbRed *
                 TransValue + rgbRed * (255 - TransValue)) shr 8;
              rgbGreen := (255 + PaletteChunk.Item[PaletteIndex].rgbGreen *
                 TransValue + rgbGreen * (255 - TransValue)) shr 8;
              rgbBlue := (255 + PaletteChunk.Item[PaletteIndex].rgbBlue *
                 TransValue + rgbBlue * (255 - TransValue)) shr 8;
            end;

            {Move to next data}
            inc(i); inc(CurBit, Header.BitmapInfo.bmiHeader.biBitCount);
          until CurBit >= 8;
          {Move to next source data}
          //inc(Data);
        until i >= Integer(W);

        {Move pointers}
        Longint(ImageData) := Longint(ImageData) + BytesPerRowDest;
        if Stretch then j2 := trunc(j / FactorY) else j2 := j;
        Longint(ImageSource) := Longint(ImageSourceOrg) - BytesPerRowSrc * j2;
      end
    end {Palette images}
  end {case Header.BitmapInfo.bmiHeader.biBitCount};

  {Draws the new bitmap on the foreground}
  BitBlt(DC, Rect.Left, Rect.Top, W, H, BufferDC, 0, 0, SRCCOPY);

  {Free bitmap}
  SelectObject(BufferDC, OldBitmap);
  DeleteObject(BufferBitmap);
  DeleteDC(BufferDC);
end;
General Grievance
  • 4,555
  • 31
  • 31
  • 45
  • Note: I've tested in Delphi Berlin as well. It doesn't work here either. Maybe no surprising, as the pngimage-unit is largely unchanged – Mads Boyd-Madsen Nov 23 '17 at 08:56
  • 2
    Not very realistic to expect someone to look at this without a MCVE. – Sertac Akyuz Nov 25 '17 at 13:33
  • Yes - Fair enough. I will try to reduce the problem to it's minimum parts. – Mads Boyd-Madsen Nov 26 '17 at 09:30
  • I have updated the question, and I think it should now be an MCVE. – Mads Boyd-Madsen Nov 26 '17 at 10:43
  • Seems like the problem is with the png draw. Your `GetDragCursor` works fine for me. Are you willing to try GDI+ instead? – kobik Nov 26 '17 at 14:27
  • Thanks for looking into this. Yes - I could use GDI+. I'm not very familiar with it though.Will it do png-draw? Also, I'm not so keen on blaming an Embarcaderro component. I mean - I have to feel very sure before I would do that. – Mads Boyd-Madsen Nov 26 '17 at 14:39
  • Blitting is ok. Icon is strange... @kobik - do you get the full square, not just the png? – Sertac Akyuz Nov 27 '17 at 00:55
  • @SertacAkyuz, I got stuck on a strange/minor issue. I'm unable to BitBlt (nor AlpaBlend) the canvas of the Painbox onto a larger 32bit TBitmap with entre alpha transparency. otherwise if I prepare that initial bitmap with photoshop and use GDI+ to draw the PNG on top of it, the `GetDragCursor` works fine. – kobik Nov 27 '17 at 05:06

1 Answers1

4

I was able to make it work with GDI+.
Seems like the Delphi png draw does not paint well on a transparent 32bit Bitmap. (* see EDIT)

Your GetDragCursor worked well for me.

I used a TPaintBox with height of 16. and loaded a PNG with size of 32x32. and used a 32bit off-screen bitmap to create the cursor.

uses GDIPOBJ, GDIPAPI;

procedure TForm1.FormCreate(Sender: TObject);
begin
  PaintBox1.Height := 16;
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  PaintBox1.Canvas.Brush.Color := clRed;
  PaintBox1.Canvas.Rectangle(0, 0, PaintBox1.Width, PaintBox1.Height );
end;

procedure GPDrawImageOver(Image: TGPImage; dc: HDC; X, Y: Integer);
var
  Graphics: TGPGraphics;
begin
  Graphics := TGPGraphics.Create(dc);
  try
    Graphics.SetCompositingMode(CompositingModeSourceOver);
    Graphics.DrawImage(Image, X, Y, Image.GetWidth, Image.GetHeight);
  finally
    Graphics.Free;
  end;
end;

procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Bmp: TBitmap;
  Png: TGPImage;
  x1, y1: Integer;
  px: PRGBQuad;
begin
  Bmp := TBitmap.Create;
  try
    Png := TGPImage.Create('C:\Users\Kobik\Downloads\Internet Explorer.png');
    try

      Bmp.Width := PaintBox1.Width;
      Bmp.Height := Png.GetHeight;
      Bmp.PixelFormat := pf32bit;
      Bmp.HandleType := bmDIB;
      Bmp.IgnorePalette := True;

      // paint PaintBox1 canvas on the bitmap
      BitBlt(Bmp.Canvas.Handle, 0, 0, PaintBox1.Width, PaintBox1.Height,
        PaintBox1.Canvas.Handle, 0, 0, SRCCOPY);

      // make the bottom bitmap part transparent
      for y1 := 0 to Bmp.Height - 1 do
      begin
        px := Bmp.ScanLine[y1];
        for x1 := 0 to Bmp.Width - 1 do
        begin
          if y1 < PaintBox1.Height then
            px.rgbReserved := 255 // opaque
          else
            px.rgbReserved := 0;  // fully transparent
          Inc(px);
        end;
      end;

      // draw png over the bitmap
      GPDrawImageOver(Png, Bmp.Canvas.Handle, 0, 0);
    finally
      Png.Free;
    end;

    Screen.Cursor := GetDragCursor(Bmp.Canvas.Handle,
      Bmp.Width, Bmp.Height, X, Y);
  finally
    Bmp.Free;
  end;
end;

The result bitmap looks like this (where the bottom part is fully transparent):

enter image description here


EDIT: The GDI+ is actually not needed (My initial answer was based with Delphi 7 in which DrawPartialTrans is not accurate) .

In newer Delphi versions the TPngImage.DrawPartialTrans works just fine from the little tests I have made.

However, preparing and using the off-screen Bitmap like I did, is the correct way to go.
You can use the same code above, but instead of using a TGPImage simply use a TPngImage.

kobik
  • 21,001
  • 4
  • 61
  • 121
  • Thank you for that. While it doesn't explain why the pngimage functions do not work, I'm satisfied with the solution. I see clearly that I should be using GDI+ for all my drawing functionality. – Mads Boyd-Madsen Nov 27 '17 at 08:16
  • 1
    @MadsBoyd-Madsen, The pngimage does not handle 32bit target bitmaps with alpha, when drawing on them. it does not take the target alpha into account. (and it always uses BitBlt to draw, while it probably needs to use AlphaBlend for 32bit target bitmaps). while GDI+ drawing paints correctly on a 32bit+alpha bitmaps. – kobik Nov 27 '17 at 10:20
  • 1
    Cool. It still doesn't really explain why further blitter-operations don't work correctly at all. – Mads Boyd-Madsen Nov 27 '17 at 10:28
  • I believe what matters in this code is *not using a device bitmap* and *intervention with the alpha channel*, not using GDIplus. Make these same changes to the code in the reproduction case in the question and it works too. – Sertac Akyuz Nov 27 '17 at 15:28
  • I still can't get around my head though on how the alpha channel survives after two blits (file to window dc and window dc to memory dc) through an API (BitBlt) that doesn't support partial transparency (alpha channel) at all. – Sertac Akyuz Nov 27 '17 at 15:32
  • @SertacAkyuz, Does replacing GDI+ drawing with TPngImage.Draw works fine? (I was testing with older D7 version). If it does my answer is pointless. Also, *"I still can't get around my head though on how the alpha channel survives after two blits"* do you refer to the GetDragCursor function? – kobik Nov 27 '17 at 16:15
  • @SertacAkyuz, You know what - Neither do I. TPngImage.DrawPartialTrans is quite peculiar. It writes the alpha-data directly to rgbReserved **and** computes alpha-reduced rgb-values but *without* blending with the background. Where the alpha-source is 0, no painting occur and the background will "shine" through. So effectively it looks like poor-mans alpha-blending, that preserves transparency simply by the fact that the background isn't overwritten. Does that make sense? – Mads Boyd-Madsen Nov 27 '17 at 16:16
  • As I see it, once the png image is transferred to the paint box, transparently over any existing drawing, its job is done. There doesn't seem to be anything wrong with that part. What the code in the repro case essentially does is to capture part of the screen - grab a bitmap from a window device context through BitBlt. The resulting bitmap shouldn't have any alpha channel information at all. Apparently I know wrong... – Sertac Akyuz Nov 27 '17 at 17:01
  • @kobik - In the reproduction case, discard the memory context/bitmap and use a regular TBitmap, don't leave it with pfDevice use a pf32bit one. Capture the screen and then modify its alpha channel like you do in your answer. Then create the cursor. It seems to work fine. – Sertac Akyuz Nov 27 '17 at 17:02
  • @sertac but that is preaty much what I did. except when I used the pngimage draw, it produced a currupted cursor. Im not sure I follow you. Also I assumed it is wrong to paint on the paintbox canvas and use that DC like the op did. – kobik Nov 27 '17 at 17:24
  • 1
    @kobik - What I assumed was you already have a graphic drawn on the screen, like a sort arrow on a header for instance. And then you want to drag that header and hence want to capture the header as a whole with the graphic on it for a drag cursor. What I assumed may or may not be correct but I don't think it will make a difference. The other part, I just commented on that GDI+ is not relevant/required. Possibility is that D7 png components you use have problems. – Sertac Akyuz Nov 27 '17 at 17:32
  • @SertacAkyuz, You are correct. I used the `TPngImage.DrawPartialTrans` in my D7 code, and it seems to work just fine (from the very little tests i have made). No GDI+ is needed. I will edit my answer. – kobik Nov 27 '17 at 19:59
  • I have looked at the differences between TPngImage.Draw and TPngImage.DrawPartialTrans in Delphi 10 and Delphi Berlin, and there are none (apart from using pByte instead of longint when casting buffers, but that shouldn't matter). I'm going to test calls to DrawPartialTrans directly, though I'm not hopeful. – Mads Boyd-Madsen Nov 27 '17 at 20:59
  • @MadsBoyd-Madsen, The `TPngImage.Draw` calls `TPngImage.DrawPartialTrans` (when `TransparencyMode` is `ptmPartial`), so no need to use it directly. What I meant is that in my Delphi 7 test project I replaced that method and used *your* `DrawPartialTrans` from a newer Delphi version. Delphi 7 does not handle the `rgbReserved` background, while your version does. – kobik Nov 28 '17 at 06:09
  • @kobik, Ah - I see. Ok.- So: *your* D7 version of DrawPartialTrans works whereas the one I've shown above from D10 doesn't? Could you post your version so I can look for differences? - Or are you guys already past that point? – Mads Boyd-Madsen Nov 28 '17 at 08:15
  • @MadsBoyd-Madsen, No. it's the other way around. *your* version (the one you shown above) works. I used that in Delphi 7, and the PNG `Draw` worked fine. As I said, try to replace the `GPDrawImageOver` with `TPngImage.Draw` in the code I posed... – kobik Nov 28 '17 at 08:56