unit DragDropBitmap;
{
  Description
  ===========
    TDragDropBitmap is a component for simple OLE drag-and-drop operations
    with bitmaps. The component is a child-class from TDragDrop.


  Disclaimer
  ==========
    The author disclaims all warranties, expressed or implied, including,
    without limitation, the warranties of merchantability and of fitness
    for any purpose. The author assumes no liability for damages, direct or
    consequential, which may result from the use of this component/unit.


  Restrictions on Using the Unit / Component
  ==========================================
    This unit/component is copyright 1998 by Dieter Steinwedel. ALL RIGHTS
    ARE RESERVED BY DIETER STEINWEDEL. You are allowed to use it freely
    subject to the following restrictions:

    • You are not allowed delete or alter the author's name and
      copyright in any manner

    • You are not allowed to publish a copy, modified version or
      compilation neither for payment in any kind nor freely

    • You are allowed to create a link to the download in the WWW

    • These restrictions and terms apply to you as long as until
      I alter them. Changes can found on my homepage


  Contact
  =======
    homepage: http://godard.oec.uni-osnabrueck.de/student_home/dsteinwe/delphi/DietersDelphiSite.htm
}

{$ALIGN ON}
{$ASSERTIONS OFF}
{$BOOLEVAL OFF}
{$DENYPACKAGEUNIT OFF}
{$EXTENDEDSYNTAX ON}
{$HINTS ON}
{$IMPORTEDDATA ON}
{$LONGSTRINGS ON}
{$OPTIMIZATION ON}
{$TYPEDADDRESS OFF}
{$TYPEINFO OFF}
{$WARNINGS ON}

interface

uses DragDrop, Windows, Classes, SysUtils, ActiveX, Graphics, Controls, Forms;

type
  TDataObjectBitmap = class(TDataObject)
  private
     DIBStream:TMemoryStream;
  public
     constructor Create(const Bitmap: TBitmap);
     destructor Destroy; override;
     function RenderData(FormatEtc:TFormatEtc;
        var StgMedium: TStgMedium):HResult; override;
  end;

  TDropTargetBitmap = class(TDropTarget)
  protected
     procedure AcceptDataObject(DataObj: IDataObject; var Accept:boolean); override;
  public
     constructor Create(AOwner: TDragDrop);
     destructor Destroy; override;
     procedure RenderDropped(DataObj: IDataObject; grfKeyState: Longint;
        pt: TPoint; var dwEffect: longint); override;
  end;

  TDragDropBitmap = class(TDragDrop)
  private
     FBitmap:TBitmap;
     procedure SetBitmap(Bitmap:TBitmap);
  protected
     function CreateDataObject:TDataObject; override;
  public
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
     property Bitmap: TBitmap read FBitmap write SetBitmap;
  end;

procedure Register;

implementation

// some local functions --------------------------------------------------------

{procedure CopyAsBitmap(Bitmap:TBitmap; DataPtr: PChar; DataSize:longint);
var  MemoryStream: TMemoryStream;
     BMF: TBitmapFileheader;
begin
     ZeroMemory(@BMF, sizeof (TBitmapFileheader));
     BMF.bfType:=$4D42;
     MemoryStream:=TMemoryStream.Create;
     try
        MemoryStream.Write(BMF, sizeof (BMF));
        MemoryStream.Write(DataPtr^, DataSize);
        MemoryStream.Seek(0,0);
        Bitmap.LoadFromStream(MemoryStream);
     finally
        MemoryStream.Free;
     end;
end;}

procedure CopyAsBitmap(Bitmap:TBitmap; DataPtr: PChar; DataSize:longint);
var BitmapInfoHeader: TBitmapInfoHeader;
    BitmapInfo: PBitmapInfo;
    Size: Word;
    Pal: HPALETTE;
    BitsMem: Pointer;
    Focus: HWND;
    DC: HDC;
    OldPal: HPALETTE;
    ImagePtr:pchar;
    ImageSize:DWord;

    function GetDInColors(BitCount: Word): Integer;
    begin
         case BitCount of
              1, 4, 8: Result := 1 shl BitCount;
              else Result := 0;
         end;
    end;

    function PaletteFromW3DIB(const BI: TBitmapInfo): HPALETTE;
    var DstPal: PLogPalette;
        Colors, n: Integer;
        Size: Longint;
        DC: HDC;
        Focus: HWND;
        SysPalSize: Integer;
        I: Integer;
    begin
         Result := 0;
         { If the ClrUsed field of the header is non-zero, it means that we could
           have a short color table }
         with BI.bmiHeader do
              if biClrUsed <> 0 then Colors := biClrUsed
              else Colors := GetDInColors(biBitCount);
         if Colors <= 2 then Exit;
         Size := SizeOf(TLogPalette) + ((Colors - 1) * SizeOf(TPaletteEntry));
         DstPal := AllocMem(Size);
         try
            FillChar(DstPal^, Size, 0);
            with DstPal^ do
            begin
                 palNumEntries := Colors;
                 palVersion := $300;
                 Focus := GetFocus;
                 DC := GetDC(Focus);
                 try
                    SysPalSize := GetDeviceCaps(DC, SIZEPALETTE);
                    if (Colors = 16) and (SysPalSize >= 16) then
                    begin
                         { Ignore the disk image of the palette for 16 color bitmaps use
                           instead the first 8 and last 8 of the current system palette }
                         GetSystemPaletteEntries(DC, 0, 8, palPalEntry);
                         I := 8;
                         GetSystemPaletteEntries(DC, SysPalSize - I, I, palPalEntry[I]);
                    end
                    else
                        { Copy the palette for all others (i.e. 256 colors) }
                        for N := 0 to Colors - 1 do
                        begin
                             palPalEntry[N].peRed := BI.bmiColors[N].rgbRed;
                             palPalEntry[N].peGreen := BI.bmiColors[N].rgbGreen;
                             palPalEntry[N].peBlue := BI.bmiColors[N].rgbBlue;
                             palPalEntry[N].peFlags := 0;
                        end;
                 finally
                    ReleaseDC(Focus, DC);
                 end;
            end;
         Result := CreatePalette(DstPal^);
         finally
            FreeMem(DstPal, Size);
         end;
    end;

begin
     ImagePtr:=DataPtr;
     ImageSize:=DataSize;
     CopyMemory(@BitmapInfoHeader,ImagePtr,SizeOf(TBitmapInfoHeader));
     ImagePtr:=ImagePtr+SizeOf(TBitmapInfoHeader);
     with BitmapInfoHeader do
     begin
          if biClrUsed = 0 then
             biClrUsed := GetDInColors(biBitCount);
          Size := biClrUsed * SizeOf(TRgbQuad);
     end;
     BitmapInfo := AllocMem(Size + SizeOf(TBitmapInfoHeader));
     try
        with BitmapInfo^ do
        begin
             bmiHeader := BitmapInfoHeader;
             CopyMemory(@bmiColors, ImagePtr,Size);
             ImagePtr:=ImagePtr+Size;
             { now we've got the color table. Create a palette from it }
             Pal := PaletteFromW3DIB(BitmapInfo^);
             { some applications do not fill in the SizeImage field in the header.
               (Actually the truth is more likely that some drivers do not fill the field
               in and the apps do not compensate for these buggy drivers.) Therefore, if
               this field is 0, we will compute the size. }
             with bmiHeader do
             begin
                  Dec(ImageSize, SizeOf(TBitmapInfoHeader) + Size);
                  if biSizeImage <> 0 then
                     if biSizeImage < ImageSize then ImageSize := biSizeImage;
                  BitsMem := AllocMem(ImageSize);
                  try
                     CopyMemory(BitsMem, ImagePtr,ImageSize);
                     { we use the handle of the window with the focus (which, if this routine
                       is called from a menu command, will be this window) in order to guarantee
                       that the realized palette will have first priority on the system palette }
                     Focus := GetFocus;
                     DC := GetDC(Focus);
                     if DC <>0 then
                     try
                        if Pal <> 0 then
                        begin
                             { select and realize our palette we have gotten the DC of the focus
                             window just to make sure that all our colors are mapped }
                             OldPal := SelectPalette(DC, Pal, False);
                             RealizePalette(DC);
                        end
                        else OldPal := 0;
                        try
                           Bitmap.Handle:=CreateDIBitmap(DC, BitmapInfo^.bmiHeader,  CBM_INIT, BitsMem,
                           BitmapInfo^, DIB_RGB_COLORS);
                           finally
                           if OldPal <> 0 then
                              SelectPalette(DC, OldPal, False);
                        end;
                     finally
                        ReleaseDC(Focus, DC);
                     end;
                  finally
                     FreeMem(BitsMem, ImageSize);
                  end;
             end;
        end;
     finally
       FreeMem(BitmapInfo, Size + SizeOf(TBitmapInfoHeader));
     end;
end;

function WidthBytes(I: Longint): Longint;
begin
     Result := ((I + 31) div 32) * 4;
end;

procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP; var BI: TBitmapInfoHeader;
  Colors: Integer);
var BM: Windows.TBitmap;
begin
     GetObject(Bitmap, SizeOf(BM), @BM);
     with BI do
     begin
          biSize := SizeOf(BI);
          biWidth := BM.bmWidth;
          biHeight := BM.bmHeight;
          if Colors <> 0 then
             case Colors of
                  2: biBitCount := 1;
                  16: biBitCount := 4;
                  256: biBitCount := 8;
             end
             else biBitCount := BM.bmBitsPixel * BM.bmPlanes;
          biPlanes := 1;
          biXPelsPerMeter := 0;
          biYPelsPerMeter := 0;
          biClrUsed := 0;
          biClrImportant := 0;
          biCompression := BI_RGB;
          if biBitCount in [16, 32] then biBitCount := 24;
          biSizeImage := WidthBytes(biWidth * biBitCount) * biHeight;
     end;
end;

procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer;
  var ImageSize: DWORD; Colors: Integer);
var BI: TBitmapInfoHeader;
begin
     InitializeBitmapInfoHeader(Bitmap, BI, Colors);
     with BI do
     begin
          if biBitCount=24 then InfoHeaderSize := SizeOf(TBitmapInfoHeader)
          else InfoHeaderSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) *
             (1 shl biBitCount);
     end;
     ImageSize := BI.biSizeImage;
end;

function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE;
  var BitmapInfo; var Bits; Colors: Integer): Boolean;
var OldPal: HPALETTE;
    Focus: HWND;
    DC: HDC;
begin
     InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(BitmapInfo), Colors);
     OldPal := 0;
     Focus := GetFocus;
     DC := GetDC(Focus);
     try
        if Palette <> 0 then
        begin
             OldPal := SelectPalette(DC, Palette, False);
             RealizePalette(DC);
        end;
        Result := GetDIBits(DC, Bitmap, 0, TBitmapInfoHeader(BitmapInfo).biHeight,
           @Bits, TBitmapInfo(BitmapInfo), DIB_RGB_COLORS) <> 0;
     finally
        if OldPal <> 0 then SelectPalette(DC, OldPal, False);
        ReleaseDC(Focus, DC);
     end;
end;

procedure DIBFromBit(Stream: TMemoryStream; Src: HBITMAP;
  Pal: HPALETTE; Colors: Integer; var DIBHeader, DIBBits: Pointer);
var HeaderSize: Integer;
    ImageSize: DWORD;
begin
     if Src=0 then exit;
     InternalGetDIBSizes(Src, HeaderSize, ImageSize, Colors);
     Stream.SetSize(HeaderSize + integer(ImageSize));
     DIBHeader:=Stream.Memory;
     DIBBits:=Pointer(Longint(DIBHeader) + HeaderSize);
     InternalGetDIB(Src, Pal, DIBHeader^, DIBBits^, Colors);
end;

// TDataObjectBitmap -----------------------------------------------------------

constructor TDataObjectBitmap.Create(const Bitmap: TBitmap);
var FE:TFormatEtc;
    SM:TStgMedium;
    DIBHeader, DIBBits: Pointer;
begin
     inherited Create;
     with FE do
     begin
          cfFormat:=CF_DIB;
          ptd:=nil;
          dwAspect:=DVASPECT_CONTENT;
          lindex:=-1;
          tymed:=TYMED_HGLOBAL;
     end;
     SetData(FE,SM,false);
     DIBStream:=TMemoryStream.Create;
     DIBFromBit(DIBStream, Bitmap.Handle, Bitmap.Palette,0,DIBHeader, DIBBits);
     // Don't release DIBHeader and DIBBits; both points to a position in the stream
end;

destructor TDataObjectBitmap.Destroy;
begin
     DIBStream.free;
     inherited Destroy;
end;

function TDataObjectBitmap.RenderData(FormatEtc:TFormatEtc;
   var StgMedium: TStgMedium):HResult;
var h: HGlobal;
    p:pointer;
begin
     Result:=E_Fail;
     if FormatEtc.cfFormat=cf_DIB then
     begin
          h:=GlobalAlloc(GHND or GMEM_SHARE, DIBStream.Size);
          if h=0 then
          begin
               Result:=E_OUTOFMEMORY;
               exit;
          end;
          p:=globallock(h);
          DIBStream.Seek(0,0);
          DIBStream.Read(p^,DIBStream.Size);
          globalunlock(h);
          with StgMedium do
          begin
               tymed:=TYMED_HGLOBAL;
               hGlobal := h;
               unkForRelease := nil;
          end;
          Result:=S_OK;
     end;
end;

// TDropTargetBitmap -----------------------------------------------------------

constructor TDropTargetBitmap.Create(AOwner: TDragDrop);
begin
     inherited Create(AOwner);
end;

destructor TDropTargetBitmap.Destroy;
begin
     inherited Destroy;
end;

procedure TDropTargetBitmap.AcceptDataObject(DataObj: IDataObject; var Accept:boolean);
var  FE:TFormatEtc;
begin
     with FE do
     begin
          cfFormat:=cf_DIB;
          ptd:=nil;
          dwAspect:=DVASPECT_CONTENT;
          lindex:=-1;
          tymed:=TYMED_HGLOBAL;
     end;
     Accept:=DataObj.QueryGetData(FE)=S_OK;
end;

procedure TDropTargetBitmap.RenderDropped(DataObj: IDataObject; grfKeyState: Longint;
   pt: TPoint; var dwEffect: longint);
var FE: TFormatEtc;
    SM: TStgMedium;
    DataSize: longint;
    DataPtr: pointer;
begin
     with FE do
     begin
          cfFormat:=CF_DIB;
          ptd:=nil;
          dwAspect:=DVASPECT_CONTENT;
          lindex:=-1;
          tymed:=TYMED_HGLOBAL;
     end;
     if DataObj.GetData(FE,SM)=S_Ok then
     begin
          DataSize:=GlobalSize(SM.HGlobal);
          try
             DataPtr:=GlobalLock(SM.HGlobal);
             CopyAsBitmap(TDragDropBitmap(FOwner).FBitmap,DataPtr, DataSize);
          finally
             GlobalUnLock(SM.HGlobal);
             ReleaseStgMedium(SM);
          end;
     end;
end;

// TDragDropBitmap -------------------------------------------------------------

constructor TDragDropBitmap.Create(AOwner: TComponent);
begin
     inherited Create(AOwner);
     FBitmap:=TBitmap.Create;
     FDropTarget._Release;
     FDropTarget:=TDropTargetBitmap.Create(self);
end;

destructor TDragDropBitmap.Destroy;
begin
     FBitmap.free;
     inherited destroy;
end;


procedure TDragDropBitmap.SetBitmap(Bitmap:TBitmap);
var MS:TMemoryStream;
begin
     // A little bit dirty but short ...
     MS:=TMemoryStream.Create;
     Bitmap.SaveToStream(MS);
     MS.Seek(0,0);
     FBitmap.LoadFromStream(MS);
     MS.Free;
end;

function TDragDropBitmap.CreateDataObject:TDataObject;
begin
     if FBitmap.Empty=false then Result:=TDataObjectBitmap.Create(FBitmap)
     else Result:=nil;
end;

// Register --------------------------------------------------------------------

procedure Register;
begin
  {MP}RegisterComponents({'Shell32'}'DragDrop', [TDragDropBitmap]);
end;

end.
