我有一个GUI应用程序,其中包括一些用于工具栏button,菜单字形,通知图标等的图标。这些图标作为资源链接到应用程序,并有各种不同的大小可用。 通常,对于工具栏button图像,我有可用的16px,24px和32px版本。 我的图标是部分透明的32bpp。
该应用程序是高DPI的意识,并根据stream行的字体缩放调整所有视觉元素的大小。 所以,例如,在100%的字体缩放,96dpi,工具栏图标大小为16px。 在125%缩放比例120dpi下,工具栏图标大小为20px。 我需要能够加载一个大小为20px的图标,而不会有任何别名效果。 我怎样才能做到这一点? 请注意,我想支持Windows 2000及更高版本。
在Vista上增加了许多新的功能,使得这个任务变得微不足道。 这里最适合的函数是LoadIconWithScaleDown
。
此功能将首先在图标文件中搜索具有完全相同大小的图标。 如果找不到匹配项,则除非cx和cy匹配标准图标大小(16,32,48或256个像素)之一,否则选择下一个最大的图标,然后缩小到所需的大小。 例如,如果callign应用程序请求x尺寸为40像素的图标,则使用48像素图标,并缩小到40像素。 相反,LoadImage函数选择32像素图标并将其最大扩展到40像素。
如果该功能无法找到更大的图标,则默认为查找下一个最小图标并将其缩放到所需大小的标准行为。
根据我的经验,这个功能在缩放方面表现出色,结果显示没有锯齿迹象。
对于Windows的早期版本,据我所知,没有一个功能可以充分执行这个任务。 从LoadImage
获得的结果质量非常差。 相反,我发现最好的办法如下:
这意味着在图标周围会有一个小小的透明边框,但通常这个尺寸足够小,可以忽略不计。 理想的选择是使用可以缩小的代码,就像LoadIconWithScaleDown
一样,但这是不重要的。
所以,这里不再赘述我使用的代码。
unit uLoadIconResource; interface uses SysUtils, Math, Classes, Windows, Graphics, CommCtrl; function LoadIconResourceSize(const ResourceName: string; IconSize: Integer): HICON;//will not throw an exception function LoadIconResourceMetric(const ResourceName: string; IconMetric: Integer): HICON; implementation function IconSizeFromMetric(IconMetric: Integer): Integer; begin case IconMetric of ICON_SMALL: Result := GetSystemMetrics(SM_CXSMICON); ICON_BIG: Result := GetSystemMetrics(SM_CXICON); else raise EAssertionFailed.Create('Invalid IconMetric'); end; end; 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; function CreateIconFromSmallerIcon(IconSize: Integer; SmallerIcon: HICON): HICON; 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; function LoadIconResourceSize(const ResourceName: string; IconSize: Integer): HICON;//will not throw an exception function LoadImage(IconSize: Integer): HICON; begin Result := Windows.LoadImage(HInstance, PChar(ResourceName), IMAGE_ICON, IconSize, IconSize, LR_DEFAULTCOLOR); end; type TGrpIconDir = packed record idReserved: Word; idType: Word; idCount: Word; end; TGrpIconDirEntry = packed record bWidth: Byte; bHeight: Byte; bColorCount: Byte; bReserved: Byte; wPlanes: Word; wBitCount: Word; dwBytesInRes: DWORD; wID: WORD; end; var i, BestAvailableIconSize, ThisSize: Integer; ResourceNameWide: WideString; Stream: TResourceStream; IconDir: TGrpIconDir; IconDirEntry: TGrpIconDirEntry; begin //LoadIconWithScaleDown does high quality scaling and so we simply use it if it's available ResourceNameWide := ResourceName; if Succeeded(LoadIconWithScaleDown(HInstance, PWideChar(ResourceNameWide), IconSize, IconSize, Result)) then begin exit; end; //XP: find the closest sized smaller icon and draw without stretching onto the centre of a canvas of the right size Try Stream := TResourceStream.Create(HInstance, ResourceName, RT_GROUP_ICON); Try Stream.Read(IconDir, SizeOf(IconDir)); Assert(IconDir.idCount>0); BestAvailableIconSize := high(BestAvailableIconSize); for i := 0 to IconDir.idCount-1 do begin Stream.Read(IconDirEntry, SizeOf(IconDirEntry)); Assert(IconDirEntry.bWidth=IconDirEntry.bHeight); ThisSize := IconDirEntry.bHeight; if ThisSize=0 then begin//indicates a 256px icon continue; end; if ThisSize=IconSize then begin //a perfect match, no need to continue Result := LoadImage(IconSize); exit; end else if ThisSize<IconSize then begin //we're looking for the closest sized smaller icon if BestAvailableIconSize<IconSize then begin //we've already found one smaller BestAvailableIconSize := Max(ThisSize, BestAvailableIconSize); end else begin //this is the first one that is smaller BestAvailableIconSize := ThisSize; end; end; end; if BestAvailableIconSize<IconSize then begin Result := CreateIconFromSmallerIcon(IconSize, LoadImage(BestAvailableIconSize)); if Result<>0 then begin exit; end; end; Finally FreeAndNil(Stream); End; Except ;//swallow because this routine is contracted not to throw exceptions End; //final fallback: make do without Result := 0; end; function LoadIconResourceMetric(const ResourceName: string; IconMetric: Integer): HICON; begin Result := LoadIconResourceSize(ResourceName, IconSizeFromMetric(IconMetric)); end; end.
使用这些功能是相当明显的。 他们假定资源与代码位于同一模块中。 如果你需要支持这个级别的普遍性的话,代码可以很容易地被推广到接收HMODULE
。
如果您希望加载与系统小图标或系统大图标相等的图标,请调用LoadIconResourceMetric
。 IconMetric
参数应该是ICON_SMALL
或ICON_BIG
。 对于工具栏,菜单和通知图标,应该使用ICON_SMALL
。
如果您希望以绝对值指定图标大小,请使用LoadIconResourceSize
。
这些功能返回一个HICON
。 您当然可以将其分配给TIcon
实例的Handle
属性。 更有可能你会希望添加到图像列表。 最简单的方法是调用ImageList_AddIcon
传递TImageList
实例的Handle
。
注1:旧版本的Delphi没有在CommCtrl
定义CommCtrl
。 对于这样的Delphi版本,您需要调用GetProcAddress
来加载它。 请注意,这是一个Unicode唯一的API,所以你必须发送一个PWideChar
作为资源名称。 像这样: LoadIconWithScaleDown(..., PWideChar(WideString(ResourceName)),...)
。
注2: LoadIconWithScaleDown
的定义存在缺陷。 如果在公共控制库被初始化后调用它,那么你将没有问题。 但是,如果在进程的生命周期中尽早调用该函数,则LoadIconWithScaleDown
可能会失败。 我刚刚提交了QC#101000来报告这个问题。 再一次,如果你受到这个困扰,那么你必须自己调用GetProcAddress
。