How capture only the region of mouse click on screen? - delphi

The code following makes a screenshot of desktop every time that mouse left button is clicked.
But i'm wanting make a screenshot only of region where happens mouse click, for example if some button is clicked on some website, the screenshot must be only of this button.
GIF
This is possible?
if yes, i will very happy if someone show a code example! Thanks in advance.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
Messages,
SysUtils,
Graphics,
Imaging.PngImage;
type
MouseLLHookStruct = record
end;
const
WH_MOUSE_LL = 14;
var
Msg: TMsg;
mHook: Cardinal;
procedure GetCursor(ScreenShotBitmap: TBitmap);
var
R: TRect;
Icon: TIcon;
II: TIconInfo;
CI: TCursorInfo;
begin
R := ScreenShotBitmap.Canvas.ClipRect;
Icon := TIcon.Create;
try
CI.cbSize := SizeOf(CI);
if GetCursorInfo(CI) then
if CI.Flags = CURSOR_SHOWING then
begin
Icon.Handle := CopyIcon(CI.hCursor);
if GetIconInfo(Icon.Handle, II) then
begin
ScreenShotBitmap.Canvas.Draw(CI.ptScreenPos.X - Integer(II.xHotspot) -
R.Left, CI.ptScreenPos.Y - Integer(II.yHotspot) - R.Top, Icon);
end;
end;
finally
Icon.Free;
end;
end;
procedure ScreenCapture;
var
DC: HDC;
Rect: TRect;
png: TPngImage;
Bitmap: TBitmap;
begin
png := TPngImage.Create;
Bitmap := TBitmap.Create;
GetWindowRect(GetDesktopWindow, Rect);
DC := GetWindowDC(GetDesktopWindow);
try
Bitmap.Width := Rect.Right - Rect.Left;
Bitmap.Height := Rect.Bottom - Rect.Top;
BitBlt(Bitmap.Canvas.Handle, 0, 0, Bitmap.Width, Bitmap.Height, DC, 0,
0, SRCCOPY);
GetCursor(Bitmap);
png.Assign(Bitmap);
png.SaveToFile('screenshot.png');
finally
ReleaseDC(GetDesktopWindow, DC);
png.Free;
Bitmap.Free;
end;
end;
function LowLevelMouseHookProc(nCode: LongInt; WPARAM: WPARAM; lParam: lParam)
: LRESULT; stdcall;
var
info: ^MouseLLHookStruct absolute lParam;
begin
Result := CallNextHookEx(mHook, nCode, WPARAM, lParam);
if (WPARAM = WM_LBUTTONUP) then
ScreenCapture;
end;
begin
mHook := SetWindowsHookEx(WH_MOUSE_LL, #LowLevelMouseHookProc, HInstance, 0);
while GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
UnhookWindowsHookEx(mHook);
end.
EDIT:
I found a alternative in VB.NET. But how could be a solution with Delphi code?
Private Shared Function CaptureCursor(ByRef x As Integer, ByRef y As Integer) As Bitmap
Dim bmp As Bitmap
Dim hicon As IntPtr
Dim ci As New CURSORINFO()
Dim icInfo As ICONINFO
ci.cbSize = Marshal.SizeOf(ci)
If GetCursorInfo(ci) Then
hicon = CopyIcon(ci.hCursor)
If GetIconInfo(hicon, icInfo) Then
x = ci.ptScreenPos.X - CInt(icInfo.xHotspot)
y = ci.ptScreenPos.Y - CInt(icInfo.yHotspot)
Dim ic As Icon = Icon.FromHandle(hicon)
bmp = ic.ToBitmap()
ic.Dispose()
Return bmp
End If
End If
Return Nothing
End Function
'Insert on Timer tick event
Private Sub Screenshot()
Dim x As Integer
Dim y As Integer
Dim cursorBmp As Bitmap = CaptureCursor(x, y)
Dim bmp As New Bitmap(Cursor.Size.Width, Cursor.Size.Height)
Dim sourceLocation As Point = Control.MousePosition
sourceLocation.Offset(-16, -16)
Using g As Graphics = Graphics.FromImage(bmp)
g.CopyFromScreen(sourceLocation, Point.Empty, bmp.Size)
g.DrawImage(cursorBmp, x - sourceLocation.X, y - sourceLocation.Y)
cursorBmp.Dispose()
End Using
Me.PictureBox1.Image = bmp
End Sub

There is a simple way, you can refer to the code in this thread,
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, Graphics;
procedure DrawCursor (ACanvas:TCanvas; Position:TPoint) ;
var
HCursor : THandle;
begin
HCursor := GetCursor;
DrawIconEx(ACanvas.Handle, Position.X, Position.Y,
HCursor, 32, 32, 0, 0, DI_NORMAL) ;
end;
function CaptureWindow(const WindowHandle: HWnd): TBitmap;
var
DC: HDC;
wRect: TRect;
CurPos: TPoint;
begin
DC := GetWindowDC(WindowHandle);
Result := TBitmap.Create;
try
GetWindowRect(WindowHandle, wRect);
Result.Width := wRect.Right - wRect.Left;
Result.Height := wRect.Bottom - wRect.Top;
BitBlt(Result.Canvas.Handle,
0,
0,
Result.Width,
Result.Height,
DC,
0,
0,
SRCCOPY);
GetCursorPos(CurPos);
DrawCursor(Result.Canvas, CurPos);
finally
ReleaseDC(WindowHandle, DC);
end;
end;
// Sample usage starts here
var
Bmp: TBitmap;
begin
Bmp := CaptureWindow(GetDesktopWindow);
Bmp.SaveToFile('D:\TempFiles\FullScreenCap.bmp');
Bmp.Free;
WriteLn('Screen captured.');
ReadLn;
end.
This code can get the screen capture containing the mouse position, just use BitBlt to specify the mouse coordinates and rectangle size (button size), and finally get the BMP image you need. Use DrawImage to draw the BMP image into a rectangular box, as shown in GIF.
Mouse coordinates can be obtained by calling GetCursorInfo, and the size of the rectangle can be specified according to your needs.
Note that after you have the mouse coordinates, you need to subtract the size of half a rectangle to the left and up respectively when you pass them into BitBlt.
For example,
BitBlt(Newhdc,
0,
0,
rect_x, //size of the rect
rect_y,
HDC,
x - half_rect_x, //x,y => mouse coordinates
y - half_rect_y, //half_rect_x, half_rect_y => the size of half the rectangle
SRCCOPY);

Related

It is possible to render the whole content of a TScrollBox control to a image file?

I tried this function, but it only caputre the visible client area... It is possible to capture the whole area of the control, like it is expanded that no vertical scroll bar is needed ?
function CaptureWinCtrlImage(AWinControl: TWinControl; AImg: TGraphic): Boolean;
var
vBmp: Graphics.TBitmap;
vControlDC: HDC;
begin
Result:= False;
if not AWinControl.HandleAllocated then Exit;
vControlDC:= GetWindowDC(AWinControl.Handle);
try
vBmp:= Graphics.TBitmap.Create;
try
vBmp.PixelFormat := pf24bit;
vBmp.Height := AWinControl.Height;
vBmp.Width := AWinControl.Width;
BitBlt(vBmp.Canvas.Handle, 0, 0, vBmp.Width, vBmp.Height, vControlDC, 0, 0, SRCCOPY);
AImg.Assign(vBmp);
Result:= True;
finally
vBmp.Free;
end;
finally
ReleaseDC(AWinControl.Handle, vControlDC);
end;
end;
I did it by scrolling the box and making a capture every time. It's not nice to see that flicker on the screen, but at least it works. And it might capture something else if some window appears on the screen at that time...
procedure CaptureScrollBox(ABox: TScrollBox; FileName: String);
var vBmp: Graphics.TBitmap;
vControlDC: HDC;
Org: TPoint;
PngImg: TPngImage;
Y, P: Integer;
begin
PngImg:= TPngImage.Create;
vControlDC:= GetWindowDC(ABox.Handle);
vBmp:= Graphics.TBitmap.Create;
vBmp.PixelFormat := pf24bit;
vBmp.Width := ABox.ClientWidth;
vBmp.Height := ABox.VertScrollBar.Range;
Org.X:= ABox.Left; Org.Y:= ABox.Top;
Org:= ABox.Parent.ClientToScreen(Org);
Org.X:= ABox.ClientOrigin.X - Org.X;
Org.Y:= ABox.ClientOrigin.Y - Org.Y;
P:= ABox.VertScrollBar.Position;
Y:= 0;
repeat
ABox.VertScrollBar.Position:= Y; ABox.Repaint;
if ABox.VertScrollBar.Position <> Y then begin
Org.Y:= Org.Y + ( Y - ABox.VertScrollBar.Position );
BitBlt(vBmp.Canvas.Handle, 0, Y, vBmp.Width, vBmp.Height, vControlDC, Org.X, Org.Y, SRCCOPY);
Break;
end
else begin
BitBlt(vBmp.Canvas.Handle, 0, Y, vBmp.Width, vBmp.Height, vControlDC, Org.X, Org.Y, SRCCOPY);
Inc(Y, ABox.ClientHeight);
end;
until False;
ABox.VertScrollBar.Position:= P;
PngImg.Assign(vBmp);
PngImg.SaveToFile(FileName);
vBmp.Free;
ReleaseDC(ABox.Handle, vControlDC);
PngImg.Free;
end;

In Delphi, how does TBitmap.Monochrome and .PixelFormat influcence the format of .ScanLine?

I want to assign a given buffer with a bitmap in Mono8 format (Monochrome 8 Bits) to a bitmap. I then assign the resulting bitmap to a TImage component to display it. The pictures are screenshots of the resulting display.
The following code works but seems a bit wasteful:
procedure CopyToBitmapMono824(_Buffer: PByte; _Bmp: TBitmap);
var
y: Integer;
x: Integer;
ScanLine: PdzRgbTripleArray;
begin
for y := 0 to _Bmp.Height - 1 do begin
ScanLine := _Bmp.ScanLine[y];
for x := 0 to _Bmp.Width - 1 do begin
// monochrome: all 3 colors set to the same value
ScanLine[x].Red := _Buffer^;
ScanLine[x].Green := _Buffer^;
ScanLine[x].Blue := _Buffer^;
Inc(_Buffer);
end;
end;
end;
// [...]
fBmp.PixelFormat := pf24Bit;
FBmp.Monochrome := False;
CopyToBitmap(Buffer, fBmp);
I would rather use a bitmap in pf8Bit format which I tried:
procedure CopyToBitmapMono8(_Buffer: PByte; _Bmp: TBitmap);
var
y: Integer;
x: Integer;
ScanLine: PByteArray;
begin
for y := 0 to _Bmp.Height - 1 do begin
ScanLine := _Bmp.ScanLine[y];
for x := 0 to _Bmp.Width - 1 do begin
ScanLine[x] := _Buffer^;
Inc(_Buffer);
end;
end;
end;
// [...]
FBmp.PixelFormat := pf8bit;
FBmp.Monochrome := False; // I also tried Monochrome := true
CopyToBitmapMono8(Buffer, FBmp)
If MonoChrome is true, the picture only has about 1/4 of the expected width, the rest is white.
If MonoChrome is false, the picture has the expected width, but the left 1/4 of it is monochrome, the rest contains false colors.
I'm obviously missing something, but what?
EDIT: The effect that the bitmap is only 1/4 of the expected size apparently was a side effect of converting it to a JPEG for saving prior to displaying it (code that I did not show above, mea culpa). So the problem was simply that I did not set a monochrome palette for the bitmap.
Monochrome has sense for pf1bit bitmaps.
Otherwise Monochrome := True changes bitmap format to DDB (pfDevice). Your screen is 32-bit, so call to Scanline caused DibNeeded call and transformation to 32bit, and using of function CopyToBitmapMono8 (intended for 8-bit) filled only 1/4 of screen.
For proper usage of 8-bit bitmaps you have to change standard weird palette (used in the right part of last image) to gray one.
procedure CopyToBitmapMono8(_Buffer: PByte; _Bmp: TBitmap);
var
y: Integer;
x: Integer;
ScanLine: PByteArray;
begin
for y := 0 to _Bmp.Height - 1 do begin
ScanLine := _Bmp.ScanLine[y];
for x := 0 to _Bmp.Width - 1 do begin
ScanLine[x] := _Buffer^;
Inc(_Buffer);
end;
end;
end;
var
FBmp: TBitmap;
Buffer: PbyteArray;
i: integer;
begin
GetMem(Buffer, 512 * 100);
for i := 0 to 512 * 100 - 1 do
Buffer[i] := (i and 511) div 2; // gray gradient
FBmp := Tbitmap.Create;
FBmp.Width := 512;
FBmp.Height := 100;
FBmp.PixelFormat := pf8bit;
CopyToBitmapMono8(PByte(Buffer), FBmp);
Canvas.Draw(0, 0, FBmp);
//now right approach
FBmp.Palette := MakeGrayPalette; // try to comment
CopyToBitmapMono8(PByte(Buffer), FBmp);
Canvas.Draw(0, 110, FBmp);
end;
function TForm1.MakeGrayPalette: HPalette;
var
i: integer;
lp: TMaxLogPalette;
begin
lp.palVersion := $300;
lp.palNumEntries := 256;
for i := 0 TO 255 do begin
lp.palPalEntry[i].peRed := i;
lp.palPalEntry[i].peGreen := i;
lp.palPalEntry[i].peBlue := i;
lp.palPalEntry[i].peFlags := PC_RESERVED;
end;
Result := CreatePalette(pLogPalette(#lp)^);
end;
And example at efg2 page

How to crop an FMX TBitmap

I receive a bitmap via TCameraComponent.SampleBufferReady event. Then I need to crop the received image so that I get a, for instance, recangular image.
I calculate the necessary parameters in the following method:
procedure TPersonalF.SampleBufferReady(Sender: TObject;
const ATime: TMediaTime);
var
BMP: TBitmap;
X, Y, W, H: Word;
begin
Try
BMP := TBitmap.Create;
CameraComponent.SampleBufferToBitmap(BMP, true);
if BMP.Width >= BMP.Height then //landscape
begin
W:=BMP.Height;
H:=W;
Y:=0;
X:=trunc((BMP.Width-BMP.Height)/2);
end
else //portrait
begin
W:=BMP.Width;
H:=W;
X:=0;
Y:=trunc((BMP.Height-BMP.Width)/2);
end;
CropBitmap(BMP, Image1.Bitmap, X,Y,W,H);
Finally
BMP.Free;
End;
end;
I found an answer by #RRUZ delphi-how-do-i-crop-a-bitmap-in-place, but it requires a VCL API handle and is uses a Windows GDI function:
procedure CropBitmap(InBitmap, OutBitMap: TBitmap; X, Y, W, H: Word);
begin
OutBitMap.PixelFormat := InBitmap.PixelFormat;
OutBitMap.Width := W;
OutBitMap.Height := H;
BitBlt(OutBitMap.Canvas.Handle, 0, 0, W, H, InBitmap.Canvas.Handle, X,
Y, SRCCOPY);
end;
My project is using FMX, and I plan to port it to Android platform in the future. So I am expecting to get problems if I use handles. How can I solve this problem?
Assuming you can guarantee that InBitmap and OutBitMap exist (if not, you can handle error checking yourself)
procedure CropBitmap(InBitmap, OutBitMap: TBitmap; X, Y, W, H: Word);
var
iRect : TRect;
begin
OutBitMap.PixelFormat := InBitmap.PixelFormat;
OutBitMap.Width := W;
OutBitMap.Height := H;
iRec.Left := 0;
iRect.Top := 0;
iRect.Width := W;
iRect.Height := H;
OutBitMap.CopyFromBitmap( InBitMap, iRect, 0, 0 );
end;
It is the same as the original but uses Firemonkey CopyFromBitmap which is similar to the Windows rather cryptically named BitBlt.

painting background from TSeStyleFont

i'm trying to paint vcl style background from TSeStyleFont like in Bitmap Style Designer ..
is there any way to draw the background ?
i have make a try :
- draw the object first in a bitmap using DrawElement .
- than copy current bitmap to a nother clean bitmap using 'Bitmap.Canvas.CopyRect' the problem is that : this methode does not work correctly with objects that has Glyph such as CheckBox ...
var
bmp, bmp2: TBitmap;
Details: TThemedElementDetails;
R, Rn: TRect;
begin
bmp := TBitmap.Create;
bmp2 := TBitmap.Create;
R := Rect(0, 0, 120, 20);
Rn := Rect(0 + 4, 0 + 4, 120 - 4, 20 - 4);
bmp.SetSize(120, 20);
bmp2.SetSize(120, 20);
Details := StyleServices.GetElementDetails(TThemedButton.tbPushButtonHot);
StyleServices.DrawElement(bmp.Canvas.Handle, Details, R);
bmp2.Canvas.CopyRect(R, bmp.Canvas, Rn);
Canvas.Draw(10, 10, bmp2);
bmp.Free;
bmp2.Free;
end;
If you want draw the background of the buttons you must use the StyleServices.DrawElement method passing the proper TThemedButton part.
Try this sample
uses
Vcl.Styles,
Vcl.Themes;
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
var
Details : TThemedElementDetails;
begin
Details := StyleServices.GetElementDetails(tbPushButtonPressed);
StyleServices.DrawElement(PaintBox1.Canvas.Handle, Details, PaintBox1.ClientRect);
Details := StyleServices.GetElementDetails(tbPushButtonNormal);
StyleServices.DrawElement(PaintBox2.Canvas.Handle, Details, PaintBox2.ClientRect);
end;
If you want draw the background without corners, you can adjust the bounds of the TRect like so
Details : TThemedElementDetails;
LRect : TRect;
begin
LRect:=PaintBox1.ClientRect;
LRect.Inflate(3,3);
Details := StyleServices.GetElementDetails(tbPushButtonPressed);
StyleServices.DrawElement(PaintBox1.Canvas.Handle, Details, LRect);
LRect:=PaintBox2.ClientRect;
LRect.Inflate(3,3);
Details := StyleServices.GetElementDetails(tbPushButtonNormal);
StyleServices.DrawElement(PaintBox2.Canvas.Handle, Details, LRect);
end;

How to add an icon to the ImageList bigger-sized without stretching?

I have ImageList sized 72x72, handle to the icon (HICON), obtained by SHGetFileInfo (for example a large icon sized 32x32). How to add it to this ImageList keeping transparency, but without stretching? Now I draw the icon in the middle of a temporary bitmap desired size, then add it to the ImageList.
SHGetFileInfo(PChar(Path + sr.Name), sr.FindData.dwFileAttributes, fi, SizeOf(fi), SHGFI_ICON or SHGFI_LARGEICON or SHGFI_USEFILEATTRIBUTES);
Bmp:=TBitmap.Create;
Bmp.PixelFormat:=pf32bit;
Bmp.SetSize(72, 72);
DrawIcon(Bmp.Canvas.Handle, 20, 20, fi.hIcon);
iIcon:=ImageList_AddMasked(ilThumbs.Handle, Bmp.Handle, 0);
Bmp.Free;
But I think a way faster exists (without drawing on temporary bitmap). Also image in ImageList loses transparency and when I set index of this Image in ImageList for ListView item.ImageIndex it looks not pretty (when this item is selected, white background around is present). Is any way to solve this problem?
Thanks.
This is the code that I use to perform this task.
Note that I am assuming that the original icon uses 32 bit colour, with alpha channel. That's reasonable in the settings that I use this code, but I can't be sure whether or not it's reasonable for you.
uses
Windows, Graphics;
function CreateIconFromSmallerIcon(IconSize: Integer; SmallerIcon: HICON): HICON;
procedure GetDIBheaderAndBits(bmp: HBITMAP; out bih: BITMAPINFOHEADER; out bits: Pointer);
var
pbih: ^BITMAPINFOHEADER;
bihSize, bitsSize: DWORD;
begin
bits := nil;
GetDIBSizes(bmp, bihSize, bitsSize);
pbih := AllocMem(bihSize);
Try
bits := AllocMem(bitsSize);
GetDIB(bmp, 0, pbih^, bits^);
if pbih.biSize<SizeOf(bih) then begin
FreeMem(bits);
bits := nil;
exit;
end;
bih := pbih^;
Finally
FreeMem(pbih);
End;
end;
procedure InitialiseBitmapInfoHeader(var bih: BITMAPINFOHEADER);
begin
bih.biSize := SizeOf(BITMAPINFOHEADER);
bih.biWidth := IconSize;
bih.biHeight := 2*IconSize;//height of xor bitmap plus height of and bitmap
bih.biPlanes := 1;
bih.biBitCount := 32;
bih.biCompression := BI_RGB;
end;
procedure CreateXORbitmap(const sbih, dbih: BITMAPINFOHEADER; sptr, dptr: PDWORD);
var
line, xOffset, yOffset: Integer;
begin
xOffset := (IconSize-sbih.biWidth) div 2;
yOffset := (IconSize-sbih.biHeight) div 2;
inc(dptr, xOffset + IconSize*yOffset);
for line := 0 to sbih.biHeight-1 do begin
Move(sptr^, dptr^, sbih.biWidth*SizeOf(DWORD));
inc(dptr, IconSize);//relies on the fact that no padding is needed for RGBA scanlines
inc(sptr, sbih.biWidth);//likewise
end;
end;
var
SmallerIconInfo: TIconInfo;
sBits, xorBits: PDWORD;
xorScanSize, andScanSize: Integer;
xorBitsSize, andBitsSize: Integer;
sbih: BITMAPINFOHEADER;
dbih: ^BITMAPINFOHEADER;
resbitsSize: DWORD;
resbits: Pointer;
begin
Result := 0;
Try
if not GetIconInfo(SmallerIcon, SmallerIconInfo) then begin
exit;
end;
Try
GetDIBheaderAndBits(SmallerIconInfo.hbmColor, sbih, Pointer(sBits));
if Assigned(sBits) then begin
Try
if (sbih.biWidth>IconSize) or (sbih.biHeight>IconSize) or (sbih.biPlanes<>1) or (sbih.biBitCount<>32) then begin
exit;
end;
xorScanSize := BytesPerScanline(IconSize, 32, 32);
Assert(xorScanSize=SizeOf(DWORD)*IconSize);
andScanSize := BytesPerScanline(IconSize, 1, 32);
xorBitsSize := IconSize*xorScanSize;
andBitsSize := IconSize*andScanSize;
resbitsSize := SizeOf(BITMAPINFOHEADER) + xorBitsSize + andBitsSize;
resbits := AllocMem(resbitsSize);//AllocMem zeroises the memory
Try
dbih := resbits;
InitialiseBitmapInfoHeader(dbih^);
xorBits := resbits;
inc(PByte(xorBits), SizeOf(BITMAPINFOHEADER));
CreateXORbitmap(sbih, dbih^, sBits, xorBits);
//don't need to fill in the mask bitmap when using RGBA
Result := CreateIconFromResourceEx(resbits, resbitsSize, True, $00030000, IconSize, IconSize, LR_DEFAULTCOLOR);
Finally
FreeMem(resbits);
End;
Finally
FreeMem(sBits);
End;
end;
Finally
if SmallerIconInfo.hbmMask<>0 then begin
DeleteObject(SmallerIconInfo.hbmMask);
end;
if SmallerIconInfo.hbmColor<>0 then begin
DeleteObject(SmallerIconInfo.hbmColor);
end;
End;
Finally
DestroyIcon(SmallerIcon);
End;
end;
Ok, my solution below:
procedure SetAlpha(Bitmap: TBitmap);
type
PPixelRec = ^TPixelRec;
TPixelRec = packed record
B, G, R, Alpha: Byte;
end;
var
X, Y: Integer;
Pixel: PPixelRec;
begin
for Y := 0 to (Bitmap.Height - 1) do
begin
Pixel := Bitmap.ScanLine[Y];
for X := 0 to (Bitmap.Width - 1) do
begin
Pixel.Alpha:=255;
Inc(Pixel);
end;
end;
end;
//skipped
var Bmp: TBitmap;
fi: TSHFileInfo;
ImageList1: TImageList;
begin
ImageList1:=TImageList.CreateSize(72, 72);
ImageList1.DrawingStyle:=dsTransparent;
ImageList1.ColorDepth:=cd32Bit;
SHGetFileInfo('c:\Windows\notepad.exe', FILE_ATTRIBUTE_NORMAL, fi, SizeOf(fi), SHGFI_ICON or SHGFI_LARGEICON or SHGFI_USEFILEATTRIBUTES);
Bmp:=TBitmap.Create;
Bmp.SetSize(72, 72);
SetAlpha(Bmp);
Bmp.Canvas.Brush.Color:=clWhite;
Bmp.Canvas.FillRect(Rect(0, 0, 72, 72));
DrawIcon(Bmp.Canvas.Handle, 20, 20, fi.hIcon);
fi.iIcon:=ImageList1.Add(Bmp, nil);
ImageList1.Draw(Canvas, 0, 0, fi.iIcon); //just to see that's alright
end;

Resources