Skip to content

Commit ffde9a9

Browse files
JimBobSquarePantsfrg2089
authored andcommitted
Refactor decoder and add notes
1 parent 4832d8f commit ffde9a9

14 files changed

Lines changed: 106 additions & 66 deletions

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
105105
/// <inheritdoc cref="BmpDecoderOptions.SkipFileHeader"/>
106106
private readonly bool skipFileHeader;
107107

108-
/// <inheritdoc cref="BmpDecoderOptions.IsDoubleHeight"/>
108+
/// <inheritdoc cref="BmpDecoderOptions.UseDoubleHeight"/>
109109
private readonly bool isDoubleHeight;
110110

111111
/// <summary>
@@ -120,7 +120,7 @@ public BmpDecoderCore(BmpDecoderOptions options)
120120
this.memoryAllocator = this.configuration.MemoryAllocator;
121121
this.processedAlphaMask = options.ProcessedAlphaMask;
122122
this.skipFileHeader = options.SkipFileHeader;
123-
this.isDoubleHeight = options.IsDoubleHeight;
123+
this.isDoubleHeight = options.UseDoubleHeight;
124124
}
125125

126126
/// <inheritdoc />

src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,26 @@ public sealed class BmpDecoderOptions : ISpecializedDecoderOptions
1818
public RleSkippedPixelHandling RleSkippedPixelHandling { get; init; }
1919

2020
/// <summary>
21-
/// Gets a value indicating whether the additional AlphaMask is processed at decoding time.
21+
/// Gets a value indicating whether the additional alpha mask is processed at decoding time.
2222
/// </summary>
2323
/// <remarks>
24-
/// It will be used at IcoDecoder.
24+
/// Used by the icon decoder.
2525
/// </remarks>
2626
internal bool ProcessedAlphaMask { get; init; }
2727

2828
/// <summary>
2929
/// Gets a value indicating whether to skip loading the BMP file header.
3030
/// </summary>
3131
/// <remarks>
32-
/// It will be used at IcoDecoder.
32+
/// Used by the icon decoder.
3333
/// </remarks>
3434
internal bool SkipFileHeader { get; init; }
3535

3636
/// <summary>
37-
/// Gets a value indicating whether the height is double of true height.
37+
/// Gets a value indicating whether to treat the height as double of true height.
3838
/// </summary>
3939
/// <remarks>
40-
/// It will be used at IcoDecoder.
40+
/// Used by the icon decoder.
4141
/// </remarks>
42-
internal bool IsDoubleHeight { get; init; }
42+
internal bool UseDoubleHeight { get; init; }
4343
}

src/ImageSharp/Formats/Icon/Cur/CurDecoderCore.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using SixLabors.ImageSharp.Metadata;
55

6+
// TODO: flatten namespace.
7+
// namespace SixLabors.ImageSharp.Formats.Cur;
68
namespace SixLabors.ImageSharp.Formats.Icon.Cur;
79

810
internal sealed class CurDecoderCore : IconDecoderCore

src/ImageSharp/Formats/Icon/Cur/CurFrameMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace SixLabors.ImageSharp.Formats.Icon.Cur;
55

66
/// <summary>
7-
/// IcoFrameMetadata
7+
/// IcoFrameMetadata. TODO: Remove base class and merge into this class.
88
/// </summary>
99
public class CurFrameMetadata : IconFrameMetadata, IDeepCloneable<CurFrameMetadata>, IDeepCloneable
1010
{

src/ImageSharp/Formats/Icon/Ico/IcoDecoderCore.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using SixLabors.ImageSharp.Metadata;
55

6+
// TODO: flatten namespace.
7+
// namespace SixLabors.ImageSharp.Formats.Ico;
68
namespace SixLabors.ImageSharp.Formats.Icon.Ico;
79

810
internal sealed class IcoDecoderCore : IconDecoderCore

src/ImageSharp/Formats/Icon/Ico/IcoFrameMetadata.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace SixLabors.ImageSharp.Formats.Icon.Ico;
55

66
/// <summary>
7-
/// IcoFrameMetadata
7+
/// IcoFrameMetadata. TODO: Remove base class and merge into this class.
88
/// </summary>
99
public class IcoFrameMetadata : IconFrameMetadata, IDeepCloneable<IcoFrameMetadata>, IDeepCloneable
1010
{
@@ -38,11 +38,9 @@ public IcoFrameMetadata(byte width, byte height, byte colorCount, ushort field1,
3838
}
3939

4040
/// <summary>
41-
/// Gets or sets Specifies bits per pixel.
41+
/// Gets or sets the bits per pixel.
42+
/// TODO: This needs to be constrained and calculated using the metadata returned from the png/bmp.
4243
/// </summary>
43-
/// <remarks>
44-
/// It may used by Encoder.
45-
/// </remarks>
4644
public ushort BitCount { get => this.Field2; set => this.Field2 = value; }
4745

4846
/// <inheritdoc/>

src/ImageSharp/Formats/Icon/IconAssert.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,4 @@ internal static long EndOfStream(long v, long length)
3232

3333
return v;
3434
}
35-
36-
internal static void NotSquare(in Size size)
37-
{
38-
if (size.Width != size.Height)
39-
{
40-
throw new FormatException("This image is not square.");
41-
}
42-
}
4335
}

src/ImageSharp/Formats/Icon/IconDecoderCore.cs

Lines changed: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Runtime.InteropServices;
5-
using SixLabors.ImageSharp;
5+
using SixLabors.ImageSharp.Formats.Bmp;
6+
using SixLabors.ImageSharp.Formats.Png;
67
using SixLabors.ImageSharp.IO;
78
using SixLabors.ImageSharp.Metadata;
89
using SixLabors.ImageSharp.PixelFormats;
@@ -30,50 +31,63 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
3031
long basePosition = stream.Position;
3132
this.ReadHeader(stream);
3233

33-
Span<byte> flag = stackalloc byte[Png.PngConstants.HeaderBytes.Length];
34+
Span<byte> flag = stackalloc byte[PngConstants.HeaderBytes.Length];
35+
Image<TPixel> result = new(this.Dimensions.Width, this.Dimensions.Height);
3436

35-
List<(Image<TPixel> Image, bool IsPng, int Index)> frames = new(this.Entries.Length);
3637
for (int i = 0; i < this.Entries.Length; i++)
3738
{
38-
_ = IconAssert.EndOfStream(stream.Seek(basePosition + this.Entries[i].ImageOffset, SeekOrigin.Begin), basePosition + this.Entries[i].ImageOffset);
39-
_ = IconAssert.EndOfStream(stream.Read(flag), Png.PngConstants.HeaderBytes.Length);
40-
_ = stream.Seek(-Png.PngConstants.HeaderBytes.Length, SeekOrigin.Current);
39+
ref IconDirEntry entry = ref this.Entries[i];
4140

42-
bool isPng = flag.SequenceEqual(Png.PngConstants.HeaderBytes);
41+
// If we hit the end of the stream we should break.
42+
if (stream.Seek(basePosition + entry.ImageOffset, SeekOrigin.Begin) >= stream.Length)
43+
{
44+
break;
45+
}
4346

44-
Image<TPixel> img = this.GetDecoder(isPng).Decode<TPixel>(stream, cancellationToken);
45-
IconAssert.NotSquare(img.Size);
46-
frames.Add((img, isPng, i));
47-
if (isPng && img.Size.Width > this.Dimensions.Width)
47+
// There should always be enough bytes for this regardless of the entry type.
48+
if (stream.Read(flag) != PngConstants.HeaderBytes.Length)
4849
{
49-
this.Dimensions = img.Size;
50+
break;
5051
}
51-
}
5252

53-
ImageMetadata metadata = new();
54-
return new(this.Options.Configuration, metadata, frames.Select(i =>
55-
{
56-
ImageFrame<TPixel> target = new(this.Options.Configuration, this.Dimensions);
57-
ImageFrame<TPixel> source = i.Image.Frames.RootFrameUnsafe;
53+
// Reset the stream position.
54+
stream.Seek(-PngConstants.HeaderBytes.Length, SeekOrigin.Current);
55+
56+
bool isPng = flag.SequenceEqual(PngConstants.HeaderBytes);
57+
using Image<TPixel> temp = this.GetDecoder(isPng).Decode<TPixel>(stream, cancellationToken);
58+
59+
ImageFrame<TPixel> source = temp.Frames.RootFrameUnsafe;
60+
ImageFrame<TPixel> target = i == 0 ? result.Frames.RootFrameUnsafe : result.Frames.CreateFrame();
61+
62+
// Draw the new frame at position 0,0. We capture the dimensions for cropping during encoding
63+
// via the icon entry.
5864
for (int h = 0; h < source.Height; h++)
5965
{
6066
source.PixelBuffer.DangerousGetRowSpan(h).CopyTo(target.PixelBuffer.DangerousGetRowSpan(h));
6167
}
6268

63-
if (i.IsPng)
69+
// Copy the format specific metadata to the image.
70+
if (isPng)
6471
{
65-
target.Metadata.UnsafeSetFormatMetadata(Png.PngFormat.Instance, i.Image.Metadata.GetPngMetadata());
72+
if (i == 0)
73+
{
74+
result.Metadata.SetFormatMetadata(PngFormat.Instance, temp.Metadata.GetPngMetadata());
75+
}
76+
77+
target.Metadata.SetFormatMetadata(PngFormat.Instance, target.Metadata.GetPngFrameMetadata());
6678
}
67-
else
79+
else if (i == 0)
6880
{
69-
target.Metadata.UnsafeSetFormatMetadata(Bmp.BmpFormat.Instance, i.Image.Metadata.GetBmpMetadata());
81+
// Bmp does not contain frame specific metadata.
82+
result.Metadata.SetFormatMetadata(BmpFormat.Instance, temp.Metadata.GetBmpMetadata());
7083
}
7184

72-
this.GetFrameMetadata(target.Metadata).FromIconDirEntry(this.Entries[i.Index]);
85+
// TODO: The inheriting decoder should be responsible for setting the actual data (FromIconDirEntry)
86+
// so we can avoid the protected Field1 and Field2 properties and use strong typing.
87+
this.GetFrameMetadata(target.Metadata).FromIconDirEntry(entry);
88+
}
7389

74-
i.Image.Dispose();
75-
return target;
76-
}).ToArray());
90+
return result;
7791
}
7892

7993
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
@@ -84,34 +98,62 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
8498
ImageFrameMetadata[] frames = new ImageFrameMetadata[this.FileHeader.Count];
8599
for (int i = 0; i < frames.Length; i++)
86100
{
101+
// TODO: Use the Identify methods in each decoder to return the
102+
// format specific metadata for the image and frame.
87103
frames[i] = new();
88104
IconFrameMetadata icoFrameMetadata = this.GetFrameMetadata(frames[i]);
89105
icoFrameMetadata.FromIconDirEntry(this.Entries[i]);
90106
}
91107

108+
// TODO: Use real values from the metadata.
92109
return new(new(32), new(0), metadata, frames);
93110
}
94111

95112
protected abstract IconFrameMetadata GetFrameMetadata(ImageFrameMetadata metadata);
96113

97114
protected void ReadHeader(Stream stream)
98115
{
116+
// TODO: Check length and throw if the header cannot be read.
99117
_ = Read(stream, out this.fileHeader, IconDir.Size);
100118
this.Entries = new IconDirEntry[this.FileHeader.Count];
101119
for (int i = 0; i < this.Entries.Length; i++)
102120
{
103121
_ = Read(stream, out this.Entries[i], IconDirEntry.Size);
104122
}
105123

106-
this.Dimensions = new(
107-
this.Entries.Max(i => i.Width),
108-
this.Entries.Max(i => i.Height));
124+
int width = 0;
125+
int height = 0;
126+
foreach (IconDirEntry entry in this.Entries)
127+
{
128+
if (entry.Width == 0)
129+
{
130+
width = 256;
131+
}
132+
133+
if (entry.Height == 0)
134+
{
135+
height = 256;
136+
}
137+
138+
if (width == 256 && height == 256)
139+
{
140+
break;
141+
}
142+
143+
width = Math.Max(width, entry.Width);
144+
height = Math.Max(height, entry.Height);
145+
}
146+
147+
this.Dimensions = new(width, height);
109148
}
110149

111150
private static int Read<T>(Stream stream, out T data, int size)
112151
where T : unmanaged
113152
{
153+
// TODO: Use explicit parsing methods for each T type.
154+
// See PngHeader.Parse() for example.
114155
Span<byte> buffer = stackalloc byte[size];
156+
115157
_ = IconAssert.EndOfStream(stream.Read(buffer), size);
116158
data = MemoryMarshal.Cast<byte, T>(buffer)[0];
117159
return size;
@@ -121,15 +163,16 @@ private IImageDecoderInternals GetDecoder(bool isPng)
121163
{
122164
if (isPng)
123165
{
124-
return new Png.PngDecoderCore(this.Options);
166+
return new PngDecoderCore(this.Options);
125167
}
126168
else
127169
{
128-
return new Bmp.BmpDecoderCore(new()
170+
return new BmpDecoderCore(new()
129171
{
172+
GeneralOptions = this.Options,
130173
ProcessedAlphaMask = true,
131174
SkipFileHeader = true,
132-
IsDoubleHeight = true,
175+
UseDoubleHeight = true,
133176
});
134177
}
135178
}

src/ImageSharp/Formats/Icon/IconFrameCompression.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Icon;
88
/// </summary>
99
public enum IconFrameCompression
1010
{
11-
/// <summary>
12-
/// Unknown
13-
/// </summary>
14-
Unknown,
15-
1611
/// <summary>
1712
/// Bmp
1813
/// </summary>

src/ImageSharp/Formats/Icon/IconFrameMetadata.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Icon;
55

66
/// <summary>
77
/// IconFrameMetadata
8+
/// TODO: Delete this. Treat the individual metadata types as separate types so we can avoid Field1, Field2 and use strong typing with constaints.
89
/// </summary>
910
public abstract class IconFrameMetadata : IDeepCloneable
1011
{

0 commit comments

Comments
 (0)