如何在不遭受别名的情况下从资源加载图标?

Dav*_*nan 28 windows delphi winapi

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

该应用程序具有高DPI感知能力,可根据主流字体缩放调整所有可视元素的大小.因此,例如,在100%字体缩放,96dpi时,工具栏图标大小为16px.在125%缩放,120dpi时,工具栏图标大小为20px.我需要能够加载大小为20px的图标而不会产生任何混叠效果.我怎样才能做到这一点?请注意,我想支持Windows 2000及更高版本.

Dav*_*nan 27

在Vista上,增加了许多新功能,使这项任务变得微不足道.这里最合适的功能是LoadIconWithScaleDown.

此功能将首先在图标文件中搜索具有完全相同大小的图标.如果未找到匹配项,则除非cx和cy都匹配标准图标大小-16,32,48或256像素之一,否则将选择下一个最大图标,然后缩小到所需大小.例如,如果callign应用程序请求x维度为40像素的图标,则使用48像素图标并缩小为40像素.相比之下,LoadImage函数选择32像素图标并将其缩放到40像素.

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

根据我的经验,此功能可以很好地进行缩放,结果显示没有锯齿现象.

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

  1. 检查资源中的可用图像,以查找尺寸小于所需图标大小的图像.
  2. 创建所需大小的新图标并将其初始化为完全透明.
  3. 将资源中较小的图标放在新(较大)图标的中心.

这意味着图标周围会有一个小的透明边框,但通常这个小到可以无关紧要.理想的选择是使用可以缩小的代码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.
Run Code Online (Sandbox Code Playgroud)

使用这些功能非常明显.他们假设资源与代码位于同一模块中.HMODULE如果您需要支持该级别的通用性,可以很容易地将代码推广到接收代码.

LoadIconResourceMetric如果要加载大小等于系统小图标或系统大图标的图标,请调用.该IconMetric参数应该是ICON_SMALLICON_BIG.对于工具栏,ICON_SMALL应使用菜单和通知图标.

如果您希望以绝对值使用指定图标大小LoadIconResourceSize.

这些函数返回一个HICON.您当然可以将其分配给实例的Handle属性TIcon.您更有可能希望添加到图像列表.要做到这一点,最简单的方法是调用ImageList_AddIcon传递Handle的的TImageList实例.

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

注2:定义LoadIconWithScaleDown存在缺陷.如果在初始化公共控件库之后调用它,那么您将没有任何问题.但是,如果您在流程的生命周期中尽早调用该函数,则LoadIconWithScaleDown可能会失败.我刚刚提交了QC#101000来报告此问题.再说一次,如果你受到这种折磨,那么你必须给GetProcAddress自己打电话.

  • 谢谢你分享这个.很有帮助. (7认同)
  • 感谢@David的兴趣.(我希望在http://stackoverflow.com/questions/6344366/messagedlg-shows-information-icon-instead-of-confirmation/6348558#6348558中解决丑陋的页脚图标.) (2认同)