【问题标题】:Why does loading of a PNG image format icon cause the "Out of system resources" exception?为什么加载 PNG 图像格式图标会导致“系统资源不足”异常?
【发布时间】:2013-03-02 02:10:20
【问题描述】:

我有一个特定的图标文件,它由 PNG 压缩图像组成,当我尝试加载它并添加到 TImageList 时,会引发 Out 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;

为什么PNG图像图标格式无法加载Out of system resources异常?如何将这种图标添加到图像列表中?

【问题讨论】:

  • 在添加此图标之前,图像列表中还有多少其他图像?
  • @Ken,即使图像列表为空,您也可以模拟这个。我猜是因为添加的图标是多尺寸图标。
  • @TLama:谢谢。我现在无法从我所在的位置访问 DropBox。不过,如果问题确实提到了这两件事会有所帮助(它是一个多尺寸图标文件,即使是空的ImageList 也会发生这种情况)。我们不必为了获得问题的基本细节而从其他站点下载文件。 :-) 您应该将其发布为答案,因为 ImageLists 必须仅包含相同大小的图像。
  • @Ken,谢谢,但恐怕案件还没有结束,因为我试图从文件this way 中提取第一个图标(因为 OP 实际上使用内存流),但也有同样的问题。问题似乎出在多尺寸图标之外。
  • 该图标由PNG图像组成,内部程序不计入此格式。它只需要位图类型的图标。有必要读取图标标题并检查ICONDIRENTRY 图标条目是否包含在其dwImageOffset PNG 图像标题中。我马上回来带一些代码...

标签: delphi delphi-7


【解决方案1】:

问题来源:

在这种情况下,图标是多尺寸图标文件这一事实并不重要。图标的位图信息标头在内部以不同于应有的方式读取。您的图标是 PNG 格式的文件图标,并且没有位图信息标题结构。之所以会出现Out of system resources 异常,是因为内部使用的过程期望图标具有TBitmapInfoHeader 结构,然后尝试根据此标头信息创建一个临时位图。对于您的图标,它是这样写的:

如果您仔细查看标题值,您会计算出系统将尝试创建一个大小为 169478669 * 218103808 像素、每像素 21060 B 的位图,至少需要 778.5 EB (exabytes)可用内存:-)

解决方法:

这当然是不可能的(此时 :-) 只是因为 PNG 文件格式图标没有此位图标题,而是在该位置直接包含 PNG 图像。解决此问题的方法是检查图像数据的前 8 个字节上是否有 PNG signature,它实际上检查是否有 PNG 图像,如果有,则将其视为 PNG 图像,否则尝试添加该图标通过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 起支持),则可以更新 Graphics.pas 中的 ReadIcon 过程以考虑到这一点。

值得阅读的内容:

【讨论】:

  • 有没有办法检查 TIcon 是否是多图标?
  • 很高兴我能帮上忙!现在让我们希望 Embarcadero 在他们的Graphics 单元中实现类似的东西(到目前为止,即使在 Delphi XE3 中也没有实现)并且 StackOverflow 允许提问者购买啤酒:-)
  • @TLama:非常好! :-) 如果我还没有对您的第一个答案投赞成票,我会再次为此投赞成票。
  • 在帖子中修复(Data 变量已经是一个指针,我应该直接传递它)。但无论如何,您使用的是 Delphi 7,我不确定如何处理缺少的 TBitmap.AlphaFormat 属性,是否可以透明地添加图像。
  • 在 Delphi 中仍未修复。登录quality.embarcadero.com/browse/RSP-21318
猜你喜欢
  • 2017-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-27
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多