Copying and pasting the contents of a FireMonkey TBitmap

One of the various things not yet implemented in FireMonkey is TClipboard. In FMX.Platform you can find routines for getting and setting text on the clipboard (using Variant arguments – I’ve no idea either), but nothing beyond that. On the forums, Mehmed Ali Caliskan posted some code (apparently inspired by those FMX.Platform methods) for saving the contents of a TBitmap to the clipboard on the Mac. Starting with that, I’ve come up with some routines for copying and pasting FMX bitmap contents to the clipboard on both OS X and Windows. These routines have the following signatures:

function CanPasteBitmapFromClipboard: Boolean;
procedure CopyBitmapToClipboard(Bitmap: TBitmap);
function PasteBitmapFromClipboard(Bitmap: TBitmap): Boolean;

Before I present the code, it is worth taking a step back to highlight how TBitmap in FireMonkey covers the ground of both TPicture and TBitmap in the VCL. As a result, instead of having different TGraphic descendants for different sorts of bitmap formats (TBitmap for BMP, TJpegImage for JPEG, TPngImage for PNG, TWicImage for TIFF), there’s just TBitmap which can load from – and save to – a range of formats. Another difference with the VCL is that there’s no FMX.Graphics unit – instead, both TBitmap and TCanvas are declared in FMX.Types.

OS X

The first task is to identify the native clipboard API. On OS X, this means working with the NSPasteboard object for the ‘general pasteboard’ (OS X actually defines more than one system clipboard, but the general one is the one we want). On my admittedly brief inspection, the NSPasteboard class is conceptually pretty similar to the IDataObject interface on Windows – it’s a bit higher level, but not much, and there’s an intertwining of clipboard and drag and drop functionality common to both.

We can get a reference to the general pasteboard like this:

uses
  Macapi.ObjectiveC, Macapi.CoreFoundation, Macapi.CocoaTypes, Macapi.Foundation, Macapi.AppKit;

function GeneralPasteboard: NSPasteboard; inline;
begin
  Result := TNSPasteboard.Wrap(TNSPasteboard.OCClass.generalPasteboard);
end;

To poll for what’s available to paste, call the availableTypeFromArray method. This is passed an NSArray of NSString objects containing the system-defined identifiers for the ‘representation types’ you are interested in – if none is matched, nil is returned, otherwise the first match is.

While you don’t have to break out the needed code into a series of helper functions, I think it makes things clearer if you do. So, first we’ll have a function to construct an NSArray from an arbitrary group of NSObject instances:

function NSObjectArray(const Objs: array of NSObject): NSArray;
var
  Counter: Integer;
  Obj: NSObject;
  Ptrs: array of Pointer;
begin
  SetLength(Ptrs, Length(Objs));
  Counter := 0;
  for Obj in Objs do
    if Obj <> nil then
    begin
      Ptrs[Counter] := (Obj as ILocalObject).GetObjectID;
      Inc(Counter);
    end;
  Result := TNSArray.Wrap(TNSArray.OCClass.arrayWithObjects(Ptrs, Counter));
end;

One of docs I read mentioned the illegality of nil elements, which is why I check for them here.

Using NSObjectArray, we can easily write a function to return an array of the standard bitmap representation types:

function NSBitmapTypesArray: NSArray; inline;
begin
  Result := NSObjectArray([NSPasteboardTypePNG, NSPasteboardTypeTIFF]);
end;

Initially I was just specifying NSPasteboardTypePNG, but then found the Grab utility bundled with Snow Leopard writing only TIFF images to the clipboard general pasteboard. Since TBitmap can read both formats, we should therefore check for both.

Finally, we can write CanPasteBitmapFromClipboard as another one-liner:

function CanPasteBitmapFromClipboard: Boolean;
begin
  Result := (GeneralPasteboard.availableTypeFromArray(NSBitmapTypesArray) <> nil);
end;

For actual pasting, we can do this:

type
  TUserMemoryStream = class(TCustomMemoryStream)
    constructor Create(AMemory: Pointer; const ALength: NativeInt);
    function Write(const Buffer; Count: Longint): Longint; override;
  end;

constructor TUserMemoryStream.Create(AMemory: Pointer; const ALength: NativeInt);
begin
  inherited Create;
  SetPointer(AMemory, ALength);
end;

function TUserMemoryStream.Write(const Buffer; Count: Longint): Longint;
begin
  Result := 0;
end;

function PasteBitmapFromClipboard(Bitmap: TBitmap): Boolean;
var
  Data: NSData;
  DataType: NSString;
  Pasteboard: NSPasteboard;
  Stream: TCustomMemoryStream;
begin
  Pasteboard := GeneralPasteboard;
  DataType := Pasteboard.availableTypeFromArray(NSBitmapTypesArray);
  if DataType <> nil then Data := Pasteboard.dataForType(DataType);
  if Data = nil then Exit(False);
  Stream := TUserMemoryStream.Create(Data.bytes, Data.length);
  try
    Bitmap.Clear(0); //needed due to FMX bug - try pasting the same PNG multiple times
    Bitmap.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
  Result := True;
end;

Instead of defining and using TUserMemoryStream, you could use a normal TMemoryStream and copy the data into it. I prefer to avoid needless copying of data if I can however.

For going the other way – i.e., assigning a bitmap to the general pasteboard – I’ve come up with the following:

procedure CopyBitmapToClipboard(Bitmap: TBitmap);
var
  Data: CFDataRef;
  Filter: TBitmapCodec;
  Pasteboard: NSPasteboard;
  Stream: TMemoryStream;
begin
  Data := nil;
  Stream := nil;
  Filter := DefaultBitmapCodecClass.Create;
  try
    Stream := TMemoryStream.Create;
    Filter.SaveToStream(Stream, Bitmap, 'png');
    Pasteboard := GeneralPasteboard;
    Pasteboard.declareTypes(NSObjectArray([NSPasteboardTypePNG]), nil);
    Data := CFDataCreateWithBytesNoCopy(nil, Stream.Memory, Stream.Size, kCFAllocatorNull);
    Pasteboard.setData(TNSData.Wrap(Data), NSPasteboardTypePNG);
  finally
    if Data <> nil then CFRelease(Data);
    Filter.Free;
    Stream.Free;
  end;
end;

Once more, I avoid data copying for data copying’s sake, this time by using the CFDataCreateWithBytesNoCopy function to construct a CFDataRef/NSData object that uses a ‘backing store’ provided on the Delphi side (like CFStringRef and NSString, CFDataRef and NSData are ‘toll-free bridged’, i.e. binary compatible).

The bit before the CFDataCreateWithBytesNoCopy call may need some explaining though: while TBitmap has its own SaveToStream method, using our own TBitmapCodec instance explicitly means we can guarantee a PNG image will be outputted – while the present implementation of TBitmap.SaveToStream will output a PNG image if an alpha channel is defined, it will output a JPEG if one isn’t. As an aside, the JPEG format is a poor default in my view given it is a ‘lossy’ one, but that’s the way it is. [Update 16/12/11: this was fixed in update 3 so that TBitmap.SaveToStream always outputs a PNG image.]

Windows

On Windows, implementing CanPasteBitmapFromClipboard is trivial:

uses
  Winapi.Windows;

function CanPasteBitmapFromClipboard: Boolean;
begin
  Result := IsClipboardFormatAvailable(CF_DIB);
end;

While we could check for other formats, that shouldn’t be necessary given DIB is the native display format, and so, essentially universal in a Windows context. In fact, it is so universal, we can assume it will be the pixel format used by TBitmap, avoiding the need to stream out:

procedure CopyBitmapToClipboard(Bitmap: TBitmap);
var
  BitsSize: Integer;
  MemObj: HGLOBAL;
  Ptr: PBitmapInfoHeader;
begin
  BitsSize := Bitmap.Width * Bitmap.Height * 4;
  MemObj := GlobalAlloc(GMEM_MOVEABLE and GMEM_DDESHARE, SizeOf(TBitmapInfoHeader) + BitsSize);
  if MemObj = 0 then RaiseLastOSError;
  Ptr := GlobalLock(MemObj);
  if Ptr = nil then
  begin
    GlobalFree(MemObj);
    RaiseLastOSError;
  end;
  //fill out the info header
  FillChar(Ptr^, SizeOf(Ptr^), 0);
  Ptr.biSize := SizeOf(TBitmapInfoHeader);
  Ptr.biPlanes := 1;
  Ptr.biBitCount := 32;
  Ptr.biCompression := BI_RGB;
  Ptr.biWidth := Bitmap.Width;
  if Ptr.biWidth <= 0 then Ptr.biWidth := 1;
  Ptr.biHeight := -Bitmap.Height;
  if Ptr.biHeight >= 0 then Ptr.biHeight := -1;
  //copy over the image bits
  Inc(Ptr);
  if BitsSize <> 0 then
    Move(Bitmap.StartLine^, Ptr^, BitsSize);
  GlobalUnlock(MemObj);
  //assign the completed DIB memory object to the clipboard
  OpenClipboard(0);
  try
    EmptyClipboard;
    if not SetClipboardData(CF_DIB, MemObj) then
    begin
      GlobalFree(MemObj);
      RaiseLastOSError;
    end;
  finally
    CloseClipboard;
  end;
end;

For PasteBitmapFromClipboard though, we do need to stream in since the source may not be a 32 bit colour DIB:

function PasteBitmapFromClipboard(Bitmap: TBitmap): Boolean;
var
  Header: TBitmapFileHeader;
  MemObj: HGLOBAL;
  Ptr: PBitmapInfoHeader;
  Stream: TMemoryStream;
begin
  Ptr := nil;
  Stream := nil;
  OpenClipboard(0);
  try
    MemObj := GetClipboardData(CF_DIB);
    if MemObj = 0 then Exit(False);
    Ptr := GlobalLock(MemObj);
    if Ptr = nil then Exit(False);
    FillChar(Header, SizeOf(Header), 0);
    Header.bfType := $4D42;
    Header.bfSize := SizeOf(Header) + GlobalSize(MemObj);
    Header.bfOffBits := SizeOf(Header) + Ptr.biSize;
    Stream := TMemoryStream.Create;
    Stream.WriteBuffer(Header, SizeOf(Header));
    Stream.WriteBuffer(Ptr^, Header.bfSize - SizeOf(Header));
    Stream.Position := 0;
    Bitmap.LoadFromStream(Stream);
    Result := True;
  finally
    if Ptr <> nil then GlobalUnlock(MemObj);
    CloseClipboard;
    Stream.Free;
  end;
end;

So, there you have it: code to read and write a FireMonkey TBitmap from and to the system clipboard on both OS X and Windows. You can get the complete unit and a small demo project from here. (Update: check out my post here for a FMX TClipboard implementation I’ve made available.)

PS – while I have few complaints about the FMX TBitmap and TCanvas interfaces in general, perhaps inevitably, there was one WTF moment when I was checking them out – the signature for TBitmapCodec.LoadFromStream looks like this:

function LoadFromStream(const AStream: TStream; var Bitmap: TBitmap): Boolean; virtual; abstract;

Was the person who wrote this not quite clear about what const and var actually mean when used on parameters typed to a class, or indeed, any pure reference type…?

35 thoughts on “Copying and pasting the contents of a FireMonkey TBitmap

  1. Thank you so much for this blog! Without it, my first FireMonkey project would have come to a screaching halt.
    There is one typo error in the Windows CopyBitmapToClipboard procedure….I found
    “if (SetClipboardData(CF_DIB, MemObj)0) then begin”
    seems to work. Also I had to declare the function arguments as (Bitmap:FMX.Types.TBitmap)
    because otherwise a type tagBitmap was assumed.

    Thanks again, David

  2. Pingback: Programmatically taking a screenshot on OS X « Delphi Haven

  3. Awesome post, but I am getting a weird result in OSX (Lion not sure if this happens on Snow Leopard).

    If I copy an image in the filesystem, I actually get an icon pasted into the bitmap, not the actual data of the image copied, the icon is the generic JPEG, or PNG icon for OSX.
    I am doing the test of CanPasteBitmapFromClipboard before trying to paste the contents to a Bitmap.

    Any ideas?

    • ‘Any ideas?’

      Well, I wouldn’t have expected the file *contents*. However, I see TextEdit (though not Word) handles this, so… watch this space – I’ve come up with the code, but I’ll make another blog post out of it.

      • Cool, can’t wait!

        I did manage to get the same sort of functionality working with Windows, had to handle CF_HDROP and read the file name using DragQueryFile and then load it depending on the file extension.

      • ‘had to handle CF_HDROP and read the file name using DragQueryFile…’

        It’s as if you have been reading my mind… In terms of concept, a similar thing needs to be done for OS X, though the specifics are quite different obviously.

        • this is that I have for windows…

          class function TClipboard.PasteBitmapFromClipboard(Bitmap: TBitmap): Boolean;
          var
          Header : TBitmapFileHeader;
          MemObj : HGLOBAL;
          Ptr : PBitmapInfoHeader;
          Stream : TMemoryStream;
          f : THandle;
          numFiles : Integer;
          FFile : string;
          buffer : array [0..MAX_PATH] of Char;
          begin
          Ptr := nil;
          Stream := nil;

          if Clipboard.HasFormat(CF_HDROP) then
          begin
          Clipboard.Open;
          try
          f := Clipboard.GetAsHandle(CF_HDROP);
          if f 0 then
          begin
          numFiles := DragQueryFile(f, $FFFFFFFF, nil, 0);
          if numFiles = 1 then
          begin
          buffer[0] := #0;
          DragQueryFile(f, 0, buffer, SizeOf(buffer));
          FFile := buffer;
          if ExtractFileExt(FFile) = ‘.jpg’ then
          Bitmap.LoadFromFile(FFile);
          end;
          end;
          finally
          Clipboard.Close;
          end;
          end
          else
          begin
          OpenClipboard(0);
          try
          MemObj := GetClipboardData(CF_DIB);
          if MemObj = 0 then Exit(False);
          Ptr := GlobalLock(MemObj);
          if Ptr = nil then Exit(False);
          FillChar(Header, SizeOf(Header), 0);
          Header.bfType := $4D42;
          Header.bfSize := SizeOf(Header) + GlobalSize(MemObj);
          Header.bfOffBits := SizeOf(Header) + Ptr.biSize;
          Stream := TMemoryStream.Create;
          Stream.WriteBuffer(Header, SizeOf(Header));
          Stream.WriteBuffer(Ptr^, Header.bfSize – SizeOf(Header));
          Stream.Position := 0;
          Bitmap.LoadFromStream(Stream);
          Result := True;
          finally
          if Ptr nil then GlobalUnlock(MemObj);
          CloseClipboard;
          Stream.Free;
          end;
          end;
          end;

          • ‘this is that I have for windows…’

            Thanks. See my new post for my version, which also handles the case of getting the icon for a file that isn’t an image file.

  4. Hello.
    It is possible, with this code, to read, from clipboard, binary data that not belongs to standard type (Text, Bitmap, TIFF…). While in WIN it is possible to Register format, in OSX how can I retrieve TClipboardFormat for nonstandard type?

      • Thank you. But i don’t see nothing else standard format cfBitmap and cfText. I have binary data in clipboard (reads in external application with Command+C). In my application I have to read data in stream and to convert them in ByteArray. But any CCR function requires a TClipboardFormat that I ignore (external applicatiion is missing of documentation)…

        • If it’s an external application, then you need to find out the pasteboard identifier being used (it will be a simple string value). Then, get a TClipboardFormat value for it by calling TClipboard.RegisterFormat(PasteboardID).

  5. I just got the latest from the SVN and under XE4 Update 1, on Window the bitmap is getting corrupted. I replaced the code with the following and it seems to work now

    procedure TWinClipboard.DoAssignBitmap(ABitmap: TBitmap);
    var
      MemObj: HGLOBAL;
      Ptr: PBitmapInfoHeader;
      WinBitmap:HBITMAP;
    
      BitmapInfo: TBitmapInfo;
      BitmapBits: Pointer;
      M: TBitmapData;
      I: Integer;
    begin
      FillChar(BitmapInfo, SizeOf(BitmapInfo), 0);
      BitmapInfo.bmiHeader.biSize := SizeOf(TBitmapInfoHeader);
      BitmapInfo.bmiHeader.biPlanes := 1;
      BitmapInfo.bmiHeader.biBitCount := 32;
      BitmapInfo.bmiHeader.biCompression := BI_RGB;
      BitmapInfo.bmiHeader.biWidth := ABitmap.Width;
      if BitmapInfo.bmiHeader.biWidth = 0 then
        BitmapInfo.bmiHeader.biHeight := -1;
      WinBitmap := CreateDIBSection(0, BitmapInfo, DIB_RGB_COLORS, Pointer(BitmapBits), 0, 0);
      if BitmapBits  nil then
      begin
        if ABitmap.Map(TMapAccess.maRead, M) then
        try
          for I := 0 to ABitmap.Height - 1 do
            Move(PAlphaColorArray(M.Data)[I * (M.Pitch div 4)],
                 PAlphaColorArray(BitmapBits)[I * ABitmap.Width],
                 ABitmap.Width * 4);
        finally
          ABitmap.Unmap(M);
        end;
      end;
    
      MemObj := GlobalAlloc(GMEM_MOVEABLE and GMEM_DDESHARE,BitmapInfo.bmiHeader.biSize+
                            (ABitmap.Width*ABitmap.Height*4));
    
      if MemObj = 0 then
         RaiseLastOSError;
      Ptr := GlobalLock(MemObj);
      if Ptr = nil then
      begin
        GlobalFree(MemObj);
        RaiseLastOSError;
      end;
      MOVE(BitmapInfo.bmiHeader,Ptr^,BitmapInfo.bmiHeader.biSize);
      Inc(Ptr);
      Move(BitmapBits^,Ptr^,ABitmap.Width*ABitmap.Height*4);
      GlobalUnlock(MemObj);
      //assign the completed DIB memory object to the clipboard
      if SetClipboardData(CF_DIB, MemObj) = 0 then
      begin
        GlobalFree(MemObj);
        RaiseLastOSError;
      end;
      if WinBitmap  0 then
         DeleteObject(WinBitmap);
    end;
    
      • It was literally just running/compiling the demo on XE4 update 1 on win7 64,  Without changing anything, I pressed the copy button and then the paste button.  The picture came in with what looked like all the scan lines offset so that it smeared in diagonal lines.  

        When I tried to paste it into Corel Photo it crashed Corel Photo.  And when I tried the binary option it crashed the demo.  Baffling.

        • Looking at it, I think the cause might be my code’s overly simple assumptions regarding pitch. Try the revised version in the SVN trunk…

  6. Oh, I also had to modified CCR.FMXClipboard and add in the usage clause {$IF FireMonkeyVersion > 17}, FMX.Surfaces {$ENDIF} and change the code of TryLoadBitmapFromFile to make it compatible with XE4 Update1:

    function TryLoadBitmapFromFile(Bitmap: TBitmap; const FileName: string): Boolean;
    {$IF FireMonkeyVersion > 17}
        var
          Surf: TBitmapSurface;
        begin
           Surf := TBitmapSurface.Create;
           try
             Surf.Assign(Bitmap);
             Result := TBitmapCodecManager.LoadFromFile(FileName, Surf);
           finally
             Surf.Free;
           end;
        end;
      {$ELSE}
       begin
          Result := TBitmapCodecManager.LoadFromFile(FileName, Bitmap);
       end;
      {$ENDIF}
    {$ENDIF}

    • Thanks. It’s all part of the FireMonkey fun – every single version (and some of the updates) breaks code that does something even mildly non-trivial.

  7. Hey, I got it to format the code right by replacing all the spaces with hard spaces (ALT+0160 on the numeric keypad…!)

    • Even better, you can use sourcecode tags 😉 [http://en.support.wordpress.com/code/posting-source-code/]

  8. BTW, half the time I try to reply it refuses to post the comment. For example, on your tips post I tried to post a helper function for the TCanvas object that reintroduces the VCL arc() drawing procedure–which takes wildly different parameters than FMX’s drawarc() equivalent–but WordPress said it couldn’t post the comment.  

    I have found your site so useful that I’ve wanted to share a number of solutions I’ve come up with and I’m wondering the best way of doing it without starting my own blog.  Could I just send them to you directly?

  9. Strange, it appears to have lost several lines during the posting — or your conversion to formatted code. Here it is again:

    procedure TWinClipboard.DoAssignBitmap(ABitmap: TBitmap);
    var
    MemObj: HGLOBAL;
    Ptr: PBitmapInfoHeader;
    WinBitmap:HBITMAP;

    BitmapInfo: TBitmapInfo;
    BitmapBits: Pointer;
    M: TBitmapData;
    I: Integer;
    begin
    FillChar(BitmapInfo, SizeOf(BitmapInfo), 0);
    BitmapInfo.bmiHeader.biSize := SizeOf(TBitmapInfoHeader);
    BitmapInfo.bmiHeader.biPlanes := 1;
    BitmapInfo.bmiHeader.biBitCount := 32;
    BitmapInfo.bmiHeader.biCompression := BI_RGB;
    BitmapInfo.bmiHeader.biWidth := ABitmap.Width;
    if BitmapInfo.bmiHeader.biWidth = 0 then
    BitmapInfo.bmiHeader.biHeight := -1;
    WinBitmap := CreateDIBSection(0, BitmapInfo, DIB_RGB_COLORS, Pointer(BitmapBits), 0, 0);
    if BitmapBits nil then
    begin
    if ABitmap.Map(TMapAccess.maRead, M) then
    try
    for I := 0 to ABitmap.Height – 1 do
    Move(PAlphaColorArray(M.Data)[I * (M.Pitch div 4)],
    PAlphaColorArray(BitmapBits)[I * ABitmap.Width],
    ABitmap.Width * 4);
    finally
    ABitmap.Unmap(M);
    end;
    end;

    MemObj := GlobalAlloc(GMEM_MOVEABLE and GMEM_DDESHARE,BitmapInfo.bmiHeader.biSize+
    (ABitmap.Width*ABitmap.Height*4));

    if MemObj = 0 then
    RaiseLastOSError;
    Ptr := GlobalLock(MemObj);
    if Ptr = nil then
    begin
    GlobalFree(MemObj);
    RaiseLastOSError;
    end;
    MOVE(BitmapInfo.bmiHeader,Ptr^,BitmapInfo.bmiHeader.biSize);
    Inc(Ptr);
    Move(BitmapBits^,Ptr^,ABitmap.Width*ABitmap.Height*4);
    GlobalUnlock(MemObj);
    //assign the completed DIB memory object to the clipboard
    if SetClipboardData(CF_DIB, MemObj) = 0 then
    begin
    GlobalFree(MemObj);
    RaiseLastOSError;
    end;
    if WinBitmap 0 then
    DeleteObject(WinBitmap);
    end;
    [\code]

  10. What the???? It lost several lines again! One more time…

    procedure TWinClipboard.DoAssignBitmap(ABitmap: TBitmap);
    var
      MemObj: HGLOBAL;
      Ptr: PBitmapInfoHeader;
      WinBitmap:HBITMAP;
    
      BitmapInfo: TBitmapInfo;
      BitmapBits: Pointer;
      M: TBitmapData;
      I: Integer;
    begin
      FillChar(BitmapInfo, SizeOf(BitmapInfo), 0);
      BitmapInfo.bmiHeader.biSize := SizeOf(TBitmapInfoHeader);
      BitmapInfo.bmiHeader.biPlanes := 1;
      BitmapInfo.bmiHeader.biBitCount := 32;
      BitmapInfo.bmiHeader.biCompression := BI_RGB;
      BitmapInfo.bmiHeader.biWidth := ABitmap.Width;
      if BitmapInfo.bmiHeader.biWidth <= 0 then
         BitmapInfo.bmiHeader.biWidth := 1;
      BitmapInfo.bmiHeader.biHeight := -ABitmap.Height;
      if BitmapInfo.bmiHeader.biHeight >= 0 then
        BitmapInfo.bmiHeader.biHeight := -1;
      WinBitmap := CreateDIBSection(0, BitmapInfo, DIB_RGB_COLORS, Pointer(BitmapBits), 0, 0);
      if BitmapBits <> nil then
      begin
        if ABitmap.Map(TMapAccess.maRead, M) then
        try
          for I := 0 to ABitmap.Height - 1 do
            Move(PAlphaColorArray(M.Data)[I * (M.Pitch div 4)],
                 PAlphaColorArray(BitmapBits)[I * ABitmap.Width],
                 ABitmap.Width * 4);
        finally
          ABitmap.Unmap(M);
        end;
      end;
    
      MemObj := GlobalAlloc(GMEM_MOVEABLE and GMEM_DDESHARE,BitmapInfo.bmiHeader.biSize+
                            (ABitmap.Width*ABitmap.Height*4));
    
      if MemObj = 0 then
         RaiseLastOSError;
      Ptr := GlobalLock(MemObj);
      if Ptr = nil then
      begin
        GlobalFree(MemObj);
        RaiseLastOSError;
      end;
      MOVE(BitmapInfo.bmiHeader,Ptr^,BitmapInfo.bmiHeader.biSize);
      Inc(Ptr);
      Move(BitmapBits^,Ptr^,ABitmap.Width*ABitmap.Height*4);
      GlobalUnlock(MemObj);
      //assign the completed DIB memory object to the clipboard
      if SetClipboardData(CF_DIB, MemObj) = 0 then
      begin
        GlobalFree(MemObj);
        RaiseLastOSError;
      end;
      if WinBitmap <> 0 then
         DeleteObject(WinBitmap);
    end;
    
  11. Okay FINALLY lines 18-20 came in. Damned if I know what happened; I loaded the code into a binary text editor and the line endings were all good so I don’t know why it discarded those three lines!! Bloody odd, if you ask me.

  12. I tested your revised clipboard code and it worked like a charm. BTW, feel free to remove the post item above with the missing lines…!

  13. With MAC version of your great solution, if in Clipboard is present a PICT image (PDF Format in OSX), it is possible to load it in TBitmap? Is PasteBitmapFromClipboard adaptable to this goal?

    • Firstly, use the TClipboard version of the code instead (I’ll be maintaining that for new versions of Delphi). Secondly, my understanding is that when an old application puts just a PICT on the clipboard, OS X will add a PNG or PDF version automatically, similar to Windows ensuring a DIB is available when a DDB is put on the clipboard. As such, my code should still work.

      • Thank ypu, Chris.
        With “HasFormat” method, I see that PDF image is in Clipboard.
        So, I have modified your code, with add PDF format.


        function NSBitmapTypesArray: NSArray; inline;
        begin
        Result := NSObjectArray([NSPasteboardTypePNG, NSPasteboardTypeTIFF, NSPasteboardTypePDF]);
        end;

        But with PasteBitmapFromClipboard(MyImage.Bitmap), image not appears.
        Maybe it is a wrong method to proceed….

          • I ask you if if some demo or other code segment that you have produced, exists example of NSImage set option.
            With your new code, PDF image load prefectly in Bitmap object, but, if I want to scaling it (stetching, for example) or set background color, none tutorial or help FM exists on subject…
            Thank you agian, Chris.

          • Short answer, no. If you want to manipulate a PDF ‘graphic’ like that, I would call relevant OS X API functions directly, and only get a FMX TBitmap for the final output, if at all.

Leave a reply to kenjschafer Cancel reply