如何从资源加载图标而不会遇到混叠? [英] How do I load icons from a resource without suffering from aliasing?

查看:188
本文介绍了如何从资源加载图标而不会遇到混叠?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个GUI应用程序,其中包括用于工具栏按钮,菜单字形,通知图标等的许多图标。这些图标作为资源链接到应用程序,并且可以使用各种不同的大小。通常,对于工具栏按钮图像,我可以使用16px,24px和32px版本。我的图标是32bpp,具有部分透明度。



应用程序是高DPI感知,并根据当前的字体缩放调整所有视觉元素的大小。所以,例如,在100%字体缩放,96dpi,工具栏图标大小是16px。在125%缩放,120dpi,工具栏图标大小为20像素。我需要能够加载大小为20像素的图标,而不会有任何混叠效果。我该怎么做?请注意,我想支持Windows 2000和更高版本。

解决方案

在Vista上添加了一些新功能,这个任务琐碎。这里最适合的功能是 LoadIconWithScaleDown


此功能将首先在图标文件中搜索具有完全相同大小的图标。如果没有找到匹配项,那么除非cx和cy都匹配了标准图标尺寸之一--16,32,48或256像素 - 选择下一个最大的图标,然后按比例缩小到所需的大小。例如,如果由呼号应用程序请求x维度为40像素的图标,则使用48像素图标并缩小至40像素。相反,LoadImage功能可以选择32像素的图标并将其缩放到40像素。



如果该功能无法找到较大的图标,则默认为找到下一个最小图标并将其缩放到所需大小的标准行为。


根据我的经验,这个功能非常出色缩放,结果显示没有混叠迹象。



对于早期版本的Windows,据我所知,没有一个可以充分执行此任务的单个功能。从 LoadImage 获得的结果质量很差。相反,我找到的最佳方法如下:


  1. 检查资源中的可用图像,以找到最大尺寸的图像

  2. 创建一个所需大小的新图标,并将其初始化为完全透明。

  3. 将较小的图标从资源在新的(较大的)图标的中心。

这意味着图标周围会有一个小的透明边框,但是通常这是足够小到不重要的。理想的选择是使用可以像 LoadIconWithScaleDown 一样缩小的代码,但这是不寻常的。



所以,不用多说,这里是我使用的代码。

  unit uLoadIconResource; 

接口

使用
SysUtils,Math,Classes,Windows,Graphics,CommCtrl;

函数LoadIconResourceSize(const ResourceName:string; IconSize:Integer):HICON; //不会抛出异常
函数LoadIconResourceMetric(const ResourceName:string; IconMetric:Integer):HICON;

实现

函数IconSizeFromMetric(IconMetric:Integer):整数;
begin
case IconMetric
ICON_SMALL:
结果:= GetSystemMetrics(SM_CXSMICON);
ICON_BIG:
结果:= GetSystemMetrics(SM_CXICON);
else
raise EAssertionFailed.Create('Invalid IconMetric');
结束
结束

程序GetDIBheaderAndBits(bmp:HBITMAP; out bih:BITMAPINFOHEADER; out bits:Pointer);
var
pbih:^ BITMAPINFOHEADER;
bihSize,bitsSize:DWORD;
begin
位:= nil;
GetDIBSizes(bmp,bihSize,bitsSize);
pbih:= AllocMem(bihSize);
尝试
位:= AllocMem(bitsSize);
GetDIB(bmp,0,pbih ^,bits ^);
如果pbih.biSize< SizeOf(bih)然后开始
FreeMem(bits);
位:= nil;
退出;
结束
bih:= pbih ^;
最后
FreeMem(pbih);
结束;
结束

函数CreateIconFromSmallerIcon(IconSize:Integer; SmallerIcon:HICON):HICON;

procedure InitializeBitmapInfoHeader(var bih:BITMAPINFOHEADER);
begin
bih.biSize:= SizeOf(BITMAPINFOHEADER);
bih.biWidth:= IconSize;
bih.biHeight:= 2 * IconSize; // xor位图的高度加上位图
bih.biPlanes:= 1;
bih.biBitCount:= 32;
bih.biCompression:= BI_RGB;
结束

程序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); //依赖于RGBA扫描线不需要填充的事实
inc(sptr,sbih.biWidth); //同样
end;
结束

var
SmallerIconInfo:TIconInfo;
sBits,xorBits:PDWORD;
xorScanSize,andScanSize:Integer;
xorBitsSize,andBitsSize:Integer;
sbih:BITMAPINFOHEADER;
dbih:^ BITMAPINFOHEADER;
resbitsSize:DWORD;
resbits:指针;

begin
结果:= 0;
尝试
如果不是GetIconInfo(SmallerIcon,SmallerIconInfo)然后开始
exit;
结束
尝试
GetDIBheaderAndBits(SmallerIconInfo.hbmColor,sbih,Pointer(sBits));
如果分配(sBits)然后开始
尝试
if(sbih.biWidth> IconSize)或(sbih.biHeight> IconSize)或(sbih.biPlanes 1)或(sbih。 biBitCount 32)然后开始
exit;
结束

xorScanSize:= BytesPerScanline(IconSize,32,32);
Assert(xorScanSize = SizeOf(DWORD)* IconSize);
andScanSize:= BytesPerScanline(IconSize,1,32);
xorBitsSize:= IconSize * xorScanSize;
和BitsSize:= IconSize * andScanSize;
resbitsSize:= SizeOf(BITMAPINFOHEADER)+ xorBitsSize + andBitsSize;
resbits:= AllocMem(resbitsSize); // AllocMem将内存置零
尝试
dbih:= resbits;
InitialiseBitmapInfoHeader(dbih ^);

xorBits:= resbits;
inc(PByte(xorBits),SizeOf(BITMAPINFOHEADER));
CreateXORbitmap(sbih,dbih ^,sBits,xorBits);结果:= CreateIconFromResourceEx(resbits,resbitsSize,True,$ 00030000,IconSize,IconSize,LR_DEFAULTCOLOR);

//使用RGBA
时不需要填写掩码位图
最后
FreeMem(resbits);
结束;
最后
FreeMem(sBits);
结束;
结束
最后
如果SmallerIconInfo.hbmMask<> 0然后开始
DeleteObject(SmallerIconInfo.hbmMask);
结束
如果SmallerIconInfo.hbmColor<> 0则开始
DeleteObject(SmallerIconInfo.hbmColor);
结束
结束;
最后
DestroyIcon(SmallerIcon);
结束;
结束

函数LoadIconResourceSize(const ResourceName:string; IconSize:Integer):HICON; //不会抛出异常

函数LoadImage(IconSize:Integer):HICON;
begin
结果:= Windows.LoadImage(HInstance,PChar(ResourceName),IMAGE_ICON,IconSize,IconSize,LR_DEFAULTCOLOR);
结束

type
TGrpIconDir =打包记录
idReserved:Word;
idType:Word;
idCount:Word;
结束

TGrpIconDirEntry =打包记录
bWidth:Byte;
bHeight:Byte;
bColorCount:Byte;
bReserved:Byte;
wPlanes:Word;
wBitCount:Word;
dwBytesInRes:DWORD;
wID:WORD;
结束

var
i,BestAvailableIconSize,ThisSize:Integer;
ResourceNameWide:WideString;
流:TResourceStream;
IconDir:TGrpIconDir;
IconDirEntry:TGrpIconDirEntry;

begin
// LoadIconWithScaleDown执行高质量缩放,所以我们只要使用它就可以使用
ResourceNameWide:= ResourceName;
如果成功(LoadIconWithScaleDown(HInstance,PWideChar(ResourceNameWide),IconSize,IconSize,Result))然后开始
exit;
结束

// XP:找到最接近大小的小图标,并绘制不伸展到正确大小的画布中心
尝试
Stream:= TResourceStream.Create(HInstance, ResourceName,RT_GROUP_ICON);
尝试
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;
如果ThisSize = 0,则开始//表示256px图标
continue;
结束
如果ThisSize = IconSize然后开始
//完美匹配,不需要继续
结果:= LoadImage(IconSize);
退出;
end else if ThisSize< IconSize then begin
//我们正在寻找最接近大小的小图标
如果BestAvailableIconSize< IconSize然后开始
//我们已经找到一个较小的
BestAvailableIconSize:= Max(ThisSize,BestAvailableIconSize);
end else begin
//这是第一个更小的
BestAvailableIconSize:= ThisSize;
结束
结束
结束
如果BestAvailableIconSize< IconSize然后开始
结果:= CreateIconFromSmallerIcon(IconSize,LoadImage(BestAvailableIconSize));
如果Result<> 0然后开始
exit;
结束
结束
最后
FreeAndNil(Stream);
结束;
除了
; //吞下,因为这个例程被约定不抛出异常
结束;

// final fallback:make do without
结果:= 0;
结束

函数LoadIconResourceMetric(const ResourceName:string; IconMetric:Integer):HICON;
begin
结果:= LoadIconResourceSize(ResourceName,IconSizeFromMetric(IconMetric));
结束

结束。

使用这些功能是相当明显的。他们假设资源位于与代码相同的模块中。如果您需要对该级别的通用性支持,代码可以很容易地被概括为接收 HMODULE



调用如果您希望加载大小等于系统小图标或系统大图标的图标,则 LoadIconResourceMetric IconMetric 参数应为 ICON_SMALL ICON_BIG 。对于工具栏,菜单和通知图标,应使用 ICON_SMALL



如果您希望指定图标大小绝对条款使用 LoadIconResourceSize



这些函数返回一个 HICON 。您当然可以将它分配给 TIcon 实例的句柄属性。更有可能您希望添加到图像列表。最简单的方法是调用 ImageList_AddIcon 传递 TImageList 实例的句柄 / p>

注1:旧版本的Delphi没有 LoadIconWithScaleDown CommCtrl 。对于这样的Delphi版本,您需要调用 GetProcAddress 来加载它。请注意,这是一个Unicode唯一的API,因此您必须为资源名称发送一个 PWideChar 。像这样: LoadIconWithScaleDown(...,PWideChar(WideString(ResourceName)),...)



注2: LoadIconWithScaleDown 的定义有缺陷。如果在普通控件库初始化之后调用它,那么您将没有任何问题。但是,如果您在进程的生命周期中调用函数,那么 LoadIconWithScaleDown 可能会失败。我刚刚提交了 QC#101000 来报告此问题。再次,如果你受到这种折磨,那么你必须自己调用 GetProcAddress


I have a GUI application which includes a number of icons used for toolbar buttons, menu glyphs, notification icons etc. These icons are linked to the application as resources and a variety of different sizes are available. Typically, for toolbar button images I have available 16px, 24px and 32px versions. My icons are 32bpp with partial transparency.

The application is high DPI aware and adjusts the size of all visual elements according to the prevailing font scaling. So, for example, at 100% font scaling, 96dpi, the toolbar icon size is 16px. At 125% scaling, 120dpi, the toolbar icon size is 20px. I need to be able to load an icon of size 20px without any aliasing effects. How can I do this? Note that I would like to support Windows 2000 and later.

解决方案

On Vista and up a number of new functions were added that make this task trivial. The function that is most appropriate here is LoadIconWithScaleDown.

This function will first search the icon file for an icon having exactly the same size. If a match is not found, then unless both cx and cy match one of the standard icon sizes—16, 32, 48, or 256 pixels— the next largest icon is selected and then scaled down to the desired size. For example, if an icon with an x dimension of 40 pixels is requested by the callign application, the 48-pixel icon is used and scaled down to 40 pixels. In contrast, the LoadImage function selects the 32-pixel icon and scales it up to 40 pixels.

If the function is unable to locate a larger icon, it defaults to the standard behavior of finding the next smallest icon and scaling it up to the desired size.

In my experience this function does an excellent job of scaling and the results show no signs of aliasing.

For earlier versions of Windows there is, to the very best of my knowledge, no single function that can perform this task adequately. The results obtained from LoadImage are of very poor quality. Instead the best approach I have found is as follows:

  1. Examine the available images in the resource to find the image with the largest size that is less than desired icon size.
  2. Create a new icon of the desired size and initialise it to be fully transparent.
  3. Place the smaller icon from the resource in the centre of the new (larger) icon.

This means that there will be a small transparent border around the icon, but typically this is small enough to be insignificant. The ideal option would be to use code that could scale down just as LoadIconWithScaleDown does, but that is non-trivial to write.

So, without further ado here is the code I use.

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.

Using these function is quite obvious. They assume that the resource is located in the same module as the code. The code could readily be generalised to receive an HMODULE in case you needed support for that level of generality.

Call LoadIconResourceMetric if you wish to load icons of size equal to the system small icon or system large icon. The IconMetric parameter should be either ICON_SMALL or ICON_BIG. For toolbars, menus and notification icons, ICON_SMALL should be used.

If you wish to specify the icon size in absolute terms use LoadIconResourceSize.

These functions return an HICON. You can of course assign this to the Handle property of a TIcon instance. More likely you will wish to add to an image list. The easiest way to do this is to call ImageList_AddIcon passing the Handle of the TImageList instance.

Note 1: Older versions of Delphi do not have LoadIconWithScaleDown defined in CommCtrl. For such Delphi versions you need to call GetProcAddress to load it. Note that this is a Unicode only API and so you must send it a PWideChar for the resource name. Like this: LoadIconWithScaleDown(..., PWideChar(WideString(ResourceName)),...).

Note 2: The definition of LoadIconWithScaleDown is flawed. If you call it after the common controls library has been initialised then you will have no problems. However, if you call the function early on in the life of your process then LoadIconWithScaleDown can fail. I have just submitted QC#101000 to report this problem. Again, if you are afflicted by this then you have to call GetProcAddress yourself.

这篇关于如何从资源加载图标而不会遇到混叠?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆