問題の原因:
この場合、アイコンがマルチサイズのアイコン ファイルであるという事実は重要ではありません。アイコンのビットマップ情報ヘッダーが、本来あるべき方法とは異なる方法で内部的に読み取られます。あなたのアイコンは 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
読むべきもの: