【Delphi】ICON图标文件解析
icon是一种图标格式,用于系统图标、软件图标等,这种图标扩展名为*.icon、*.ico。常见的软件或windows桌面上的那些图标一般都是ICON格式的。
ICON文件格式比较简单,包含文件头段、图像数据头段、图像数据段。
文件头:
文件头为6个字节,定义如下:
type ICONDIR = packed record idReserved: SmallInt; // Reserved idType: SmallInt; // Resource type idCount: SmallInt; // Image Count end; // 6 bytes
idCount标记了文件中包含的图像数量。
图像数据头段:
紧接着文件头的就是图像数据头段了,它存放着文件中每个图像宽度、高度、颜色数量、数据段的偏移等信息,大小为16 * idCount。它是一个数组,每项数据16字节,定义如下:
type ICONDIRENTRY = packed record bWidth: Byte; // Width of the image bHeight: Byte; // Height of the image (2 * Height) bColorCount: Byte; // Number of colors in image (0 when >= 8 bpp) bReserved: Byte; // Reserved wPlanes: SmallInt; // Color Planes (-> xHotspot [Cursor]) wBitCount: SmallInt; // Bits per pixel (-> yHotspot [Cursor]) dwBytesInRes: Integer; // How many bytes in this resource? dwImageOffset: Integer; // Where in the file is this image? end; // 16 bytes
读取完图像数据头段后,就可以知道文件中每个图标的大小,颜色位数了,同时直接根据数据段的偏移,读取图像数据段。图像数据的偏移是从文件最开始算起的。
图像数据段:
图像数据为多个图像的DIB数据,根据数据头段的偏移来定位。定位后,读取BIH信息。从BIH信息中,判断颜色位数大于8位的,记取XOR调色盘数据(小于8位的不存在XOR调色盘,只包含有一个MASK调色盘)。读取完XOR调色盘后,初始化图像DIB头,然后在文件中读取DIB数据。
下面来看看实现的加载代码:(注意:本示例代码,只加载ICO文件中颜色位数最高,最大的一个)
相关定义:
type dibBPPCts = (bpp_01 = 1, bpp_04 = 4, bpp_08 = 8, bpp_16 = 16, bpp_24 = 24, bpp_32 = 32); DIBItem = packed record private function GetBPP: dibBPPCts; function GetBytesPerScanline: Integer; function GetHeight: Integer; function GetWidth: Integer; function GetSize: Integer; public m_uBIH: BITMAPINFOHEADER; m_hDC: Integer; m_hDIB: Integer; m_hOldDIB: Integer; m_lpBits: Pointer; m_lpBitsSize: Integer; function Create(NewWidth, NewHeight: Integer; NewBPP: dibBPPCts): Boolean; procedure Free(); procedure Clone(var toDIB: DIBItem); procedure GetPalette(var Palette: TBytes); procedure SetPalette(Palette: TBytes); function Stretch(dc: HDC; x, y, w, h, xSrc, ySrc, wSrc, hSrc: Integer; rop: Cardinal): Integer; function Stretch32(dc: HDC; BufferBits: Pointer; BytesPerRow, x, y, w, h, xSrc, ySrc, wSrc, hSrc: Integer): Integer; property Width: Integer read GetWidth; property Height: Integer read GetHeight; property BPP: dibBPPCts read GetBPP; property BytesPerScanline: Integer read GetBytesPerScanline; property Size: Integer read GetSize; end; DIBDATA = packed record XORDIB: DIBItem; // XOR DIB section ANDDIB: DIBItem; // AND DIB section end; type BITMAPINFO_001 = packed record bmiHeader: BITMAPINFOHEADER; bmiColors: array [0 .. 7] of Byte; end; BITMAPINFO_004 = packed record bmiHeader: BITMAPINFOHEADER; bmiColors: array [0 .. 63] of Byte; end; BITMAPINFO_008 = packed record bmiHeader: BITMAPINFOHEADER; bmiColors: array [0 .. 1023] of Byte; end; BITMAPINFO_RGB = packed record bmiHeader: BITMAPINFOHEADER; end; type icoBPPCts = (Colors_002 = 1, Colors_016 = 4, Colors_256 = 8, Color_True = 24, Color_ARGB = 32);
加载代码:
procedure TYxdIcon.LoadFromStream(Stream: TStream); var nImg: Integer; begin // Get icon header Fillchar(m_uDir, sizeof(m_uDir), 0); Stream.Read(m_uDir, sizeof(m_uDir)); // Get icon entries SetLength(m_uDirEntry, m_uDir.idCount); Stream.Read(m_uDirEntry[0], sizeof(ICONDIRENTRY) * m_uDir.idCount); // Initialize arrays and monochrome palette //SetLength(m_OrderKey, m_uDir.idCount); //SetLength(m_uDIBData, m_uDir.idCount); //SetLength(m_DIBData, m_uDir.idCount); SetLength(aPalAND, 8); FillChar(aPalAND[0], SizeOf(aPalAND), 0); aPalAND[4] := $FF; aPalAND[5] := $FF; aPalAND[6] := $FF; // Get images nMaxIndex := -1; nMaxW := 0; for nImg := 0 to m_uDir.idCount - 1 do begin if (m_uDirEntry[nImg].bWidth > nMaxW) or ((m_uDirEntry[nImg].bWidth = nMaxW) and (m_uDirEntry[nImg].bColorCount = 0)) then begin nMaxW := m_uDirEntry[nImg].bWidth; nMaxIndex := nImg; end; end; if nMaxIndex > -1 then begin // Move to begin of image data Stream.Position := m_uDirEntry[nMaxIndex].dwImageOffset; // Load BITMAPINFOHEADER Stream.Read(uBIH, SizeOf(uBIH)); // Load XOR palette [?] (<= 8 bpp) if uBIH.biBitCount <= 8 then begin SetLength(aPalXOR, 4 * Trunc(Power(2, uBIH.biBitCount))); Stream.Read(aPalXOR[0], Length(aPalXOR)); end; // Inititalize XOR DIB FillChar(m_uDIBData, SizeOf(m_uDIBData), 0); m_uDIBData.XORDIB.Create(uBIH.biWidth, uBIH.biHeight div 2, dibBPPCts(ubih.biBitCount)); if uBIH.biBitCount <= 8 then m_uDIBData.XORDIB.SetPalette(aPalXOR); // Inititalize AND DIB m_uDIBData.ANDDIB.Create(uBIH.biWidth, uBIH.biHeight div 2, bpp_01); m_uDIBData.ANDDIB.SetPalette(aPalAND); // Read DIB bits m_uDIBData.XORDIB.m_lpBits := GetMemory(m_uDIBData.XORDIB.Size); m_uDIBData.ANDDIB.m_lpBits := GetMemory(m_uDIBData.ANDDIB.Size); Stream.Read(m_uDIBData.XORDIB.m_lpBits^, m_uDIBData.XORDIB.Size); Stream.Read(m_uDIBData.ANDDIB.m_lpBits^, m_uDIBData.ANDDIB.Size); m_OrderKey := IntToHex(uBIH.biWidth, 3) + IntToHex(uBIH.biHeight div 2, 3) + IntToHex(uBIH.biBitCount, 2); end; Changed(Self); end;
DIBItem 就是ICON文件中一个图标的图像数据,代码如下:
{ DIBItem } procedure DIBItem.Clone(var toDIB: DIBItem); var aPal: TBytes; begin if m_hDIB <> 0 then begin toDIB.Create(m_uBIH.biWidth, m_uBIH.biHeight, dibBPPCts(m_uBIH.biBitCount)); if m_lpBits <> nil then begin toDIB.m_lpBits := GetMemory(Size); CopyMemory(toDIB.m_lpBits, m_lpBits, Size); end else toDIB.m_lpBits := nil; if (m_uBIH.biBitCount <= 8) then begin SetLength(aPal, 4 * Trunc(Power(2, m_uBIH.biBitCount)) - 1); GetPalette(aPal); toDIB.SetPalette(aPal); end; end; end; function DIBItem.Create(NewWidth, NewHeight: Integer; NewBPP: dibBPPCts): Boolean; var BI_001: BITMAPINFO_001; BI_004: BITMAPINFO_004; BI_008: BITMAPINFO_008; BI_RGB: BITMAPINFO_RGB; begin Free(); // Define DIB header m_uBIH.biSize := SizeOf(m_uBIH); m_uBIH.biPlanes := 1; m_uBIH.biBitCount := Integer(NewBPP); m_uBIH.biWidth := NewWidth; m_uBIH.biHeight := NewHeight; m_uBIH.biSizeImage := 4 * ((m_uBIH.biWidth * m_uBIH.biBitCount + 31) div 32) * m_uBIH.biHeight; case NewBPP of bpp_01: BI_001.bmiHeader := m_uBIH; bpp_04: BI_004.bmiHeader := m_uBIH; bpp_08: BI_008.bmiHeader := m_uBIH; else BI_RGB.bmiHeader := m_uBIH end; // Create DIB and select into a DC m_hDC := CreateCompatibleDC(0); if m_hDC <> 0 then begin case NewBPP of bpp_01: m_hDIB := CreateDIBSection(m_hDC, pBitmapInfo(@BI_001)^, DIB_RGB_COLORS, m_lpBits, 0, 0); bpp_04: m_hDIB := CreateDIBSection(m_hDC, pBitmapInfo(@BI_004)^, DIB_RGB_COLORS, m_lpBits, 0, 0); bpp_08: m_hDIB := CreateDIBSection(m_hDC, pBitmapInfo(@BI_008)^, DIB_RGB_COLORS, m_lpBits, 0, 0); else m_hDIB := CreateDIBSection(m_hDC, pBitmapInfo(@BI_RGB)^, DIB_RGB_COLORS, m_lpBits, 0, 0); end; if m_hDIB <> 0 then m_hOldDIB := SelectObject(m_hDC, m_hDIB) else Free; end; Result := m_hDIB <> 0; end; procedure DIBItem.Free; begin if m_hDC <> 0 then begin if m_hDIB <> 0 then begin SelectObject(m_hDC, m_hOldDIB); DeleteObject(m_hDIB); end; DeleteDC(m_hDC); end; if m_lpBits <> nil then begin FreeMemory(m_lpBits); m_lpBits := nil; end; FillChar(m_uBIH, SizeOf(m_uBIH), 0); m_hDC := 0; m_hDIB := 0; m_hOldDIB := 0; end; function DIBItem.GetBPP: dibBPPCts; begin Result := dibBPPCts(m_uBIH.biBitCount); end; function DIBItem.GetBytesPerScanline: Integer; begin Result := ((m_uBIH.biWidth * m_uBIH.biBitCount + 31) div 32) * 4; end; function DIBItem.GetHeight: Integer; begin Result := m_uBIH.biHeight; end; procedure DIBItem.GetPalette(var Palette: TBytes); begin if m_hDIB <> 0 then GetDIBColorTable(m_hDC, 0, Trunc(Power(2, m_uBIH.biBitCount)), Palette[Low(Palette)]); end; function DIBItem.GetSize: Integer; begin Result := m_uBIH.biSizeImage; end; function DIBItem.GetWidth: Integer; begin Result := m_uBIH.biWidth; end; procedure DIBItem.SetPalette(Palette: TBytes); begin SetDIBColorTable(m_hDC, 0, (High(Palette) - Low(Palette) + 1) div 4, Palette[Low(Palette)]) end; function DIBItem.Stretch(dc: HDC; x, y, w, h, xSrc, ySrc, wSrc, hSrc: Integer; rop: Cardinal): Integer; var b001: BITMAPINFO_001; b004: BITMAPINFO_004; b008: BITMAPINFO_008; brgb: BITMAPINFO_RGB; iLen: Integer; lOldMode: Integer; begin Result := 0; if m_hDIB = 0 then Exit; lOldMode := SetStretchBltMode(dc, COLORONCOLOR); iLen := Trunc(Power(2, m_uBIH.biBitCount)); case dibBPPCts(m_uBIH.biBitCount) of bpp_01: begin b001.bmiHeader := m_uBIH; GetDIBColorTable(m_hDC, 0, iLen, b001.bmiColors[0]); StretchDIBits(dc, x, y, w, h, xSrc, ySrc, wsrc, hsrc, m_lpBits, pBitmapinfo(@b001)^, DIB_RGB_COLORS, rop); end; bpp_04: begin b004.bmiHeader := m_uBIH; GetDIBColorTable(m_hDC, 0, iLen, b004.bmiColors[0]); StretchDIBits(dc, x, y, w, h, xSrc, ySrc, wsrc, hsrc, m_lpBits, pBitmapinfo(@b004)^, DIB_RGB_COLORS, rop); end; bpp_08: begin b008.bmiHeader := m_uBIH; GetDIBColorTable(m_hDC, 0, iLen, b008.bmiColors[0]); StretchDIBits(dc, x, y, w, h, xSrc, ySrc, wsrc, hsrc, m_lpBits, pBitmapinfo(@b008)^, DIB_RGB_COLORS, rop); end; else begin brgb.bmiHeader := m_uBIH; StretchDIBits(dc, x, y, w, h, xSrc, ySrc, wsrc, hsrc, m_lpBits, pBitmapinfo(@brgb)^, DIB_RGB_COLORS, rop); end; end; SetStretchBltMode(dc, lOldMode); Result := 1; end; function DIBItem.Stretch32(dc: HDC; BufferBits: Pointer; BytesPerRow, x, y, w, h, xSrc, ySrc, wSrc, hSrc: Integer): Integer; var i, i2, j, j2: Integer; Stretch: Boolean; FactorX, FactorY: Double; ABytesPerScanline: Integer; AlphaSource, ImageData: pPixelLine; begin Result := 0; if (m_hDIB = 0) or (m_lpBits = nil) then Exit; Stretch := (W <> wSrc) or (H <> hSrc); if Stretch then FactorX := W / wSrc else FactorX := 1; if Stretch then FactorY := H / hSrc else FactorY := 1; AlphaSource := m_lpBits; ABytesPerScanline := BytesPerScanline; PByte(ImageData) := PByte(Integer(BufferBits) + BytesPerRow * (H - 1)); BufferBits := ImageData; if m_uBIH.biBitCount = 32 then begin FOR j := 1 TO H DO begin FOR i := 0 TO W - 1 DO begin if Stretch then i2 := trunc(i / FactorX) else i2 := i; if (AlphaSource[i2].rgbReserved <> 0) then begin if (AlphaSource[i2].rgbReserved = 255) then begin ImageData[i] := AlphaSource[i2]; end else with ImageData[i] do begin rgbRed := ($7F + AlphaSource[i2].rgbRed * AlphaSource[i2].rgbReserved + rgbRed * (not AlphaSource[i2].rgbReserved)) div $FF; rgbGreen := ($7F + AlphaSource[i2].rgbGreen * AlphaSource[i2].rgbReserved + rgbGreen * (not AlphaSource[i2].rgbReserved)) div $FF; rgbBlue := ($7F + AlphaSource[i2].rgbBlue * AlphaSource[i2].rgbReserved + rgbBlue * (not AlphaSource[i2].rgbReserved)) div $FF; rgbReserved := not (($7F + (not rgbReserved) * (not AlphaSource[i2].rgbReserved)) div $FF); end; end; end; {Move pointers} PByte(ImageData) := PByte(Integer(BufferBits) - BytesPerRow * j); if Stretch then j2 := trunc(j / FactorY) else j2 := j; PByte(AlphaSource) := PByte(m_lpBits) + ABytesPerScanline * j2; end end; Result := 1; end;
ICON的显示:
本示例中的TYxdIcon继承自TIcon,本身就是一个Graphic了,所以可以直接重载Draw实现显示。
type TYxdIcon = class(TIcon) private m_uDir: ICONDIR; // Icon file header m_uDirEntry: array of ICONDIRENTRY; // Icon image headers m_OrderKey: string; // Image format key aPalXOR: TBytes; aPalAND: TBytes; nMaxW: Byte; nMaxIndex: Integer; uBIH: BITMAPINFOHEADER; BufferDC: HDC; OldBufferBitmap, BufferBitmap: HBitmap; LastW, LastH: Integer; FUseBuffer: Boolean; protected m_uDIBData: DIBDATA; // Icon data (DIBs) function GetEmpty: Boolean; override; function GetHeight: Integer; override; function GetWidth: Integer; override; procedure Draw(ACanvas: TCanvas; const Rect: TRect); override; function GetSupportsPartialTransparency: Boolean; override; public constructor Create; override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; procedure LoadFromStream(Stream: TStream); override; procedure SaveToStream(Stream: TStream); override; property UseBuffer: Boolean read FUseBuffer write FUseBuffer; end;
procedure TYxdIcon.Draw(ACanvas: TCanvas; const Rect: TRect); var BitmapInfo: TBitmapInfo; BufferBits, Buf: Pointer; AlphaSource, ImageData: pPixelLine; W, H: Integer; BytesPerRow: Integer; I, J: Integer; begin W := Rect.Right - Rect.Left; H := Rect.Bottom - Rect.Top; BitmapInfo := GetBitmapInfoHeader(W, H); BufferDC := CreateCompatibleDC(0); if (BufferDC = 0) then Exit; BytesPerRow := (((BitmapInfo.bmiHeader.biBitCount * W) + 31) and not 31) div 8; BufferBitmap := CreateDIBSection(BufferDC, pBitmapInfo(@BitmapInfo)^, DIB_RGB_COLORS, BufferBits, 0, 0); OldBufferBitmap := SelectObject(BufferDC, BufferBitmap); BitBlt(BufferDC, 0, 0, W, H, ACanvas.Handle, Rect.Left, Rect.Top, SRCCOPY); if m_uDIBData.XORDIB.BPP = bpp_32 then begin m_uDIBData.XORDIB.Stretch32(BufferDC, BufferBits, BytesPerRow, Rect.Left, Rect.Top, W, H, 0, 0, m_uDIBData.XORDIB.Width, m_uDIBData.XORDIB.Height); end else begin // 画 Mask 层 m_uDIBData.ANDDIB.Stretch(BufferDC, Rect.Left, Rect.Top, W, H, 0, 0, m_uDIBData.XORDIB.Width, m_uDIBData.XORDIB.Height, SRCCOPY); // 保存透明区域信息 Buf := GetMemory(BytesPerRow*H); CopyMemory(Buf, BufferBits, BytesPerRow*H); // 画实际图像 m_uDIBData.XORDIB.Stretch(BufferDC, Rect.Left, Rect.Top, W, H, 0, 0, m_uDIBData.XORDIB.Width, m_uDIBData.XORDIB.Height, SRCCOPY); // 应用 Mask PByte(ImageData) := PByte(BufferBits); AlphaSource := Buf; FOR j := 1 TO H DO begin FOR i := 0 TO W - 1 DO begin if (AlphaSource[i].rgbBlue = 0) and (AlphaSource[i].rgbGreen = 0) and (AlphaSource[i].rgbRed = 0) then begin ImageData[i].rgbReserved := $ff; end else ImageData[i].rgbReserved := 0; end; {Move pointers} inc(PByte(AlphaSource), BytesPerRow); inc(PByte(ImageData), BytesPerRow); end; FreeMemory(Buf); end; TYxdCanvas(ACanvas).RequiredState([csHandleValid]); BitBlt(ACanvas.Handle, Rect.Left, Rect.Top, W, H, BufferDC, 0, 0, SRCCOPY); SelectObject(BufferDC, OldBufferBitmap); DeleteObject(BufferBitmap); DeleteDC(BufferDC); BufferBitmap := 0; end;
显示中主要特别处理的地方就是区分是否为32位的带透明通道图标。不带透明通道的,使用MASK层进行透明处理。