Skip to content

Commit b31bd23

Browse files
Complete tests and fix issues
1 parent 9a7727b commit b31bd23

File tree

16 files changed

+363
-119
lines changed

16 files changed

+363
-119
lines changed

src/ImageSharp/Formats/Cur/CurDecoderCore.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@ public CurDecoderCore(DecoderOptions options)
1414
{
1515
}
1616

17-
protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel)
17+
protected override void SetFrameMetadata(
18+
ImageFrameMetadata metadata,
19+
in IconDirEntry entry,
20+
IconFrameCompression compression,
21+
BmpBitsPerPixel bitsPerPixel,
22+
ReadOnlyMemory<Color>? colorTable)
1823
{
1924
CurFrameMetadata curFrameMetadata = metadata.GetCurMetadata();
2025
curFrameMetadata.FromIconDirEntry(entry);
2126
curFrameMetadata.Compression = compression;
2227
curFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
28+
curFrameMetadata.ColorTable = colorTable;
2329
}
2430
}

src/ImageSharp/Formats/Cur/CurEncoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public sealed class CurEncoder : QuantizingImageEncoder
1111
/// <inheritdoc/>
1212
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
1313
{
14-
CurEncoderCore encoderCore = new();
14+
CurEncoderCore encoderCore = new(this);
1515
encoderCore.Encode(image, stream, cancellationToken);
1616
}
1717
}

src/ImageSharp/Formats/Cur/CurEncoderCore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ namespace SixLabors.ImageSharp.Formats.Cur;
77

88
internal sealed class CurEncoderCore : IconEncoderCore
99
{
10-
public CurEncoderCore()
11-
: base(IconFileType.CUR)
10+
public CurEncoderCore(QuantizingImageEncoder encoder)
11+
: base(encoder, IconFileType.CUR)
1212
{
1313
}
1414
}

src/ImageSharp/Formats/Cur/CurFrameMetadata.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using SixLabors.ImageSharp.Formats.Bmp;
55
using SixLabors.ImageSharp.Formats.Icon;
6+
using SixLabors.ImageSharp.PixelFormats;
67

78
namespace SixLabors.ImageSharp.Formats.Cur;
89

@@ -18,14 +19,14 @@ public CurFrameMetadata()
1819
{
1920
}
2021

21-
private CurFrameMetadata(CurFrameMetadata metadata)
22+
private CurFrameMetadata(CurFrameMetadata other)
2223
{
23-
this.Compression = metadata.Compression;
24-
this.HotspotX = metadata.HotspotX;
25-
this.HotspotY = metadata.HotspotY;
26-
this.EncodingWidth = metadata.EncodingWidth;
27-
this.EncodingHeight = metadata.EncodingHeight;
28-
this.BmpBitsPerPixel = metadata.BmpBitsPerPixel;
24+
this.Compression = other.Compression;
25+
this.HotspotX = other.HotspotX;
26+
this.HotspotY = other.HotspotY;
27+
this.EncodingWidth = other.EncodingWidth;
28+
this.EncodingHeight = other.EncodingHeight;
29+
this.BmpBitsPerPixel = other.BmpBitsPerPixel;
2930
}
3031

3132
/// <summary>
@@ -45,13 +46,13 @@ private CurFrameMetadata(CurFrameMetadata metadata)
4546

4647
/// <summary>
4748
/// Gets or sets the encoding width. <br />
48-
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
49+
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
4950
/// </summary>
5051
public byte EncodingWidth { get; set; }
5152

5253
/// <summary>
5354
/// Gets or sets the encoding height. <br />
54-
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
55+
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
5556
/// </summary>
5657
public byte EncodingHeight { get; set; }
5758

@@ -61,6 +62,12 @@ private CurFrameMetadata(CurFrameMetadata metadata)
6162
/// </summary>
6263
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
6364

65+
/// <summary>
66+
/// Gets or sets the color table, if any.
67+
/// The underlying pixel format is represented by <see cref="Bgr24"/>.
68+
/// </summary>
69+
public ReadOnlyMemory<Color>? ColorTable { get; set; }
70+
6471
/// <inheritdoc/>
6572
public CurFrameMetadata DeepClone() => new(this);
6673

src/ImageSharp/Formats/Ico/IcoDecoderCore.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@ public IcoDecoderCore(DecoderOptions options)
1414
{
1515
}
1616

17-
protected override void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel)
17+
protected override void SetFrameMetadata(
18+
ImageFrameMetadata metadata,
19+
in IconDirEntry entry,
20+
IconFrameCompression compression,
21+
BmpBitsPerPixel bitsPerPixel,
22+
ReadOnlyMemory<Color>? colorTable)
1823
{
1924
IcoFrameMetadata icoFrameMetadata = metadata.GetIcoMetadata();
2025
icoFrameMetadata.FromIconDirEntry(entry);
2126
icoFrameMetadata.Compression = compression;
2227
icoFrameMetadata.BmpBitsPerPixel = bitsPerPixel;
28+
icoFrameMetadata.ColorTable = colorTable;
2329
}
2430
}

src/ImageSharp/Formats/Ico/IcoEncoder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ namespace SixLabors.ImageSharp.Formats.Ico;
66
/// <summary>
77
/// Image encoder for writing an image to a stream as a Windows Icon.
88
/// </summary>
9-
public sealed class IcoEncoder : ImageEncoder
9+
public sealed class IcoEncoder : QuantizingImageEncoder
1010
{
1111
/// <inheritdoc/>
1212
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
1313
{
14-
IcoEncoderCore encoderCore = new();
14+
IcoEncoderCore encoderCore = new(this);
1515
encoderCore.Encode(image, stream, cancellationToken);
1616
}
1717
}

src/ImageSharp/Formats/Ico/IcoEncoderCore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ namespace SixLabors.ImageSharp.Formats.Ico;
77

88
internal sealed class IcoEncoderCore : IconEncoderCore
99
{
10-
public IcoEncoderCore()
11-
: base(IconFileType.ICO)
10+
public IcoEncoderCore(QuantizingImageEncoder encoder)
11+
: base(encoder, IconFileType.ICO)
1212
{
1313
}
1414
}

src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using SixLabors.ImageSharp.Formats.Bmp;
55
using SixLabors.ImageSharp.Formats.Icon;
6+
using SixLabors.ImageSharp.PixelFormats;
67

78
namespace SixLabors.ImageSharp.Formats.Ico;
89

@@ -18,12 +19,17 @@ public IcoFrameMetadata()
1819
{
1920
}
2021

21-
private IcoFrameMetadata(IcoFrameMetadata metadata)
22+
private IcoFrameMetadata(IcoFrameMetadata other)
2223
{
23-
this.Compression = metadata.Compression;
24-
this.EncodingWidth = metadata.EncodingWidth;
25-
this.EncodingHeight = metadata.EncodingHeight;
26-
this.BmpBitsPerPixel = metadata.BmpBitsPerPixel;
24+
this.Compression = other.Compression;
25+
this.EncodingWidth = other.EncodingWidth;
26+
this.EncodingHeight = other.EncodingHeight;
27+
this.BmpBitsPerPixel = other.BmpBitsPerPixel;
28+
29+
if (other.ColorTable?.Length > 0)
30+
{
31+
this.ColorTable = other.ColorTable.Value.ToArray();
32+
}
2733
}
2834

2935
/// <summary>
@@ -33,13 +39,13 @@ private IcoFrameMetadata(IcoFrameMetadata metadata)
3339

3440
/// <summary>
3541
/// Gets or sets the encoding width. <br />
36-
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
42+
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
3743
/// </summary>
3844
public byte EncodingWidth { get; set; }
3945

4046
/// <summary>
4147
/// Gets or sets the encoding height. <br />
42-
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels.
48+
/// Can be any number between 0 and 255. Value 0 means a frame height of 256 pixels or greater.
4349
/// </summary>
4450
public byte EncodingHeight { get; set; }
4551

@@ -49,6 +55,12 @@ private IcoFrameMetadata(IcoFrameMetadata metadata)
4955
/// </summary>
5056
public BmpBitsPerPixel BmpBitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel32;
5157

58+
/// <summary>
59+
/// Gets or sets the color table, if any.
60+
/// The underlying pixel format is represented by <see cref="Bgr24"/>.
61+
/// </summary>
62+
public ReadOnlyMemory<Color>? ColorTable { get; set; }
63+
5264
/// <inheritdoc/>
5365
public IcoFrameMetadata DeepClone() => new(this);
5466

src/ImageSharp/Formats/Icon/IconDecoderCore.cs

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,16 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
3131

3232
Span<byte> flag = stackalloc byte[PngConstants.HeaderBytes.Length];
3333

34-
List<(Image<TPixel> Image, IconFrameCompression Compression, int Index)> decodedEntries = new(this.entries.Length);
34+
List<(Image<TPixel> Image, IconFrameCompression Compression, int Index)> decodedEntries
35+
= new((int)Math.Min(this.entries.Length, this.Options.MaxFrames));
3536

3637
for (int i = 0; i < this.entries.Length; i++)
3738
{
39+
if (i == this.Options.MaxFrames)
40+
{
41+
break;
42+
}
43+
3844
ref IconDirEntry entry = ref this.entries[i];
3945

4046
// If we hit the end of the stream we should break.
@@ -69,6 +75,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
6975
Image<TPixel> result = new(this.Options.Configuration, metadata, decodedEntries.Select(x =>
7076
{
7177
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
78+
ReadOnlyMemory<Color>? colorTable = null;
7279
ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions);
7380
ImageFrame<TPixel> source = x.Image.Frames.RootFrameUnsafe;
7481
for (int y = 0; y < source.Height; y++)
@@ -88,11 +95,22 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
8895
}
8996
else
9097
{
91-
bmpMetadata = x.Image.Metadata.GetBmpMetadata();
92-
bitsPerPixel = bmpMetadata.BitsPerPixel;
98+
BmpMetadata meta = x.Image.Metadata.GetBmpMetadata();
99+
bitsPerPixel = meta.BitsPerPixel;
100+
colorTable = meta.ColorTable;
101+
102+
if (x.Index == 0)
103+
{
104+
bmpMetadata = meta;
105+
}
93106
}
94107

95-
this.SetFrameMetadata(target.Metadata, this.entries[x.Index], x.Compression, bitsPerPixel);
108+
this.SetFrameMetadata(
109+
target.Metadata,
110+
this.entries[x.Index],
111+
x.Compression,
112+
bitsPerPixel,
113+
colorTable);
96114

97115
x.Image.Dispose();
98116

@@ -122,11 +140,14 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
122140
Span<byte> flag = stackalloc byte[PngConstants.HeaderBytes.Length];
123141

124142
ImageMetadata metadata = new();
125-
ImageFrameMetadata[] frames = new ImageFrameMetadata[this.fileHeader.Count];
143+
BmpMetadata? bmpMetadata = null;
144+
PngMetadata? pngMetadata = null;
145+
ImageFrameMetadata[] frames = new ImageFrameMetadata[Math.Min(this.fileHeader.Count, this.Options.MaxFrames)];
126146
int bpp = 0;
127147
for (int i = 0; i < frames.Length; i++)
128148
{
129149
BmpBitsPerPixel bitsPerPixel = BmpBitsPerPixel.Pixel32;
150+
ReadOnlyMemory<Color>? colorTable = null;
130151
ref IconDirEntry entry = ref this.entries[i];
131152

132153
// If we hit the end of the stream we should break.
@@ -149,25 +170,65 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
149170
// Decode the frame into a temp image buffer. This is disposed after the frame is copied to the result.
150171
ImageInfo temp = this.GetDecoder(isPng).Identify(stream, cancellationToken);
151172

152-
frames[i] = new();
153-
if (!isPng)
173+
ImageFrameMetadata frameMetadata = new();
174+
175+
if (isPng)
176+
{
177+
if (i == 0)
178+
{
179+
pngMetadata = temp.Metadata.GetPngMetadata();
180+
}
181+
182+
frameMetadata.SetFormatMetadata(PngFormat.Instance, temp.FrameMetadataCollection[0].GetPngMetadata());
183+
}
184+
else
154185
{
155-
bitsPerPixel = temp.Metadata.GetBmpMetadata().BitsPerPixel;
186+
BmpMetadata meta = temp.Metadata.GetBmpMetadata();
187+
bitsPerPixel = meta.BitsPerPixel;
188+
colorTable = meta.ColorTable;
189+
190+
if (i == 0)
191+
{
192+
bmpMetadata = meta;
193+
}
156194
}
157195

158196
bpp = Math.Max(bpp, (int)bitsPerPixel);
159197

160-
this.SetFrameMetadata(frames[i], this.entries[i], isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp, bitsPerPixel);
198+
frames[i] = frameMetadata;
199+
200+
this.SetFrameMetadata(
201+
frames[i],
202+
this.entries[i],
203+
isPng ? IconFrameCompression.Png : IconFrameCompression.Bmp,
204+
bitsPerPixel,
205+
colorTable);
161206

162207
// Since Windows Vista, the size of an image is determined from the BITMAPINFOHEADER structure or PNG image data
163208
// which technically allows storing icons with larger than 256 pixels, but such larger sizes are not recommended by Microsoft.
164209
this.Dimensions = new(Math.Max(this.Dimensions.Width, temp.Size.Width), Math.Max(this.Dimensions.Height, temp.Size.Height));
165210
}
166211

212+
// Copy the format specific metadata to the image.
213+
if (bmpMetadata != null)
214+
{
215+
metadata.SetFormatMetadata(BmpFormat.Instance, bmpMetadata);
216+
}
217+
218+
if (pngMetadata != null)
219+
{
220+
metadata.SetFormatMetadata(PngFormat.Instance, pngMetadata);
221+
}
222+
167223
return new(new(bpp), this.Dimensions, metadata, frames);
168224
}
169225

170-
protected abstract void SetFrameMetadata(ImageFrameMetadata metadata, in IconDirEntry entry, IconFrameCompression compression, BmpBitsPerPixel bitsPerPixel);
226+
protected abstract void SetFrameMetadata(
227+
ImageFrameMetadata metadata,
228+
in IconDirEntry entry,
229+
IconFrameCompression compression,
230+
BmpBitsPerPixel bitsPerPixel,
231+
ReadOnlyMemory<Color>? colorTable);
171232

172233
[MemberNotNull(nameof(entries))]
173234
protected void ReadHeader(Stream stream)

0 commit comments

Comments
 (0)