Skip to content

Commit 0888e54

Browse files
Merge pull request #2511 from Poker-sang/main
APNG support
2 parents d93bc6c + b4e9805 commit 0888e54

File tree

81 files changed

+1530
-573
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1530
-573
lines changed

src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ public override int ReadByte()
123123
/// <inheritdoc/>
124124
public override int Read(byte[] buffer, int offset, int count)
125125
{
126-
if (this.currentDataRemaining == 0)
126+
if (this.currentDataRemaining is 0)
127127
{
128128
// Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks.
129129
this.currentDataRemaining = this.getData();
130130

131-
if (this.currentDataRemaining == 0)
131+
if (this.currentDataRemaining is 0)
132132
{
133133
return 0;
134134
}
@@ -142,11 +142,11 @@ public override int Read(byte[] buffer, int offset, int count)
142142
// Keep reading data until we've reached the end of the stream or filled the buffer.
143143
int bytesRead = 0;
144144
offset += totalBytesRead;
145-
while (this.currentDataRemaining == 0 && totalBytesRead < count)
145+
while (this.currentDataRemaining is 0 && totalBytesRead < count)
146146
{
147147
this.currentDataRemaining = this.getData();
148148

149-
if (this.currentDataRemaining == 0)
149+
if (this.currentDataRemaining is 0)
150150
{
151151
return totalBytesRead;
152152
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers.Binary;
5+
6+
namespace SixLabors.ImageSharp.Formats.Png.Chunks;
7+
8+
internal readonly struct AnimationControl
9+
{
10+
public const int Size = 8;
11+
12+
public AnimationControl(int numberFrames, int numberPlays)
13+
{
14+
this.NumberFrames = numberFrames;
15+
this.NumberPlays = numberPlays;
16+
}
17+
18+
/// <summary>
19+
/// Gets the number of frames
20+
/// </summary>
21+
public int NumberFrames { get; }
22+
23+
/// <summary>
24+
/// Gets the number of times to loop this APNG. 0 indicates infinite looping.
25+
/// </summary>
26+
public int NumberPlays { get; }
27+
28+
/// <summary>
29+
/// Writes the acTL to the given buffer.
30+
/// </summary>
31+
/// <param name="buffer">The buffer to write to.</param>
32+
public void WriteTo(Span<byte> buffer)
33+
{
34+
BinaryPrimitives.WriteInt32BigEndian(buffer[..4], this.NumberFrames);
35+
BinaryPrimitives.WriteInt32BigEndian(buffer[4..8], this.NumberPlays);
36+
}
37+
38+
/// <summary>
39+
/// Parses the APngAnimationControl from the given data buffer.
40+
/// </summary>
41+
/// <param name="data">The data to parse.</param>
42+
/// <returns>The parsed acTL.</returns>
43+
public static AnimationControl Parse(ReadOnlySpan<byte> data)
44+
=> new(
45+
numberFrames: BinaryPrimitives.ReadInt32BigEndian(data[..4]),
46+
numberPlays: BinaryPrimitives.ReadInt32BigEndian(data[4..8]));
47+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers.Binary;
5+
6+
namespace SixLabors.ImageSharp.Formats.Png.Chunks;
7+
8+
internal readonly struct FrameControl
9+
{
10+
public const int Size = 26;
11+
12+
public FrameControl(uint width, uint height)
13+
: this(0, width, height, 0, 0, 0, 0, default, default)
14+
{
15+
}
16+
17+
public FrameControl(
18+
uint sequenceNumber,
19+
uint width,
20+
uint height,
21+
uint xOffset,
22+
uint yOffset,
23+
ushort delayNumerator,
24+
ushort delayDenominator,
25+
PngDisposalMethod disposeOperation,
26+
PngBlendMethod blendOperation)
27+
{
28+
this.SequenceNumber = sequenceNumber;
29+
this.Width = width;
30+
this.Height = height;
31+
this.XOffset = xOffset;
32+
this.YOffset = yOffset;
33+
this.DelayNumerator = delayNumerator;
34+
this.DelayDenominator = delayDenominator;
35+
this.DisposeOperation = disposeOperation;
36+
this.BlendOperation = blendOperation;
37+
}
38+
39+
/// <summary>
40+
/// Gets the sequence number of the animation chunk, starting from 0
41+
/// </summary>
42+
public uint SequenceNumber { get; }
43+
44+
/// <summary>
45+
/// Gets the width of the following frame
46+
/// </summary>
47+
public uint Width { get; }
48+
49+
/// <summary>
50+
/// Gets the height of the following frame
51+
/// </summary>
52+
public uint Height { get; }
53+
54+
/// <summary>
55+
/// Gets the X position at which to render the following frame
56+
/// </summary>
57+
public uint XOffset { get; }
58+
59+
/// <summary>
60+
/// Gets the Y position at which to render the following frame
61+
/// </summary>
62+
public uint YOffset { get; }
63+
64+
/// <summary>
65+
/// Gets the X limit at which to render the following frame
66+
/// </summary>
67+
public uint XMax => this.XOffset + this.Width;
68+
69+
/// <summary>
70+
/// Gets the Y limit at which to render the following frame
71+
/// </summary>
72+
public uint YMax => this.YOffset + this.Height;
73+
74+
/// <summary>
75+
/// Gets the frame delay fraction numerator
76+
/// </summary>
77+
public ushort DelayNumerator { get; }
78+
79+
/// <summary>
80+
/// Gets the frame delay fraction denominator
81+
/// </summary>
82+
public ushort DelayDenominator { get; }
83+
84+
/// <summary>
85+
/// Gets the type of frame area disposal to be done after rendering this frame
86+
/// </summary>
87+
public PngDisposalMethod DisposeOperation { get; }
88+
89+
/// <summary>
90+
/// Gets the type of frame area rendering for this frame
91+
/// </summary>
92+
public PngBlendMethod BlendOperation { get; }
93+
94+
public Rectangle Bounds => new((int)this.XOffset, (int)this.YOffset, (int)this.Width, (int)this.Height);
95+
96+
/// <summary>
97+
/// Validates the APng fcTL.
98+
/// </summary>
99+
/// <param name="header">The header.</param>
100+
/// <exception cref="NotSupportedException">
101+
/// Thrown if the image does pass validation.
102+
/// </exception>
103+
public void Validate(PngHeader header)
104+
{
105+
if (this.Width == 0)
106+
{
107+
PngThrowHelper.ThrowInvalidParameter(this.Width, "Expected > 0");
108+
}
109+
110+
if (this.Height == 0)
111+
{
112+
PngThrowHelper.ThrowInvalidParameter(this.Height, "Expected > 0");
113+
}
114+
115+
if (this.XMax > header.Width)
116+
{
117+
PngThrowHelper.ThrowInvalidParameter(this.XOffset, this.Width, $"The x-offset plus width > {nameof(PngHeader)}.{nameof(PngHeader.Width)}");
118+
}
119+
120+
if (this.YMax > header.Height)
121+
{
122+
PngThrowHelper.ThrowInvalidParameter(this.YOffset, this.Height, $"The y-offset plus height > {nameof(PngHeader)}.{nameof(PngHeader.Height)}");
123+
}
124+
}
125+
126+
/// <summary>
127+
/// Writes the fcTL to the given buffer.
128+
/// </summary>
129+
/// <param name="buffer">The buffer to write to.</param>
130+
public void WriteTo(Span<byte> buffer)
131+
{
132+
BinaryPrimitives.WriteUInt32BigEndian(buffer[..4], this.SequenceNumber);
133+
BinaryPrimitives.WriteUInt32BigEndian(buffer[4..8], this.Width);
134+
BinaryPrimitives.WriteUInt32BigEndian(buffer[8..12], this.Height);
135+
BinaryPrimitives.WriteUInt32BigEndian(buffer[12..16], this.XOffset);
136+
BinaryPrimitives.WriteUInt32BigEndian(buffer[16..20], this.YOffset);
137+
BinaryPrimitives.WriteUInt16BigEndian(buffer[20..22], this.DelayNumerator);
138+
BinaryPrimitives.WriteUInt16BigEndian(buffer[22..24], this.DelayDenominator);
139+
140+
buffer[24] = (byte)this.DisposeOperation;
141+
buffer[25] = (byte)this.BlendOperation;
142+
}
143+
144+
/// <summary>
145+
/// Parses the APngFrameControl from the given data buffer.
146+
/// </summary>
147+
/// <param name="data">The data to parse.</param>
148+
/// <returns>The parsed fcTL.</returns>
149+
public static FrameControl Parse(ReadOnlySpan<byte> data)
150+
=> new(
151+
sequenceNumber: BinaryPrimitives.ReadUInt32BigEndian(data[..4]),
152+
width: BinaryPrimitives.ReadUInt32BigEndian(data[4..8]),
153+
height: BinaryPrimitives.ReadUInt32BigEndian(data[8..12]),
154+
xOffset: BinaryPrimitives.ReadUInt32BigEndian(data[12..16]),
155+
yOffset: BinaryPrimitives.ReadUInt32BigEndian(data[16..20]),
156+
delayNumerator: BinaryPrimitives.ReadUInt16BigEndian(data[20..22]),
157+
delayDenominator: BinaryPrimitives.ReadUInt16BigEndian(data[22..24]),
158+
disposeOperation: (PngDisposalMethod)data[24],
159+
blendOperation: (PngBlendMethod)data[25]);
160+
}

src/ImageSharp/Formats/Png/PngHeader.cs renamed to src/ImageSharp/Formats/Png/Chunks/PngHeader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
using System.Buffers.Binary;
66

7-
namespace SixLabors.ImageSharp.Formats.Png;
7+
namespace SixLabors.ImageSharp.Formats.Png.Chunks;
88

99
/// <summary>
1010
/// Represents the png header chunk.

src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs renamed to src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers.Binary;
@@ -10,11 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Chunks;
1010
/// <summary>
1111
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
1212
/// </summary>
13-
internal readonly struct PhysicalChunkData
13+
internal readonly struct PngPhysical
1414
{
1515
public const int Size = 9;
1616

17-
public PhysicalChunkData(uint x, uint y, byte unitSpecifier)
17+
public PngPhysical(uint x, uint y, byte unitSpecifier)
1818
{
1919
this.XAxisPixelsPerUnit = x;
2020
this.YAxisPixelsPerUnit = y;
@@ -44,13 +44,13 @@ public PhysicalChunkData(uint x, uint y, byte unitSpecifier)
4444
/// </summary>
4545
/// <param name="data">The data buffer.</param>
4646
/// <returns>The parsed PhysicalChunkData.</returns>
47-
public static PhysicalChunkData Parse(ReadOnlySpan<byte> data)
47+
public static PngPhysical Parse(ReadOnlySpan<byte> data)
4848
{
4949
uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data[..4]);
5050
uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4));
5151
byte unit = data[8];
5252

53-
return new PhysicalChunkData(hResolution, vResolution, unit);
53+
return new PngPhysical(hResolution, vResolution, unit);
5454
}
5555

5656
/// <summary>
@@ -59,7 +59,7 @@ public static PhysicalChunkData Parse(ReadOnlySpan<byte> data)
5959
/// </summary>
6060
/// <param name="meta">The metadata.</param>
6161
/// <returns>The constructed PngPhysicalChunkData instance.</returns>
62-
public static PhysicalChunkData FromMetadata(ImageMetadata meta)
62+
public static PngPhysical FromMetadata(ImageMetadata meta)
6363
{
6464
byte unitSpecifier = 0;
6565
uint x;
@@ -92,7 +92,7 @@ public static PhysicalChunkData FromMetadata(ImageMetadata meta)
9292
break;
9393
}
9494

95-
return new PhysicalChunkData(x, y, unitSpecifier);
95+
return new PngPhysical(x, y, unitSpecifier);
9696
}
9797

9898
/// <summary>

src/ImageSharp/Formats/Png/PngTextData.cs renamed to src/ImageSharp/Formats/Png/Chunks/PngTextData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
namespace SixLabors.ImageSharp.Formats.Png;
4+
namespace SixLabors.ImageSharp.Formats.Png.Chunks;
55

66
/// <summary>
77
/// Stores text data contained in the iTXt, tEXt, and zTXt chunks.

src/ImageSharp/Formats/Png/MetadataExtensions.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Diagnostics.CodeAnalysis;
45
using SixLabors.ImageSharp.Formats.Png;
56
using SixLabors.ImageSharp.Metadata;
67

@@ -14,7 +15,22 @@ public static partial class MetadataExtensions
1415
/// <summary>
1516
/// Gets the png format specific metadata for the image.
1617
/// </summary>
17-
/// <param name="metadata">The metadata this method extends.</param>
18+
/// <param name="source">The metadata this method extends.</param>
1819
/// <returns>The <see cref="PngMetadata"/>.</returns>
19-
public static PngMetadata GetPngMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PngFormat.Instance);
20+
public static PngMetadata GetPngMetadata(this ImageMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
21+
22+
/// <summary>
23+
/// Gets the aPng format specific metadata for the image frame.
24+
/// </summary>
25+
/// <param name="source">The metadata this method extends.</param>
26+
/// <returns>The <see cref="PngFrameMetadata"/>.</returns>
27+
public static PngFrameMetadata GetPngFrameMetadata(this ImageFrameMetadata source) => source.GetFormatMetadata(PngFormat.Instance);
28+
29+
/// <summary>
30+
/// Gets the aPng format specific metadata for the image frame.
31+
/// </summary>
32+
/// <param name="source">The metadata this method extends.</param>
33+
/// <param name="metadata">The metadata.</param>
34+
/// <returns>The <see cref="PngFrameMetadata"/>.</returns>
35+
public static bool TryGetPngFrameMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out PngFrameMetadata? metadata) => source.TryGetFormatMetadata(PngFormat.Instance, out metadata);
2036
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Formats.Png;
5+
6+
/// <summary>
7+
/// Specifies whether the frame is to be alpha blended into the current output buffer content,
8+
/// or whether it should completely replace its region in the output buffer.
9+
/// </summary>
10+
public enum PngBlendMethod
11+
{
12+
/// <summary>
13+
/// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
14+
/// </summary>
15+
Source,
16+
17+
/// <summary>
18+
/// The frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as
19+
/// described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2].
20+
/// </summary>
21+
Over
22+
}

src/ImageSharp/Formats/Png/PngChunk.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public PngChunk(int length, PngChunkType type, IMemoryOwner<byte> data = null)
4242
/// Gets a value indicating whether the given chunk is critical to decoding
4343
/// </summary>
4444
public bool IsCritical =>
45-
this.Type == PngChunkType.Header ||
46-
this.Type == PngChunkType.Palette ||
47-
this.Type == PngChunkType.Data;
45+
this.Type is PngChunkType.Header or
46+
PngChunkType.Palette or
47+
PngChunkType.Data or
48+
PngChunkType.FrameData;
4849
}

0 commit comments

Comments
 (0)