Skip to content

Commit 1a51a5c

Browse files
committed
Add AniChunk
1 parent 9055bed commit 1a51a5c

File tree

8 files changed

+320
-23
lines changed

8 files changed

+320
-23
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
#nullable disable
5+
using System.Buffers;
6+
using SixLabors.ImageSharp.Formats.Png;
7+
8+
namespace SixLabors.ImageSharp.Formats.Ani;
9+
10+
internal readonly struct AniChunk
11+
{
12+
public AniChunk(int length, AniChunkType type, IMemoryOwner<byte> data = null)
13+
{
14+
this.Length = length;
15+
this.Type = type;
16+
this.Data = data;
17+
}
18+
19+
/// <summary>
20+
/// Gets the length.
21+
/// An unsigned integer giving the number of bytes in the chunk's
22+
/// data field. The length counts only the data field, not itself,
23+
/// the chunk type code, or the CRC. Zero is a valid length
24+
/// </summary>
25+
public int Length { get; }
26+
27+
/// <summary>
28+
/// Gets the chunk type.
29+
/// The value is the equal to the UInt32BigEndian encoding of its 4 ASCII characters.
30+
/// </summary>
31+
public AniChunkType Type { get; }
32+
33+
/// <summary>
34+
/// Gets the data bytes appropriate to the chunk type, if any.
35+
/// This field can be of zero length or null.
36+
/// </summary>
37+
public IMemoryOwner<byte> Data { get; }
38+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Formats.Ani;
5+
6+
internal enum AniChunkType : uint
7+
{
8+
Seq = 0x73_65_71_20,
9+
Rate = 0x72_61_74_65,
10+
List = 0x4C_49_53_54
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Formats.Ani;
5+
6+
internal static class AniConstants
7+
{
8+
/// <summary>
9+
/// The list of mime types that equate to an ani.
10+
/// </summary>
11+
/// <remarks>
12+
/// See <see href="https://en.wikipedia.org/wiki/ICO_(file_format)#MIME_type"/>
13+
/// </remarks>
14+
public static readonly IEnumerable<string> MimeTypes = [];
15+
16+
/// <summary>
17+
/// The list of file extensions that equate to an ani.
18+
/// </summary>
19+
public static readonly IEnumerable<string> FileExtensions = ["ani"];
20+
21+
}
Lines changed: 176 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,206 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Buffers;
5+
using System.Buffers.Binary;
6+
using System.Runtime.CompilerServices;
47
using SixLabors.ImageSharp.Formats.Icon;
8+
using SixLabors.ImageSharp.Formats.Png;
59
using SixLabors.ImageSharp.IO;
10+
using SixLabors.ImageSharp.Memory.Internals;
11+
using SixLabors.ImageSharp.Memory;
12+
using SixLabors.ImageSharp.Metadata;
613

714
namespace SixLabors.ImageSharp.Formats.Ani;
815

916
internal class AniDecoderCore : ImageDecoderCore
1017
{
18+
/// <summary>
19+
/// The general decoder options.
20+
/// </summary>
21+
private readonly Configuration configuration;
22+
23+
/// <summary>
24+
/// The stream to decode from.
25+
/// </summary>
26+
private BufferedReadStream currentStream = null!;
27+
28+
private AniHeader header;
29+
1130
public AniDecoderCore(DecoderOptions options)
12-
: base(options)
13-
{
14-
}
31+
: base(options) => this.configuration = options.Configuration;
1532

1633
protected override Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
1734
{
18-
this.ReadHeader(stream);
19-
Span<byte> buffer = stackalloc byte[4];
20-
_ = stream.Read(buffer);
21-
uint type = BitConverter.ToUInt32(buffer);
22-
switch (type)
35+
this.currentStream = stream;
36+
this.ReadHeader();
37+
ImageMetadata metadata = new();
38+
AniMetadata aniMetadata = metadata.GetAniMetadata();
39+
Image<TPixel>? image = null;
40+
41+
Span<byte> buffer = stackalloc byte[20];
42+
43+
try
2344
{
24-
case 0x73_65_71_20: // seq
25-
break;
26-
case 0x72_61_74_65: // rate
27-
break;
28-
case 0x4C_49_53_54: // list
29-
break;
30-
default:
31-
break;
45+
while (this.TryReadChunk(buffer, out AniChunk chunk))
46+
{
47+
try
48+
{
49+
switch (chunk.Type)
50+
{
51+
case AniChunkType.Seq:
52+
53+
break;
54+
case AniChunkType.Rate:
55+
56+
break;
57+
case AniChunkType.List:
58+
59+
break;
60+
default:
61+
break;
62+
}
63+
}
64+
finally
65+
{
66+
chunk.Data?.Dispose();
67+
}
68+
}
3269
}
70+
catch
71+
{
72+
image?.Dispose();
73+
throw;
74+
}
75+
3376

3477
throw new NotImplementedException();
3578
}
3679

80+
private void ReadSeq()
81+
{
82+
83+
}
84+
85+
private bool TryReadChunk(Span<byte> buffer, out AniChunk chunk)
86+
{
87+
if (!this.TryReadChunkLength(buffer, out int length))
88+
{
89+
// IEND
90+
chunk = default;
91+
return false;
92+
}
93+
94+
while (length < 0)
95+
{
96+
// Not a valid chunk so try again until we reach a known chunk.
97+
if (!this.TryReadChunkLength(buffer, out length))
98+
{
99+
// IEND
100+
chunk = default;
101+
return false;
102+
}
103+
}
104+
105+
AniChunkType type = this.ReadChunkType(buffer);
106+
107+
// A chunk might report a length that exceeds the length of the stream.
108+
// Take the minimum of the two values to ensure we don't read past the end of the stream.
109+
long position = this.currentStream.Position;
110+
chunk = new AniChunk(
111+
length: (int)Math.Min(length, this.currentStream.Length - position),
112+
type: type,
113+
data: this.ReadChunkData(length));
114+
115+
return true;
116+
}
117+
118+
37119
protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
38120
{
39121
throw new NotImplementedException();
40122
}
41123

42-
private void ReadHeader(Stream stream)
124+
private void ReadHeader()
43125
{
44126
// Skip the identifier
45-
stream.Skip(12);
127+
this.currentStream.Skip(12);
46128
Span<byte> buffer = stackalloc byte[36];
47-
_ = stream.Read(buffer);
48-
AniHeader header = AniHeader.Parse(buffer);
129+
_ = this.currentStream.Read(buffer);
130+
this.header = AniHeader.Parse(buffer);
131+
}
132+
133+
private void ReadSeq(Stream stream)
134+
{
135+
Span<byte> buffer = stackalloc byte[4];
136+
int length = BinaryPrimitives.ReadInt32BigEndian(buffer);
137+
49138
}
139+
140+
/// <summary>
141+
/// Attempts to read the length of the next chunk.
142+
/// </summary>
143+
/// <param name="buffer">Temporary buffer.</param>
144+
/// <param name="result">The result length. If the return type is <see langword="false"/> this parameter is passed uninitialized.</param>
145+
/// <returns>
146+
/// Whether the length was read.
147+
/// </returns>
148+
[MethodImpl(InliningOptions.ShortMethod)]
149+
private bool TryReadChunkLength(Span<byte> buffer, out int result)
150+
{
151+
if (this.currentStream.Read(buffer, 0, 4) == 4)
152+
{
153+
result = BinaryPrimitives.ReadInt32BigEndian(buffer);
154+
155+
return true;
156+
}
157+
158+
result = 0;
159+
return false;
160+
}
161+
162+
/// <summary>
163+
/// Identifies the chunk type from the chunk.
164+
/// </summary>
165+
/// <param name="buffer">Temporary buffer.</param>
166+
/// <exception cref="ImageFormatException">
167+
/// Thrown if the input stream is not valid.
168+
/// </exception>
169+
[MethodImpl(InliningOptions.ShortMethod)]
170+
private AniChunkType ReadChunkType(Span<byte> buffer)
171+
{
172+
if (this.currentStream.Read(buffer, 0, 4) == 4)
173+
{
174+
return (AniChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
175+
}
176+
177+
PngThrowHelper.ThrowInvalidChunkType();
178+
179+
// The IDE cannot detect the throw here.
180+
return default;
181+
}
182+
183+
/// <summary>
184+
/// Reads the chunk data from the stream.
185+
/// </summary>
186+
/// <param name="length">The length of the chunk data to read.</param>
187+
[MethodImpl(InliningOptions.ShortMethod)]
188+
private IMemoryOwner<byte> ReadChunkData(int length)
189+
{
190+
if (length == 0)
191+
{
192+
return new BasicArrayBuffer<byte>([]);
193+
}
194+
195+
// We rent the buffer here to return it afterwards in Decode()
196+
// We don't want to throw a degenerated memory exception here as we want to allow partial decoding
197+
// so limit the length.
198+
length = (int)Math.Min(length, this.currentStream.Length - this.currentStream.Position);
199+
IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
200+
201+
this.currentStream.Read(buffer.GetSpan(), 0, length);
202+
203+
return buffer;
204+
}
205+
50206
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
5+
using SixLabors.ImageSharp.Formats.Ico;
6+
7+
namespace SixLabors.ImageSharp.Formats.Ani;
8+
9+
10+
/// <summary>
11+
/// Registers the image encoders, decoders and mime type detectors for the bmp format.
12+
/// </summary>
13+
public sealed class AniFormat : IImageFormat<AniMetadata>
14+
{
15+
/// <summary>
16+
/// Gets the shared instance.
17+
/// </summary>
18+
public static AniFormat Instance { get; } = new();
19+
20+
/// <inheritdoc/>
21+
public AniMetadata CreateDefaultFormatMetadata() => throw new NotImplementedException();
22+
23+
/// <inheritdoc/>
24+
public string Name => "ANI";
25+
26+
/// <inheritdoc/>
27+
public string DefaultMimeType { get; }
28+
29+
/// <inheritdoc/>
30+
public IEnumerable<string> MimeTypes { get; }
31+
32+
/// <inheritdoc/>
33+
public IEnumerable<string> FileExtensions { get; }
34+
}
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,39 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using SixLabors.ImageSharp.Formats.Ico;
45
using SixLabors.ImageSharp.PixelFormats;
56

67
namespace SixLabors.ImageSharp.Formats.Ani;
78

8-
internal class AniMetadata : IFormatMetadata<AniMetadata>
9+
/// <summary>
10+
/// Provides Ani specific metadata information for the image.
11+
/// </summary>
12+
public class AniMetadata : IFormatMetadata<AniMetadata>
913
{
10-
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="AniMetadata"/> class.
16+
/// </summary>
17+
public AniMetadata()
18+
{
19+
}
20+
21+
/// <inheritdoc/>
1122
public static AniMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) => throw new NotImplementedException();
1223

24+
/// <inheritdoc/>
1325
public void AfterImageApply<TPixel>(Image<TPixel> destination)
1426
where TPixel : unmanaged, IPixel<TPixel> => throw new NotImplementedException();
1527

28+
/// <inheritdoc/>
1629
public IDeepCloneable DeepClone() => throw new NotImplementedException();
1730

31+
/// <inheritdoc/>
1832
public PixelTypeInfo GetPixelTypeInfo() => throw new NotImplementedException();
1933

34+
/// <inheritdoc/>
2035
public FormatConnectingMetadata ToFormatConnectingMetadata() => throw new NotImplementedException();
2136

37+
/// <inheritdoc/>
2238
AniMetadata IDeepCloneable<AniMetadata>.DeepClone() => throw new NotImplementedException();
2339
}

src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal enum BmpRenderingIntent
1717
/// <summary>
1818
/// Maintains saturation. Used for business charts and other situations in which undithered colors are required.
1919
/// </summary>
20-
LCS_GM_BUSINESS = 1,
20+
LCS_GM_BUSINESS = 1,
2121

2222
/// <summary>
2323
/// Maintains colorimetric match. Used for graphic designs and named colors.

0 commit comments

Comments
 (0)