2013-03-02 52 views
5

我有一個特定的圖標文件,它由PNG壓縮圖像組成,當我嘗試加載它並添加到TImageList時,會引發Out of system resources異常。爲什麼加載PNG圖像格式圖標會導致「超出系統資源」異常?

圖標文件是在這裏:https://www.dropbox.com/s/toll6jhlwv3cpq0/icon.ico?m

下面是代碼,它與普通型圖標的作品,但無法與PNG圖像圖標:

procedure TForm1.Button1Click(Sender: TObject); 
var 
    Icon: TIcon; 
begin 
    try 
    Icon := TIcon.Create; 
    Icon.LoadFromFile('icon.ico'); 
    ImageList1.AddIcon(Icon); 
    Caption := IntToStr(ImageList1.Count); 
    finally 
    Icon.Free; 
    end; 
end; 

爲什麼PNG圖像圖標格式失敗加載Out of system resources異常?如何將這種圖標添加到圖像列表?

+0

多少其他圖像在圖像列表中添加該圖標之前? – 2013-03-02 02:14:15

+1

@Ken,即使圖像列表爲空,也可以模擬這個。我想這是因爲添加的圖標是多尺寸的圖標。 – TLama 2013-03-02 02:19:43

+0

@TLama - 確實。如果提取第一個圖像可以添加沒有問題。 – 2013-03-02 02:24:06

回答

10

問題來源:

事實上,該圖標是一個多尺寸圖標文件並不在這種情況下,無所謂。圖標的位圖信息頭在內部以不同於應該的方式讀取。您的圖標是PNG格式的文件圖標,並且它們沒有位圖信息標題結構。之所以出現Out of system resources例外,是因爲內部使用的程序期望圖標具有TBitmapInfoHeader結構,然後嘗試根據此標頭信息創建臨時位圖。爲了您的圖標,它是這樣寫的:

enter image description here

如果你看一看上的標頭值越接近,你算算,該系統將嘗試創建它的尺寸就達到169478669個* 218103808個像素的位圖每像素21060 B,就需要有空閒內存至少778.5 EB (exabytes) :-)什麼

解決方法:

這當然是不可能的(此時:-)和HA只是因爲PNG文件格式圖標沒有這個位圖標題,而是直接包含該位置上的PNG圖像。你可以做的解決方法是檢查圖像數據的前8個字節是否存在PNG signature,它實際上檢查是否存在PNG圖像,如果是,則將其視爲PNG圖像,否則嘗試添加圖標以通常的方式通過TIcon對象。

在下面的代碼中,ImageListAddIconEx函數會迭代圖標文件中的所有圖標,並且當存在與處理圖像列表尺寸相匹配的圖標時。如果在數據偏移位置上存在PNG圖像,則處理首先檢查這8個字節,如果是,則將該PNG圖像添加到圖像列表。如果不是,則通過TIcon對象以通用方式添加圖標。該函數返回的圖像列表添加圖標的指標,如果成功,否則爲-1:

uses 
    PNGImage; 

type 
    TIconDirEntry = packed record 
    bWidth: Byte;   // image width, in pixels 
    bHeight: Byte;   // image height, in pixels 
    bColorCount: Byte;  // number of colors in the image (0 if >= 8bpp) 
    bReserved: Byte;  // reserved (must be 0) 
    wPlanes: Word;   // color planes 
    wBitCount: Word;  // bits per pixel 
    dwBytesInRes: DWORD; // image data size 
    dwImageOffset: DWORD; // image data offset 
    end; 

    TIconDir = packed record 
    idReserved: Word;  // reserved (must be 0) 
    idType: Word;   // resource type (1 for icons) 
    idCount: Word;   // image count 
    idEntries: array[0..255] of TIconDirEntry; 
    end; 
    PIconDir = ^TIconDir; 

function ImageListAddIconEx(AImageList: TCustomImageList; 
    AIconStream: TMemoryStream): Integer; 
var 
    I: Integer; 
    Data: PByte; 
    Icon: TIcon; 
    IconHeader: PIconDir; 
    Bitmap: TBitmap; 
    PNGImage: TPNGImage; 
    PNGStream: TMemoryStream; 
const 
    PNGSignature: array[0..7] of Byte = ($89, $50, $4E, $47, $0D, $0A, $1A, $0A); 
begin 
    // initialize result to -1 
    Result := -1; 
    // point to the icon header 
    IconHeader := AIconStream.Memory; 
    // iterate all the icons in the icon file 
    for I := 0 to IconHeader.idCount - 1 do 
    begin 
    // if the icon dimensions matches to the image list, then... 
    if (IconHeader.idEntries[I].bWidth = AImageList.Width) and 
     (IconHeader.idEntries[I].bHeight = AImageList.Height) then 
    begin 
     // point to the stream beginning 
     Data := AIconStream.Memory; 
     // point with the Data pointer to the current icon image data 
     Inc(Data, IconHeader.idEntries[I].dwImageOffset); 
     // check if the first 8 bytes are PNG image signature; if so, then... 
     if CompareMem(Data, @PNGSignature[0], 8) then 
     begin 
     Bitmap := TBitmap.Create; 
     try 
      PNGImage := TPNGImage.Create; 
      try 
      PNGStream := TMemoryStream.Create; 
      try 
       // set the icon stream position to the current icon data offset 
       AIconStream.Position := IconHeader.idEntries[I].dwImageOffset; 
       // copy the whole PNG image from icon data to a temporary stream 
       PNGStream.CopyFrom(AIconStream, 
       IconHeader.idEntries[I].dwBytesInRes); 
       // reset the temporary stream position to the beginning 
       PNGStream.Position := 0; 
       // load the temporary stream data to a temporary TPNGImage object 
       PNGImage.LoadFromStream(PNGStream); 
      finally 
       PNGStream.Free; 
      end; 
      // assign temporary TPNGImage object to a temporary TBitmap object 
      Bitmap.Assign(PNGImage); 
      finally 
      PNGImage.Free; 
      end; 
      // to properly add the bitmap to the image list set the AlphaFormat 
      // to afIgnored, see e.g. http://stackoverflow.com/a/4618630/960757 
      // if you don't have TBitmap.AlphaFormat property available, simply 
      // comment out the following line 
      Bitmap.AlphaFormat := afIgnored; 
      // and finally add the temporary TBitmap object to the image list 
      Result := AImageList.Add(Bitmap, nil); 
     finally 
      Bitmap.Free; 
     end; 
     end 
     // the icon is not PNG type icon, so load it to a TIcon object 
     else 
     begin 
     // reset the position of the input stream 
     AIconStream.Position := 0; 
     // load the icon and add it to the image list in a common way 
     Icon := TIcon.Create; 
     try 
      Icon.LoadFromStream(AIconStream); 
      Result := AImageList.AddIcon(Icon); 
     finally 
      Icon.Free; 
     end; 
     end; 
     // break the loop to exit the function 
     Break; 
    end; 
    end; 
end; 

和使用:

procedure TForm1.Button1Click(Sender: TObject); 
var 
    Index: Integer; 
    Stream: TMemoryStream; 
begin 
    Stream := TMemoryStream.Create; 
    try 
    Stream.LoadFromFile('d:\Icon.ico'); 
    Index := ImageListAddIconEx(ImageList1, Stream); 
    if (Index <> -1) then 
     ImageList1.Draw(Canvas, 8, 8, Index); 
    finally 
    Stream.Free; 
    end; 
end; 

結論:

我會如果Microsoft建議使用PNG圖標格式(自Windows Vista以後支持),那麼更新Graphics.pas中的ReadIcon過程就可以考慮到這一點。

東西可以讀:

+0

有沒有辦法檢查TIcon是否是multiicon? – Casady 2013-03-02 03:14:30

+0

是ExtractIconEx()獲取小圖標的唯一可能性嗎?問題是,與本例中不同,我的實際代碼中有圖標數據所在的TMemoryStream,所以我不能先使用ExtractIconEx(),而不先保存TMemoryStream,而使用數千個圖標會很慢。 – Casady 2013-03-02 03:22:30

+0

無論如何,我已經嘗試了您的代碼,並且如果添加IconSmall,也會退出系統資源。這個圖標一定是其他問題。 – Casady 2013-03-02 11:17:17