How do I copy a transparent TBitmap to the clipboard?

How do I copy a transparent TBitmap to the clipboard?

(Need a tested snippet as a fix or replacement for
unit CCR.FMXClipboard.Win
to copy a FMX TBitmap to the clipboard in XE4.)

There is a blog topic:
https://delphihaven.wordpress.com/2013/07/22/fmx-tclipboard-and-tmacpreferencesinifile-xe4/
which says: "On Windows again, when a source bitmap includes transparency, the outputted DIB has this transparency removed, however at the same time a PNG representation is added that maintains the original alpha channel."

My problem: the transparent png does not seem to work, I mean it cannot be pasted into Photoshop.

//using transparent background:
Viewport.Color := claNull;

procedure TForm1.CopyBitmap;
var
  bmp: TBitmap;
  r: TRect;
begin
  r := Rect(0, 0, Viewport.Width, Viewport.Height);
  bmp := TBitmap.Create(r.Width, r.Height);
  Viewport.Context.CopyToBitmap(bmp, r);
  //bmp.SaveToFile(filename); //works!
  Clipboard.Assign(bmp); //DIB ok, not what I want
  //Clipboard.Assign(cfPNG, ABitmap); //nope
  bmp.Free;
end;

I can paste the DIB (without transparency) into Microsoft Paint, but this is no longer good enough. The new goal is to copy and paste a transparent png image (TBitmap) directly from my application to Photoshop Elements.

Comments

  1. Untested, but an idea: you could try saving it to a stream - it streams in PNG format (it's a whole, valid PNG file in the stream.)  Then copy the stream to the clipboard marking the data as PNG?

    ReplyDelete
  2. I tried this (to copy the png only), no error, but nothing can be pasted then in Paint or Photoshop. (Not quite sure how to mark it as png.) I think I need a tested snippet, have already googled for one, but nope :)

    ReplyDelete
  3. Chris Rolliston is the author of the unit, btw.

    the unit under test looks like so:

    procedure TWinClipboard.DoAssignBitmap(const ABitmap: TBitmap);
    begin
      DoAssignBitmap1(ABitmap);
    end;

    //Trimmed down method
    procedure TWinClipboard.DoAssignBitmap1(const ABitmap: TBitmap);
    var
      Stream: TMemoryStream;
    begin
      Stream := TMemoryStream.Create;
      try
        ABitmap.SaveToStream(Stream);
        DoAssignBuffer(cfPNG, Stream.Memory^, Stream.Size);
      finally
        Stream.Free;
      end;
    end;

    procedure TWinClipboard.DoAssignBuffer(const AFormat: TClipboardFormat; const ABuffer; ASize: Integer);
    var
      DataHandle: HGLOBAL;
      Ptr: PByte;
    begin
      DataHandle := GlobalAlloc(GMEM_MOVEABLE or GMEM_DDESHARE, ASize);
      if DataHandle = 0 then RaiseLastOSError;
      Ptr := GlobalLock(DataHandle);
      try
        if Ptr = nil then
        begin
          GlobalFree(DataHandle);
          RaiseLastOSError;
        end;
        Move(ABuffer, Ptr^, ASize);
      finally
        GlobalUnlock(DataHandle);
      end;
      SetClipboardData(AFormat, DataHandle);
    end;

    ReplyDelete
  4. > "Not quite sure how to mark it as png."
    SetClipboardData is where you set the format - try something like:
    CF_PNG := RegisterClipboardFormat('PNG');
    and then use that for the format parameter.

    ReplyDelete
  5. OK, progress, I managed to copy and paste a transparent png into Paint.net, but not yet into Photoshop Elements.

    ReplyDelete
  6. Interesting. It probably depends on the formats Photoshop is looking for. Can you copy a PNG in a way that already works for Photoshop, then use Clipboard Viewer to see what formats are on the clipboard?  (I can't remember if that program shows the codes or not - can't access it right now - but if not there's bound to be a freeware utility somewhere that will show you the same info.)

    ReplyDelete
  7. Searched for "photoshop clipboard formats" and found
    http://stackoverflow.com/questions/12258519/how-to-save-pngimage-from-clipboard
    I will use this as an entry point for further reading...

    ReplyDelete
  8. I confess I've never used Photoshop. If you can find a reliable way for Photoshop to accept transparent bitmaps via the clipboard I'll add it to the published CCR.FMXClipboard.Win code soon enough...

    ReplyDelete
  9. Did some reading, and some testing. TBitmap is a nice class, SafeToFile is the way to go. No change needed in CCR.FMXClipboard.Win.pas, You are simply not supposed to import a transparent Bitmap into Photoshop through the clipboard. Normally, Clipboard.Assign(cfPNG, ABitmap) is the alternative to SaveToFile for transparent images. (Just be aware of this overload before you complain.)

    :)

    //Pseudocode used for testing
    procedure CopyBitmapToClipboard(ABitmap: TBitmap);
    begin
      if HardCopyFlag then
        ABitmap.SaveToFile(filename)
      else if PngCopyFlag then
        Clipboard.Assign(cfPNG, ABitmap)
      else
        Clipboard.Assign(ABitmap);
    end;

    //Tried to paste the copied image into
    a) MS Paint, from Windows 8
    b) PSE, Photoshop Elements Editor version 8.0
    c) Paint.net, version 4.0.3
    d) Gimp, version 2.8.14

    1. CCR.FMXClipboard.pas / Clipboard.Assign(cfPNG, ABitmap)

    Format: PNG (49288)

    a) MS Paint no (expected)
    b) PSE no (surprise)
    c) Paint.net yes
    d) Gimp yes

    2. CCR.FMXClipboard.pas / Clipboard.Assign(ABitmap)
     
    Format: CF_DIBV5
    Width: 1024 px
    Height: -768 px
    Bit depth: 32 bpp
    Compression: BI_BITFIELDS

    a) MS Paint yes (w/o transparency)
    b) PSE no (bummer)
    c) Paint.net no (surprise)
    d) Gimp yes (transparency preserved)

    3. Image copied from MS Paint (not transparent)

    Format: CF_DIBV5
    Width: 1024 px
    Height: 768 px
    Bit depth: 24 bpp
    Compression: BI_RGB

    a) MS Paint yes (expected)
    b) PSE (yes, if image is RGB)
    c) Paint.net yes
    d) Gimp yes

    Findings:

    * Clipboard.Assign(cfPNG, ABitmap) works great for transfer of transparent TBitmap to Paint.net and Gimp, but does not work with Photoshop at all.

    * Keep new feature in App: Pass transparent png to Photoshop by redefining the meaning of "copy to clipboard" such that it uses SaveToFile with known location, followed by drag and drop from Explorer.

    * Clipboard.Assign(ABitmap) works with MS Paint if there is no transparency.

    * Perhaps CCR.FMXClipboard.Win should copy 24 bit DIBV5 for best support of TBitmap without Alpha?

    ReplyDelete
  10. Gustav Schubert Nice work! And very strange that PS won't paste those formats.

    ReplyDelete
  11. Gustav Schubert
    When using Clipboard.Assign(ABitmap) on a bitmap with transparency, the code puts both a PNG and a top-down DIBV5 onto the clipboard. IMO when reading from the clipboard Paint.NET should be doing what my code does (where should <> is!), which is to look for a PNG first, and a DIB only secondly as a fallback. What version of Paint.NET are you using out of interest?

    Also, and without expecting this to work, but what happens if you copy a PNG image file in Windows Explorer - does Photoshop's Paste function work then?

    ReplyDelete
  12. Chris Rolliston 

    1. When I copy the png image (saved from Delphi with SaveToFile)
    from Explorer using right mouse button context menu, I have this on the clipboard:

    Shell IDList Array (49328)
    DataObjectAttributes (49507)
    DataObjectAttributesRequiringElevation (49509)
    Standard clipboard format (CF_HDROP)
    FileName (49158)
    FileContents (49331)
    FileNameW (49159)
    FileGroupDescriptorW (49333)
    UIDisplayed (49356)
    DropDescription (49353)
    DropEffectFolderList (49348)
    Shell Object Offsets (49329)
    Preferred DropEffect (49338)
    AsyncFlag (49355)

    I can paste the image into paint.net v4.0.3,
    but cannot paste into Photoshop Elements 8.0.

    2. When using Clipboard.Assign(ABitmap) on a bitmap with transparency, transparency is lost when pasted into paint.net.
    I see this on the clipboard:

    PNG (49287)
    Standard clipboard format (CF_DIBV5)
    Standard clipboard format (CF_BITMAP)
    Standard clipboard format (CF_DIB)
    -----------------------------------------------------------------
    Clipboard data of CF_DIBV5 format:
    -----------------------------------------------------------------
    Width: 1024 px
    Height: -748 px
    Bit depth: 32 bpp
    Compression format: Not compressed with color masks (BI_BITFIELDS)

    So paint.net may not behave as expected!

    (3. Added "if HasTransparency then Break;"
    to the outer loop where it tests for HasTransparency
    in TWinClipboard.DoAssignBitmap. Just a cosmetic thing,
    so that I can leave the finger on F7 when debugging.)

    ReplyDelete
  13. Gustav Schubert
    Thanks. I'll have a look at re-working the code to use the OLE clipboard API instead of the flat-function version, which will allow adding FileGroupDescriptorW and FileContents to the formats uploaded. Can you confirm your Paint.NET version though?

    ReplyDelete
  14. Chris Rolliston My Paint.NET version is 4.0.3, which I downloaded days ago.

    ReplyDelete
  15. Gustav Schubert
    Sorry, I now see you had actually reported your Paint.NET version originally.

    I've got a revised version of my code nearly ready; while I doubt it will help with Photoshop, the issue with Paint.NET you mention will be fixed. Main new feature will be delayed rendering support on desktop platforms, once I can figure out why Windows is prone to requesting a DIB immediately (which rather defeats the object of delayed rendering support)...

    ReplyDelete
  16. Gustav Schubert
    Yes, though nothing to do with RDP specificially. Instead of providing the data upfront, you provide a way for the system to request it when someone actually asks for it. Explicit delay rendering will be enabled with new Assign overloads, though internally data will always be delay rendered from the system point of view - i.e., use one of the existing assignment methods and the data will be held in memory by TClipboard until the system requests it.

    ReplyDelete

Post a Comment