2011-11-13 24 views
27

我有一個GUI應用程序,其中包括一些用於工具欄按鈕,菜單字形,通知圖標等的圖標。這些圖標作爲資源鏈接到應用程序,並提供各種不同的大小。通常,對於工具欄按鈕圖像,我可以使用16px,24px和32px版本。我的圖標是部分透明的32bpp。如何從資源加載圖標而不會遭受別名?

該應用程序具有較高的DPI感知能力,並根據流行的字體縮放比例調整所有視覺元素的大小。因此,例如,在100%的字體縮放比例下,96dpi,工具欄圖標大小爲16px。在125%縮放比例120dpi下,工具欄圖標大小爲20像素。我需要能夠加載大小爲20px的圖標,而不會出現任何別名效果。我怎樣才能做到這一點?請注意,我想支持Windows 2000及更高版本。

+6

如果有人錯過了,這個問題+答案是請求。 –

回答

27

在Vista上增加了許多新功能,使這項任務變得微不足道。這裏最適合的功能是LoadIconWithScaleDown

此功能將首先在圖標文件中搜索具有完全相同大小的圖標。如果找不到匹配項,則除非cx和cy匹配標準圖標大小之一(16,32,48或256像素),否則選擇下一個最大圖標,然後縮小到所需大小。例如,如果callign應用程序請求x尺寸爲40像素的圖標,則會使用48像素圖標並縮小至40像素。相反,LoadImage函數選擇32像素圖標並將其擴展到40像素。

如果該功能無法找到較大的圖標,則默認爲查找下一個最小圖標並將其縮放到所需大小的標準行爲。

在我的經驗中,這個函數做了很好的縮放工作,結果顯示沒有鋸齒跡象。

對於Windows的早期版本,據我所知,沒有任何單一功能可以充分執行此任務。從​​獲得的結果質量非常差。相反,我找到的最佳方法如下:

  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. 

使用這些函數是非常明顯的。他們假定資源與代碼位於同一模塊中。如果您需要支持這種通用性級別,則代碼很容易被推廣到接收HMODULE

如果您希望加載與系統小圖標或系統大圖標相等的圖標,請致電LoadIconResourceMetricIconMetric參數應該是ICON_SMALLICON_BIG。對於工具欄,菜單和通知圖標,應使用ICON_SMALL

如果您希望以絕對值指定圖標大小,請使用LoadIconResourceSize。這些函數返回HICON。您當然可以將其分配給TIcon實例的Handle屬性。更有可能你會希望添加到圖像列表。最簡單的方法是呼叫ImageList_AddIcon傳遞TImageList實例的Handle

注1:老版本的Delphi沒有在CommCtrl中定義的LoadIconWithScaleDown。對於這樣的Delphi版本,您需要撥打GetProcAddress加載它。請注意,這是一個僅適用於Unicode的API,因此您必須將其發送給PWideChar作爲資源名稱。像這樣:LoadIconWithScaleDown(..., PWideChar(WideString(ResourceName)),...)

注2:LoadIconWithScaleDown的定義存在缺陷。如果在公共控制庫被初始化後調用它,那麼你將沒有問題。但是,如果您在生命週期中儘早調用函數,那麼LoadIconWithScaleDown可能會失敗。我剛剛提交了QC#101000來報告此問題。同樣,如果您受此影響,那麼您必須自己撥打GetProcAddress

+7

感謝您分享此內容。很有幫助。 –

+0

有沒有辦法用例如(MakeIntResource)來調用'LoadIconResourceMetric' 'LoadIconResourceMetric(IDI_INFORMATION,ICON_SMALL)'?它是否適用於D <= 2007? –

+0

@Ulrich 1.對於序數,您只需重新設置它即可使用'PChar'而不是'string'參數。這會讓我更加一般我同意。 2.對於舊版本的Delphi,在CommCtrl中不包含'LoadIconWithScaleDown',你需要爲自己做LoadLibrary/GetProcAddress跳舞。 –