tagger tag windows delphi winapi

windows - tag - ¿Cómo cargo iconos desde un recurso sin sufrir aliasing?



tag folders (1)

Tengo una aplicación de GUI que incluye una serie de iconos utilizados para botones de la barra de herramientas, glifos de menú, iconos de notificación, etc. Estos iconos están vinculados a la aplicación como recursos y están disponibles una variedad de tamaños diferentes. Normalmente, para las imágenes del botón de la barra de herramientas, tengo disponibles versiones de 16px, 24px y 32px. Mis íconos son 32bpp con transparencia parcial.

La aplicación tiene un alto reconocimiento de DPI y ajusta el tamaño de todos los elementos visuales de acuerdo con la escala de la fuente predominante. Por lo tanto, por ejemplo, al escalar 100% de fuente, 96 ppp, el tamaño del icono de la barra de herramientas es 16px. Con una escala de 125%, 120 ppp, el tamaño del icono de la barra de herramientas es 20px. Necesito poder cargar un icono de tamaño 20px sin ningún efecto de aliasing. ¿Cómo puedo hacer esto? Tenga en cuenta que me gustaría apoyar Windows 2000 y versiones posteriores.


En Vista y hasta se agregaron una serie de funciones nuevas que hacen que esta tarea sea trivial. La función más adecuada aquí es LoadIconWithScaleDown .

Esta función buscará primero en el archivo de icono un ícono que tenga exactamente el mismo tamaño. Si no se encuentra una coincidencia, a menos que tanto cx como cy coincidan con uno de los tamaños de icono estándar: 16, 32, 48 o 256 píxeles, se selecciona el siguiente ícono más grande y luego se reduce al tamaño deseado. Por ejemplo, si la aplicación Callign solicita un icono con una dimensión x de 40 píxeles, el icono de 48 píxeles se usa y se reduce a 40 píxeles. Por el contrario, la función LoadImage selecciona el icono de 32 píxeles y lo escala hasta 40 píxeles.

Si la función no puede ubicar un ícono más grande, tiene el comportamiento estándar de buscar el siguiente ícono más pequeño y escalarlo al tamaño deseado.

En mi experiencia, esta función hace un excelente trabajo de escalado y los resultados no muestran signos de aliasing.

Para las versiones anteriores de Windows, a mi leal saber y entender, no hay ninguna función que pueda realizar esta tarea adecuadamente. Los resultados obtenidos de LoadImage son de muy mala calidad. En cambio, el mejor enfoque que he encontrado es el siguiente:

  1. Examine las imágenes disponibles en el recurso para encontrar la imagen con el tamaño más grande que es menor que el tamaño del icono deseado.
  2. Cree un nuevo ícono del tamaño deseado e inicialícelo para que sea completamente transparente.
  3. Coloque el ícono más pequeño del recurso en el centro del ícono nuevo (más grande).

Esto significa que habrá un pequeño borde transparente alrededor del ícono, pero generalmente esto es lo suficientemente pequeño para ser insignificante. La opción ideal sería utilizar un código que pudiera reducirse al igual que LoadIconWithScaleDown , pero no es trivial de escribir.

Entonces, sin más preámbulos aquí está el código que uso.

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.

Usar estas funciones es bastante obvio. Suponen que el recurso se encuentra en el mismo módulo que el código. El código podría generalizarse fácilmente para recibir un HMODULE en caso de que necesite soporte para ese nivel de generalidad.

Llame a LoadIconResourceMetric si desea cargar iconos de tamaño igual al icono pequeño del sistema o al icono grande del sistema. El parámetro IconMetric debe ser ICON_SMALL o ICON_BIG . Para barras de herramientas, menús e iconos de notificación, se debe usar ICON_SMALL .

Si desea especificar el tamaño del icono en términos absolutos, use LoadIconResourceSize .

Estas funciones devuelven un HICON . Por supuesto, puede asignar esto a la propiedad Handle de una instancia de TIcon . Es más probable que desee agregar a una lista de imágenes. La forma más sencilla de hacerlo es llamar a ImageList_AddIcon pasando el Handle de la instancia TImageList .

Nota 1: las versiones anteriores de Delphi no tienen definido CommCtrl en CommCtrl . Para tales versiones de Delphi necesita llamar a GetProcAddress para cargarlo. Tenga en cuenta que esta es una API única de Unicode, por lo que debe enviarle un PWideChar para el nombre del recurso. De esta manera: LoadIconWithScaleDown(..., PWideChar(WideString(ResourceName)),...) .

Nota 2: La definición de LoadIconWithScaleDown es defectuosa. Si lo llama después de que se haya inicializado la biblioteca de controles comunes, entonces no tendrá problemas. Sin embargo, si llama a la función desde el principio en la vida de su proceso, entonces LoadIconWithScaleDown puede fallar. Acabo de presentar QC#101000 para informar este problema. Nuevamente, si está afligido por esto, entonces tiene que llamar a GetProcAddress usted mismo.