10

PNG 圧縮画像から構成された特定のアイコン ファイルがあり、それを読み込んで に追加しようとするとTImageListOut of system resources例外が発生します。

アイコン ファイルはこちら: 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;

Out of system resourcesPNG 画像アイコン形式が例外でロードに失敗するのはなぜですか? この種のアイコンを画像リストに追加するにはどうすればよいですか?

4

1 に答える 1

12

問題の原因:

この場合、アイコンがマルチサイズのアイコン ファイルであるという事実は重要ではありません。アイコンのビットマップ情報ヘッダーが、本来あるべき方法とは異なる方法で内部的に読み取られます。あなたのアイコンは PNG 形式のファイル アイコンであり、それらにはビットマップ情報ヘッダー構造がありません。例外が発生する理由Out of system resourcesは、内部的に使用されるプロシージャがアイコンからTBitmapInfoHeader構造を持っていることを期待し、このヘッダー情報に基づいて一時的なビットマップを作成しようとするためです。あなたのアイコンの場合、次のように読み取られました。

ここに画像の説明を入力

ヘッダー値を詳しく見てみると、システムがサイズ 169478669 * 218103808 ピクセル、ピクセルあたり 21060 B のビットマップを作成しようとすることが計算されます。これには、少なくとも778.5 EB (exabytes)空きメモリが必要です:-)

回避策:

もちろん、これは不可能です (現時点では :-)。これは、PNG ファイル形式のアイコンにこのビットマップ ヘッダーがなく、代わりにその位置に直接 PNG 画像が含まれているために発生します。これを回避するためにできることはPNG signature、画像データの最初の 8 バイトに .TIconオブジェクトを通る一般的な方法。

次のコードでは、ImageListAddIconEx関数はアイコン ファイル内のすべてのアイコンを反復処理し、イメージ リストのサイズに一致するアイコンがあれば処理されます。処理は最初にこれらの 8 バイトをチェックして、データ オフセット位置に PNG 画像があるかどうかを確認し、ある場合は、この 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 以降でサポートされています)、これを考慮してReadIcon手順を更新しても問題ありません。Graphics.pas

読むべきもの:

于 2013-03-02T02:38:58.153 に答える