Delphi 为什么加载PNG图像格式图标会导致;“系统外资源”;例外?
我有一个特定的图标文件,它由PNG压缩图像组成,当我尝试加载它并添加到Delphi 为什么加载PNG图像格式图标会导致;“系统外资源”;例外?,delphi,delphi-7,Delphi,Delphi 7,我有一个特定的图标文件,它由PNG压缩图像组成,当我尝试加载它并添加到TImageList时,会引发系统外资源异常 图标文件如下所示: 以下代码适用于常见类型的图标,但无法使用PNG图像图标: procedure TForm1.Button1Click(Sender: TObject); var Icon: TIcon; begin try Icon := TIcon.Create; Icon.LoadFromFile('icon.ico'); ImageList
TImageList
时,会引发系统外资源
异常
图标文件如下所示:
以下代码适用于常见类型的图标,但无法使用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图像图标格式无法加载,系统外资源出现异常?如何将此类图标添加到图像列表?问题来源:
事实上,在这种情况下,图标是一个多大小的图标文件并不重要。图标的位图信息标题的内部读取方式与应该的不同。您的图标是PNG格式的文件图标,没有位图信息头结构。您从系统资源中获取异常的原因是,内部使用的过程希望from icon具有结构,然后尝试基于此标题信息创建临时位图。对于您的图标,其内容如下:
如果仔细查看标题值,您会计算出系统将尝试创建一个位图,该位图的大小为169478669*218103808像素,每像素21060 B,那么至少需要多少可用内存:-)
解决方法:
这当然是不可能的(此时:-),这仅仅是因为PNG文件格式图标没有这个位图头,而是直接在该位置包含PNG图像。解决这个问题的方法是检查图像数据的前8个字节上是否有图标,这实际上检查是否有PNG图像,如果有,将其视为PNG图像,否则尝试通过TIcon
对象以常见方式添加图标
在下面的代码中,imagelistadiconex
函数迭代图标文件中的所有图标,当有一个图标与图像列表维度匹配时,将对其进行处理。处理首先检查这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;
procedure TForm1.按钮1点击(发送方:TObject);
变量
索引:整数;
流:TMemoryStream;
开始
Stream:=TMemoryStream.Create;
尝试
Stream.LoadFromFile('d:\Icon.ico');
索引:=ImageListAddIconEx(ImageList1,流);
如果(索引-1),则
ImageList1.Draw(画布,8,8,索引);
最后
免费;
结束;
结束;
结论:
我想说,如果Microsoft建议使用PNG图标格式(自Windows Vista以来就受支持),则可以更新Graphics.pas
中的ReadIcon
过程来考虑这一点
要读的东西:
添加此图标之前,图像列表中还有多少其他图像?@Ken,即使图像列表为空,您也可以模拟此操作。我想这是因为添加的图标是一个多尺寸的图标。@TLama:谢谢。我现在无法访问DropBox。不过,如果问题真的提到了这两件事,那会有所帮助(这是一个多尺寸的图标文件,即使是空的ImageList
)。我们不必从其他网站下载文件来获取问题的基本细节。:-)您应该将此作为答案发布,因为ImageList必须只包含大小相同的图像。@Ken,谢谢,但我恐怕这个案例尚未结束,因为我已尝试从文件中提取第一个图标(因为OP实际上使用内存流),但也有同样的问题。问题似乎不仅仅在于多尺寸图标。该图标由PNG图像组成,内部过程不适用于此格式。它只需要位图类型的图标。有必要读取图标标题,并检查ICONDIRENTRY
图标条目是否包含在其dwImageOffset
PNG图像标题上。我马上回来,带上一些代码……有没有办法检查TIcon是否是多图标的?很高兴我能帮上忙!现在,让我们希望Embarcadero在其图形单元中实现类似的功能(到目前为止,甚至在Delphi XE3中都还没有实现),并且StackOverflow允许提问者购买啤酒:-)@TLama:非常好!:-)如果我没有投票支持你的第一个答案,我会再次投票支持这个。在post中修复(数据变量已经是指针,我应该直接传递它)。但无论如何,您使用的是Delphi 7,我不确定如何处理缺少的TBitmap.AlphaFormat
属性,如果可以透明地添加图像,仍然无法在Delphi中修复。登录