Displaying 'x' icon in TBalloonHint - delphi

How to display 'x' (close) icon in TBalloonHint?
I want to programmatically display near a control on form a balloon hint that looks like notifications in system tray. If this is not what TBalloonHint can do, what should I use?

First you need a procedure to show your hint :
uses
CommCtrl;
// hWnd - control window handle to attach the baloon to.
// Icon - icon index; 0 = none, 1 = info, 2 = warning, 3 = error.
// BackCL - background color or clDefault to use system setting.
// TextCL - text and border colors or clDefault to use system setting.
// Title - tooltip title (bold first line).
// Text - tooltip text.
procedure ShowBalloonTip(hWnd: THandle; Icon: integer; BackCL, TextCL: TColor; Title: pchar; Text: PWideChar);
const
TOOLTIPS_CLASS = 'tooltips_class32';
TTS_ALWAYSTIP = $01;
TTS_NOPREFIX = $02;
TTS_BALLOON = $40;
TTF_SUBCLASS = $0010;
TTF_TRANSPARENT = $0100;
TTF_CENTERTIP = $0002;
TTM_ADDTOOL = $0400 + 50;
TTM_SETTITLE = (WM_USER + 32);
ICC_WIN95_CLASSES = $000000FF;
type
TOOLINFO = packed record
cbSize: integer;
uFlags: integer;
hWnd: THandle;
uId: integer;
rect: TRect;
hinst: THandle;
lpszText: PWideChar;
lParam: integer;
end;
var
hWndTip: THandle;
ti: TOOLINFO;
begin
hWndTip := CreateWindow(TOOLTIPS_CLASS, nil, WS_POPUP or TTS_CLOSE or TTS_NOPREFIX or TTS_BALLOON or TTS_ALWAYSTIP, 0, 0, 0, 0, hWnd, 0, HInstance, nil);
if hWndTip <> 0 then
begin
SetWindowPos(hWndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
ti.cbSize := SizeOf(ti);
ti.uFlags := TTF_CENTERTIP or TTF_TRANSPARENT or TTF_SUBCLASS;
ti.hWnd := hWnd;
ti.lpszText := Text;
Windows.GetClientRect(hWnd, ti.rect);
if BackCL <> clDefault then
SendMessage(hWndTip, TTM_SETTIPBKCOLOR, BackCL, 0);
if TextCL <> clDefault then
SendMessage(hWndTip, TTM_SETTIPTEXTCOLOR, TextCL, 0);
SendMessage(hWndTip, TTM_ADDTOOL, 1, integer(#ti));
SendMessage(hWndTip, TTM_SETTITLE, Icon mod 4, integer(Title));
//TTM_TRACKACTIVATE => Makes sure you have to close the hint you self
SendMessage(hWndTip, TTM_TRACKACTIVATE, integer(true), integer(#ti));
end;
end;
Then call it :
ShowBalloonTip(Button1.Handle, 4, clDefault, clRed, 'Baloon Title', 'Baloon text');
Hint: if you don’t have hWnd (e.g. Speed Buttons or other graphic component) or want to show the baloon elsewhere send TTM_TRACKPOSITION message after TTM_SETTITLE.
***** EDIT *****
This could also be done via a class helper
First create a unit with a Class helper
unit ComponentBaloonHintU;
interface
uses
Controls, CommCtrl, Graphics;
{$SCOPEDENUMS ON}
type
TIconKind = (None = TTI_NONE, Info = TTI_INFO, Warning = TTI_WARNING, Error = TTI_ERROR, Info_Large = TTI_INFO_LARGE, Warning_Large = TTI_WARNING_LARGE, Eror_Large = TTI_ERROR_LARGE);
TComponentBaloonhint = class helper for TWinControl
public
procedure ShowBalloonTip(Icon: TIconKind; const Title, Text: string);
end;
implementation
uses
Windows;
{ TComponentBaloonhint }
procedure TComponentBaloonhint.ShowBalloonTip(Icon: TIconKind; const Title, Text: string);
var
hWndTip: THandle;
ToolInfo: TToolInfo;
BodyText: pWideChar;
begin
hWndTip := CreateWindow(TOOLTIPS_CLASS, nil, WS_POPUP or TTS_CLOSE or TTS_NOPREFIX or TTS_BALLOON or TTS_ALWAYSTIP, 0, 0, 0, 0, Handle, 0, HInstance, nil);
if hWndTip = 0 then
exit;
GetMem(BodyText, 2 * 256);
try
ToolInfo.cbSize := SizeOf(TToolInfo);
ToolInfo.uFlags := TTF_CENTERTIP or TTF_TRANSPARENT or TTF_SUBCLASS;
ToolInfo.hWnd := Handle;
ToolInfo.lpszText := StringToWideChar(Text, BodyText, 2 * 356);
SetWindowPos(hWndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
ToolInfo.Rect := GetClientRect;
SendMessage(hWndTip, TTM_ADDTOOL, 1, integer(#ToolInfo));
SendMessage(hWndTip, TTM_SETTITLE, integer(Icon), integer(PChar(Title)));
SendMessage(hWndTip, TTM_TRACKACTIVATE, integer(true), integer(#ToolInfo));
finally
FreeMem(BodyText);
end;
end;
end.
Then call it:
uses
ComponentBaloonHintU;
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.ShowBalloonTip(TIconKind.Eror_Large, 'Baloon Title', 'Baloon text');
end;

Related

How to resize image using Skia4Delphi

I can load and save image files using Skia4Delphi.
Here is my code:
var
LImage: ISkImage;
LSurface: ISkSurface;
LPaint: ISkPaint;
begin
LImage := TSkImage.MakeFromEncodedFile('C:\IMAGE-OLD.PNG');
LPaint := TSkPaint.Create;
LSurface := TSkSurface.MakeRaster(LImage.Width, LImage.Height);
LSurface.Canvas.DrawImage(LImage, 0, 0, LPaint);
LSurface.MakeImageSnapshot.EncodeToFile('C:\IMAGE-NEW.PNG');
end;
How can I resize the image to a defined size (width and height) before saving? (Delphi 10.3.3 VCL)
Here is the code for a simple (stretched) resize:
uses
System.UITypes, Skia;
function GetResizedImage(const AImage: ISkImage; const ANewWidth, ANewHeight: Integer): ISkImage;
var
LSurface: ISkSurface;
begin
LSurface := TSkSurface.MakeRaster(ANewWidth, ANewHeight);
LSurface.Canvas.Clear(TAlphaColors.Null);
LSurface.Canvas.Scale(ANewWidth / AImage.Width, ANewHeight / AImage.Height);
LSurface.Canvas.DrawImage(AImage, 0, 0, TSkSamplingOptions.High);
Result := LSurface.MakeImageSnapshot;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
LImage: ISkImage;
begin
LImage := TSkImage.MakeFromEncodedFile('a.png');
LImage := GetResizedImage(LImage, 24, 24);
LImage.EncodeToFile('a.png', 100);
end;

How capture only the region of mouse click on screen?

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);

Loading Bitmaps onto WinAPI Window(NO FORM!) From .res Files

i have a Win32 API Application in Delphi 2007 with no form and would like to load a bitmap from a .res file. Been looking around for two day's and just can't seem to find anything on this subject so was time to post. :)
Assuming i need to add code to "WM_PAINT" message just not sure what to add. maybe could use GDI.
/Thanks.
EDIT:
function WndProc(hWin: HWnd; Msg, WParam, LParam: Longint): Longint; stdcall;
var
hbmp: HBITMAP;
ps: PAINTSTRUCT;
DC, hdcMem: HDC;
bmp: BITMAP;
oldBitmap: HGDIOBJ;
begin
case Msg of
WM_CREATE:
begin
hbmp := LoadImage(HInstance, 'C:\test_img.bmp', IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); // Never called H2077 Value assigned to 'hbmp' never used
ShowMessage('Im Here'); // Called
Result := 0;
Exit;
end;
WM_PAINT:
begin
hbmp := LoadImage(HInstance, 'C:\test_img.bmp', IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); // Called
DC := BeginPaint(hWin, ps);
hdcMem := CreateCompatibleDC(DC);
oldBitmap := SelectObject(hdcMem, hbmp);
GetObject(hbmp, SizeOf(bmp), #bmp);
BitBlt(DC, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
DeleteObject(hbmp);
EndPaint(hWin, ps);
Result := 0;
Exit;
end;
WM_DESTROY:
begin
PostQuitMessage(0);
Result := 0;
Exit;
end;
end;
Result := DefWindowProc(hWin, Msg, WParam, LParam);
end;
Am i right in thinking that WM_CREATE is the equivalent of Form1.OnCreate and WM_DESTROY is Form1.OnDestroy ect..
Look at the TBitmap class in the Graphics unit. It has LoadFromResourceName() and LoadFromResourceID() methods.

Delphi progressbar with two or more current values

I want to make a kind of multi-color bar in my software. A kind of progressbar, but with two current values.
That's why I need it.
I have some "budget parts", and each one of them has its own limit (100$, 1000$ etc.)
I also have an editing form for adding new bills (and linking bills to budget parts).
In this editor I want to visually represent how full is a budget part, and how much price of current bill affects this budget part.
For example, the whole bar is 100$.
Green part means sum of prices across saved bills, for example 60$.
Yellow part means price of the current bill, which is not saved yet, for example 5$.
Like this:
Of course, values should be set dynamically.
Can you recommend me any components for drawing this (maybe some advanced progressbar, that can display more than one current value?)
As David suggests, just paint it yourself. Just about the same amount of trouble. Drop a TImage where you want your gauge and use something like this:
procedure PaintTwoColorGauge(const BackgroundColor, BorderColor, FirstGaugeColor, SecondGaugeColor: TColor; FirstGaugeValue, SecondGaugeValue, TotalValue: Integer; const Img: TImage);
var B: TBitmap;
ImgWidth, G1Width, G2Width: Integer;
begin
B := TBitmap.Create;
try
B.Width := Img.Width;
B.Height := Img.Height;
B.Canvas.Brush.Color := BackgroundColor;
B.Canvas.Brush.Style := bsSolid;
B.Canvas.Pen.Style := psClear;
B.Canvas.Pen.Width := 1;
B.Canvas.FillRect(Rect(0, 0, B.Width, B.Height));
if TotalValue <> 0 then
begin
ImgWidth := B.Width - 2; // Don't account the width of the borders.
G1Width := (FirstGaugeValue * ImgWidth) div TotalValue;
G2Width := (SecondGaugeValue * ImgWidth) div TotalValue;
if G1Width > ImgWidth then G1Width := ImgWidth; // Just in case
if G2Width > ImgWidth then G2Width := ImgWidth;
if G2Width > G1Width then
begin
B.Canvas.Brush.Color := SecondGaugeColor;
B.Canvas.FillRect(Rect(0, 0, G2Width, B.Height));
B.Canvas.Brush.Color := FirstGaugeColor;
B.Canvas.FillRect(Rect(0, 0, G1Width, B.Height));
end
else
begin
B.Canvas.Brush.Color := FirstGaugeColor;
B.Canvas.FillRect(Rect(0, 0, G1Width, B.Height));
B.Canvas.Brush.Color := SecondGaugeColor;
B.Canvas.FillRect(Rect(0, 0, G2Width, B.Height));
end;
end;
B.Canvas.Pen.Color := BorderColor;
B.Canvas.Pen.Style := psSolid;
B.Canvas.Brush.Style := bsClear;
B.Canvas.Rectangle(0, 0, B.Width, B.Height);
Img.Picture.Assign(B);
finally B.Free;
end;
end;
For example, here's what this code does to my 3 TImages (my images are intentionally shpaed as you see them):
procedure TForm1.FormCreate(Sender: TObject);
begin
PaintTwoColorGauge(clWhite, clBlack, clGreen, clYellow, 50, 55, 100, Image1);
PaintTwoColorGauge(clWhite, clBlack, clGreen, clYellow, 50, 60, 100, Image2);
PaintTwoColorGauge(clWhite, clBlack, clGreen, clYellow, 20, 60, 100, Image3);
end;
Write your own, it's fun! But while not really thát difficult, writing an own component could look like a daunting task. Especially for novice uses or without experience doing so.
Next in line of options is to draw it yourself, and the therefore intended component should "always" be the TPaintBox control. Implement the OnPaint event handler and it redraws itself when needed. Here an example implementation of how to transform such a paint box into a double gauge component:
type
TDoubleGauge = record
BackgroundColor: TColor;
BorderColor: TColor;
Color1: TColor;
Color2: TColor;
Value1: Integer;
Value2: Integer;
MaxValue: Integer;
end;
TForm1 = class(TForm)
PaintBox1: TPaintBox;
procedure FormCreate(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
private
FDoubleGauge: TDoubleGauge;
end;
...
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
Box: TPaintBox absolute Sender;
MaxWidth: Integer;
Width1: Integer;
Width2: Integer;
begin
with FDoubleGauge do
begin
Box.Canvas.Brush.Color := BackgroundColor;
Box.Canvas.Pen.Color := BorderColor;
Box.Canvas.Rectangle(0, 0, Box.Width, Box.Height);
if MaxValue <> 0 then
begin
MaxWidth := Box.Width - 2;
Width1 := (MaxWidth * Value1) div MaxValue;
Width2 := (MaxWidth * Value2) div MaxValue;
Box.Canvas.Brush.Color := Color2;
if Abs(Value2) < Abs(MaxValue) then
Box.Canvas.FillRect(Rect(1, 1, Width2, Box.Height - 1));
Box.Canvas.Brush.Color := Color1;
if Abs(Value1) < Abs(Value2) then
Box.Canvas.FillRect(Rect(1, 1, Width1, Box.Height - 1));
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDoubleGauge.BackgroundColor := clWhite;
FDoubleGauge.BorderColor := clBlack;
FDoubleGauge.Color1 := clGreen;
FDoubleGauge.Color2 := clYellow;
FDoubleGauge.Value1 := 50;
FDoubleGauge.Value2 := 60;
FDoubleGauge.MaxValue := 100;
PaintBox1.Invalidate;
end;
Well, that looks like quite an effort. Especially when there are more of such doudble gauges needed on a single form. Therefore I like Cosmin Prund's answer, because he uses TImage components which are capable of "memorizing" what has to be redrawn when needed. Just as a bonus, here an alternative version of his code (with slightly different behaviour on invalid input):
procedure DrawDoubleGauge(BackgroundColor, BorderColor, Color1, Color2: TColor;
Value1, Value2, MaxValue: Integer; Img: TImage);
var
Width: Integer;
Width1: Integer;
Width2: Integer;
begin
Img.Canvas.Brush.Color := BackgroundColor;
Img.Canvas.Pen.Color := BorderColor;
Img.Canvas.Rectangle(0, 0, Img.Width, Img.Height);
if MaxValue <> 0 then
begin
Width := Img.Width - 2;
Width1 := (Width * Value1) div MaxValue;
Width2 := (Width * Value2) div MaxValue;
Img.Canvas.Brush.Color := Color2;
if Abs(Value2) < Abs(MaxValue) then
Img.Canvas.FillRect(Rect(1, 1, Width2, Img.Height - 1));
Img.Canvas.Brush.Color := Color1;
if Abs(Value1) < Abs(Value2) then
Img.Canvas.FillRect(Rect(1, 1, Width1, Img.Height - 1));
end;
end;
I was also looking for this exactly, as I don't want to spend any money on this I will follow the proposed solution, nevertheless if anyone would like an advanced component I found one that's not too expensive and look pretty decent in my opinion, here's the link in case it could be useful for someone else:
http://www.tmssoftware.com/site/advprogr.asp?s=
Thank's to all.

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