Skip to content

Commit 79f5387

Browse files
JimBobSquarePantsfrg2089
authored andcommitted
Handle frames exceeding 256 pixels
1 parent ffde9a9 commit 79f5387

1 file changed

Lines changed: 44 additions & 16 deletions

File tree

src/ImageSharp/Formats/Icon/IconDecoderCore.cs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
3232
this.ReadHeader(stream);
3333

3434
Span<byte> flag = stackalloc byte[PngConstants.HeaderBytes.Length];
35-
Image<TPixel> result = new(this.Dimensions.Width, this.Dimensions.Height);
35+
36+
List<(Image<TPixel> Image, bool IsPng, int Index)> decodedEntries = new(this.Entries.Length);
3637

3738
for (int i = 0; i < this.Entries.Length; i++)
3839
{
@@ -54,37 +55,62 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
5455
stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current);
5556

5657
bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes);
57-
using Image<TPixel> temp = this.GetDecoder(isPng).Decode<TPixel>(stream, cancellationToken);
5858

59-
ImageFrame<TPixel> source = temp.Frames.RootFrameUnsafe;
60-
ImageFrame<TPixel> target = i == 0 ? result.Frames.RootFrameUnsafe : result.Frames.CreateFrame();
59+
// Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result.
60+
Image<TPixel> temp = this.GetDecoder(isPng).Decode<TPixel>(stream, cancellationToken);
61+
decodedEntries.Add((temp, isPng, i));
62+
63+
// Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data
64+
// which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft.
65+
this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height));
66+
}
6167

62-
// Draw the new frame at position 0,0. We capture the dimensions for cropping during encoding
63-
// via the icon entry.
64-
for (int h = 0; h < source.Height; h++)
68+
ImageMetadata metadata = new();
69+
BmpMetadata? bmpMetadata = null;
70+
PngMetadata? pngMetadata = null;
71+
Image<TPixel> result = new(this.Options.Configuration, metadata, decodedEntries.Select(x =>
72+
{
73+
ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions);
74+
ImageFrame<TPixel> source = x.Image.Frames.RootFrameUnsafe;
75+
for (int y = 0; y < source.Height; y++)
6576
{
66-
source.PixelBuffer.DangerousGetRowSpan(h).CopyTo(target.PixelBuffer.DangerousGetRowSpan(h));
77+
source.PixelBuffer.DangerousGetRowSpan(y).CopyTo(target.PixelBuffer.DangerousGetRowSpan(y));
6778
}
6879

69-
// Copy the format specific metadata to the image.
70-
if (isPng)
80+
// Copy the format specific frame metadata to the image.
81+
if (x.IsPng)
7182
{
72-
if (i == 0)
83+
if (x.Index == 0)
7384
{
74-
result.Metadata.SetFormatMetadata(PngFormat.Instance, temp.Metadata.GetPngMetadata());
85+
pngMetadata = x.Image.Metadata.GetPngMetadata();
7586
}
7687

88+
// Bmp does not contain frame specific metadata.
7789
target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngFrameMetadata());
7890
}
79-
else if (i == 0)
91+
else if (x.Index == 0)
8092
{
81-
// Bmp does not contain frame specific metadata.
82-
result.Metadata.SetFormatMetadata(BmpFormat.Instance, temp.Metadata.GetBmpMetadata());
93+
bmpMetadata = x.Image.Metadata.GetBmpMetadata();
8394
}
8495

8596
// TODO: The inheriting decoder should be responsible for setting the actual data (FromIconDirEntry)
8697
// so we can avoid the protected Field1 and Field2 properties and use strong typing.
87-
this.GetFrameMetadata(target.Metadata).FromIconDirEntry(entry);
98+
this.GetFrameMetadata(target.Metadata).FromIconDirEntry(this.Entries[x.Index]);
99+
100+
x.Image.Dispose();
101+
102+
return target;
103+
}).ToArray());
104+
105+
// Copy the format specific metadata to the image.
106+
if (bmpMetadata != null)
107+
{
108+
result.Metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata);
109+
}
110+
111+
if (pngMetadata != null)
112+
{
113+
result.Metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata);
88114
}
89115

90116
return result;
@@ -125,6 +151,8 @@ protected void ReadHeader(Stream stream)
125151
int height = 0;
126152
foreach (IconDirEntry entry in this.Entries)
127153
{
154+
// Since Windows 95 size of an image in the ICONDIRENTRY structure might
155+
// be set to zero, which means 256 pixels.
128156
if (entry.Width == 0)
129157
{
130158
width = 256;

0 commit comments

Comments
 (0)