2013-03-02 27 views
5

Tôi có một tệp biểu tượng cụ thể, được tạo thành từ hình ảnh nén PNG và khi tôi cố gắng tải nó và thêm vào một TImageList, ngoại lệ Out of system resources được nêu ra.Tại sao tải biểu tượng định dạng hình ảnh PNG gây ra ngoại lệ "Hết tài nguyên hệ thống"?

Các tập tin biểu tượng là ở đây: https://www.dropbox.com/s/toll6jhlwv3cpq0/icon.ico?m

Đây là mã, mà làm việc với loại phổ biến của các biểu tượng, nhưng không thành công với các biểu tượng hình ảnh 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; 

Tại sao định dạng biểu tượng hình ảnh PNG thất bại tải với ngoại lệ Out of system resources? Cách thêm loại biểu tượng này vào danh sách hình ảnh?

+0

Có bao nhiêu hình ảnh khác nằm trong danh sách hình ảnh trước khi thêm biểu tượng này? –

+1

@Ken, bạn có thể mô phỏng điều này ngay cả khi danh sách hình ảnh trống. Tôi đoán đó là vì biểu tượng được thêm vào là biểu tượng nhiều kích thước. – TLama

+0

@TLama - Thật vậy. Hình ảnh đầu tiên có thể được thêm vào mà không có vấn đề gì nếu được trích xuất. –

Trả lời

10

nguồn Vấn đề:

Thực tế, mà biểu tượng là một tập tin biểu tượng đa kích thước không quan trọng trong trường hợp này. Tiêu đề thông tin bitmap của biểu tượng được đọc nội bộ theo một cách khác. Biểu tượng của bạn là biểu tượng tệp định dạng PNG và biểu tượng không có cấu trúc tiêu đề thông tin bitmap. Lý do, tại sao bạn nhận được ngoại lệ Out of system resources, là do các thủ tục được sử dụng nội bộ được mong đợi từ biểu tượng có cấu trúc TBitmapInfoHeader và sau đó cố gắng tạo một bitmap tạm thời dựa trên thông tin tiêu đề này. Đối với biểu tượng của bạn được đọc như thế này:

enter image description here

Nếu bạn xem xét kỹ hơn về các giá trị tiêu đề, bạn tính toán rằng hệ thống sẽ cố gắng để tạo ra một bitmap đó sẽ là kích thước 169.478.669 * 218.103.808 pixel tại 21.060 B mỗi pixel, điều gì sẽ cần phải có ít nhất 778.5 EB (exabytes) bộ nhớ miễn phí :-)

Cách giải quyết:

Đó là tất nhiên không thể (tại thời điểm này :-) và ha trang chỉ vì các biểu tượng định dạng tệp PNG không có tiêu đề bitmap này, nhưng thay vào đó chứa trực tiếp hình ảnh PNG trên vị trí đó. Những gì bạn có thể làm để giải quyết vấn đề này là kiểm tra, nếu có PNG signature trên 8 byte đầu tiên của dữ liệu hình ảnh, thực tế sẽ kiểm tra xem có hình ảnh PNG hay không và xử lý dưới dạng hình ảnh PNG, nếu không hãy thử thêm biểu tượng theo cách thông thường thông qua đối tượng TIcon.

Trong mã sau, hàm ImageListAddIconEx lặp lại tất cả các biểu tượng trong tệp biểu tượng và khi có một biểu tượng khớp với thứ nguyên danh sách hình ảnh được xử lý. Trước tiên, quá trình xử lý sẽ kiểm tra 8 byte đó nếu có hình ảnh PNG trên vị trí bù đắp dữ liệu và nếu có, nó sẽ thêm hình ảnh PNG này vào danh sách hình ảnh. Nếu không, biểu tượng sẽ được thêm theo cách thông thường thông qua đối tượng TIcon. Hàm này trả về chỉ số của các biểu tượng được thêm vào trong danh sách hình ảnh nếu thành công, -1 khác:

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; 

Và việc sử dụng:

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; 

Kết luận:

Tôi muốn nếu Microsoft khuyến cáo định dạng biểu tượng PNG để sử dụng (được hỗ trợ từ Windows Vista), bạn có thể cập nhật thủ tục ReadIcon trong Graphics.pas để tính đến điều này.

cái gì đó để đọc:

+0

Có cách nào để kiểm tra xem một TIcon có phải là một đa giác không? – Casady

+0

Là ExtractIconEx() khả năng duy nhất để có được biểu tượng nhỏ? Vấn đề là không giống như trong ví dụ này, mã thực sự của tôi có TMemoryStream nơi dữ liệu biểu tượng được đặt, vì vậy tôi không thể sử dụng ExtractIconEx() mà không lưu TMemoryStream trước, điều này sẽ rất chậm với hàng nghìn biểu tượng. – Casady

+0

Tôi đã thử mã của bạn, và tôi nhận được Out of System Resources cũng nếu thêm IconSmall. Nó phải là một cái gì đó khác là vấn đề với biểu tượng này. – Casady

Các vấn đề liên quan