diff --git a/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs b/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs
index 7a76e5a09..a9f99a9e4 100644
--- a/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs
+++ b/src/ImageSharp/Formats/Png/Chunks/AnimationControl.cs
@@ -5,21 +5,25 @@ using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.Png.Chunks;
-internal record AnimationControl(
- int NumberFrames,
- int NumberPlays)
+internal readonly struct AnimationControl
{
public const int Size = 8;
+ public AnimationControl(int numberFrames, int numberPlays)
+ {
+ this.NumberFrames = numberFrames;
+ this.NumberPlays = numberPlays;
+ }
+
///
/// Gets the number of frames
///
- public int NumberFrames { get; } = NumberFrames;
+ public int NumberFrames { get; }
///
/// Gets the number of times to loop this APNG. 0 indicates infinite looping.
///
- public int NumberPlays { get; } = NumberPlays;
+ public int NumberPlays { get; }
///
/// Writes the acTL to the given buffer.
@@ -38,6 +42,6 @@ internal record AnimationControl(
/// The parsed acTL.
public static AnimationControl Parse(ReadOnlySpan data)
=> new(
- NumberFrames: BinaryPrimitives.ReadInt32BigEndian(data[..4]),
- NumberPlays: BinaryPrimitives.ReadInt32BigEndian(data[4..8]));
+ numberFrames: BinaryPrimitives.ReadInt32BigEndian(data[..4]),
+ numberPlays: BinaryPrimitives.ReadInt32BigEndian(data[4..8]));
}
diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
index 5f0bc716d..0414840a8 100644
--- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
+++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
@@ -86,32 +86,32 @@ internal readonly struct FrameControl
{
if (this.XOffset < 0)
{
- throw new NotSupportedException($"Invalid XOffset. Expected >= 0. Was '{this.XOffset}'.");
+ PngThrowHelper.ThrowInvalidParameter(this.XOffset, "Expected >= 0");
}
if (this.YOffset < 0)
{
- throw new NotSupportedException($"Invalid YOffset. Expected >= 0. Was '{this.YOffset}'.");
+ PngThrowHelper.ThrowInvalidParameter(this.YOffset, "Expected >= 0");
}
if (this.Width <= 0)
{
- throw new NotSupportedException($"Invalid Width. Expected > 0. Was '{this.Width}'.");
+ PngThrowHelper.ThrowInvalidParameter(this.Width, "Expected > 0");
}
if (this.Height <= 0)
{
- throw new NotSupportedException($"Invalid Height. Expected > 0. Was '{this.Height}'.");
+ PngThrowHelper.ThrowInvalidParameter(this.Height, "Expected > 0");
}
if (this.XOffset + this.Width > hdr.Width)
{
- throw new NotSupportedException($"Invalid XOffset or Width. The sum > PngHeader.Width. Was '{this.XOffset + this.Width}'.");
+ PngThrowHelper.ThrowInvalidParameter(this.XOffset, this.Width, $"The sum > {nameof(PngHeader)}.{nameof(PngHeader.Width)}");
}
if (this.YOffset + this.Height > hdr.Height)
{
- throw new NotSupportedException($"Invalid YOffset or Height. The sum > PngHeader.Height. Was '{this.YOffset + this.Height}'.");
+ PngThrowHelper.ThrowInvalidParameter(this.YOffset, this.Height, "The sum > PngHeader.Height");
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index ac9aa5fad..618ca42df 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -34,12 +34,17 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private readonly Configuration configuration;
///
- /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
+ /// Whether the metadata should be ignored when the image is being decoded.
+ ///
+ private readonly uint maxFrames;
+
+ ///
+ /// Whether the metadata should be ignored when the image is being decoded.
///
private readonly bool skipMetadata;
///
- /// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only.
+ /// Whether to read the IHDR and tRNS chunks only.
///
private readonly bool colorMetadataOnly;
@@ -61,7 +66,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
///
/// The png animation control.
///
- private AnimationControl? animationControl;
+ private AnimationControl animationControl;
///
/// The number of bytes per pixel.
@@ -116,6 +121,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
this.Options = options;
this.configuration = options.Configuration;
+ this.maxFrames = options.MaxFrames;
this.skipMetadata = options.SkipMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
@@ -124,6 +130,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
{
this.Options = options;
this.colorMetadataOnly = colorMetadataOnly;
+ this.maxFrames = options.MaxFrames;
this.skipMetadata = true;
this.configuration = options.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
@@ -139,6 +146,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
+ uint frameCount = 0;
ImageMetadata metadata = new();
PngMetadata pngMetadata = metadata.GetPngMetadata();
this.currentStream = stream;
@@ -174,10 +182,21 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.FrameControl:
+ ++frameCount;
+ if (frameCount == this.maxFrames)
+ {
+ break;
+ }
+
currentFrame = null;
lastFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
break;
case PngChunkType.FrameData:
+ if (frameCount == this.maxFrames)
+ {
+ break;
+ }
+
if (image is null)
{
PngThrowHelper.ThrowMissingDefaultData();
@@ -290,6 +309,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
///
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
+ uint frameCount = 0;
ImageMetadata metadata = new();
PngMetadata pngMetadata = metadata.GetPngMetadata();
this.currentStream = stream;
@@ -331,9 +351,20 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.FrameControl:
+ ++frameCount;
+ if (frameCount == this.maxFrames)
+ {
+ break;
+ }
+
lastFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
break;
case PngChunkType.FrameData:
+ if (frameCount == this.maxFrames)
+ {
+ break;
+ }
+
if (this.colorMetadataOnly)
{
goto EOF;
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index da2dc6210..cc654b2e7 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -1077,7 +1077,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{
int width = this.width;
int height = this.height;
- if (pixels.Metadata.TryGetAPngFrameMetadata(out PngFrameMetadata? pngMetadata))
+ if (pixels.Metadata.TryGetPngFrameMetadata(out PngFrameMetadata? pngMetadata))
{
width = pngMetadata.Width;
height = pngMetadata.Height;
diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
index caba88792..125aa75b7 100644
--- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
+++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
@@ -22,67 +22,16 @@ internal static class PngScanlineProcessor
bool hasTrans,
L16 luminance16Trans,
L8 luminanceTrans)
- where TPixel : unmanaged, IPixel
- {
- TPixel pixel = default;
- ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
- ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
- int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1);
-
- if (!hasTrans)
- {
- if (header.BitDepth == 16)
- {
- int o = 0;
- for (nuint x = 0; x < (uint)header.Width; x++, o += 2)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- pixel.FromL16(Unsafe.As(ref luminance));
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- else
- {
- for (nuint x = 0; x < (uint)header.Width; x++)
- {
- byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor);
- pixel.FromL8(Unsafe.As(ref luminance));
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
-
- return;
- }
-
- if (header.BitDepth == 16)
- {
- La32 source = default;
- int o = 0;
- for (nuint x = 0; x < (uint)header.Width; x++, o += 2)
- {
- ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- source.L = luminance;
- source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue;
-
- pixel.FromLa32(source);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- else
- {
- La16 source = default;
- byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor);
- for (nuint x = 0; x < (uint)header.Width; x++)
- {
- byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor);
- source.L = luminance;
- source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue;
-
- pixel.FromLa16(source);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- }
+ where TPixel : unmanaged, IPixel =>
+ ProcessInterlacedGrayscaleScanline(
+ header,
+ scanlineSpan,
+ rowSpan,
+ 0,
+ 1,
+ hasTrans,
+ luminance16Trans,
+ luminanceTrans);
public static void ProcessInterlacedGrayscaleScanline(
in PngHeader header,
@@ -161,39 +110,15 @@ internal static class PngScanlineProcessor
Span rowSpan,
uint bytesPerPixel,
uint bytesPerSample)
- where TPixel : unmanaged, IPixel
- {
- TPixel pixel = default;
- ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
- ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
-
- if (header.BitDepth == 16)
- {
- La32 source = default;
- int o = 0;
- for (nuint x = 0; x < (uint)header.Width; x++, o += 4)
- {
- source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
- source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
-
- pixel.FromLa32(source);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- else
- {
- La16 source = default;
- for (nuint x = 0; x < (uint)header.Width; x++)
- {
- nuint offset = x * bytesPerPixel;
- source.L = Unsafe.Add(ref scanlineSpanRef, offset);
- source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample);
-
- pixel.FromLa16(source);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- }
+ where TPixel : unmanaged, IPixel =>
+ ProcessInterlacedGrayscaleWithAlphaScanline(
+ header,
+ scanlineSpan,
+ rowSpan,
+ 0,
+ 1,
+ bytesPerPixel,
+ bytesPerSample);
public static void ProcessInterlacedGrayscaleWithAlphaScanline(
in PngHeader header,
@@ -244,48 +169,14 @@ internal static class PngScanlineProcessor
Span rowSpan,
ReadOnlySpan palette,
byte[] paletteAlpha)
- where TPixel : unmanaged, IPixel
- {
- if (palette.IsEmpty)
- {
- PngThrowHelper.ThrowMissingPalette();
- }
-
- TPixel pixel = default;
- ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
- ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
- ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette);
- ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels);
-
- if (paletteAlpha?.Length > 0)
- {
- // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
- // channel and we should try to read it.
- Rgba32 rgba = default;
- ref byte paletteAlphaRef = ref MemoryMarshal.GetArrayDataReference(paletteAlpha);
-
- for (nuint x = 0; x < (uint)header.Width; x++)
- {
- uint index = Unsafe.Add(ref scanlineSpanRef, x);
- rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
- rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue;
-
- pixel.FromRgba32(rgba);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- else
- {
- for (nuint x = 0; x < (uint)header.Width; x++)
- {
- int index = Unsafe.Add(ref scanlineSpanRef, x);
- Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index);
-
- pixel.FromRgb24(rgb);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- }
+ where TPixel : unmanaged, IPixel =>
+ ProcessInterlacedPaletteScanline(header,
+ scanlineSpan,
+ rowSpan,
+ 0,
+ 1,
+ palette,
+ paletteAlpha);
public static void ProcessInterlacedPaletteScanline(
in PngHeader header,
@@ -297,6 +188,11 @@ internal static class PngScanlineProcessor
byte[] paletteAlpha)
where TPixel : unmanaged, IPixel
{
+ if (palette.IsEmpty)
+ {
+ PngThrowHelper.ThrowMissingPalette();
+ }
+
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@@ -347,9 +243,9 @@ internal static class PngScanlineProcessor
TPixel pixel = default;
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
- if (!hasTrans)
+ if (header.BitDepth == 16)
{
- if (header.BitDepth == 16)
+ if (!hasTrans)
{
Rgb48 rgb48 = default;
int o = 0;
@@ -365,31 +261,27 @@ internal static class PngScanlineProcessor
}
else
{
- PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width);
+ Rgb48 rgb48 = default;
+ Rgba64 rgba64 = default;
+ int o = 0;
+ for (nuint x = 0; x < (uint)header.Width; x++, o += bytesPerPixel)
+ {
+ rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
+ rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
+ rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
+
+ rgba64.Rgb = rgb48;
+ rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
+
+ pixel.FromRgba64(rgba64);
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
+ }
}
return;
}
- if (header.BitDepth == 16)
- {
- Rgb48 rgb48 = default;
- Rgba64 rgba64 = default;
- int o = 0;
- for (nuint x = 0; x < (uint)header.Width; x++, o += bytesPerPixel)
- {
- rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
- rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
- rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
-
- rgba64.Rgb = rgb48;
- rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue;
-
- pixel.FromRgba64(rgba64);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- else
+ if (hasTrans)
{
Rgba32 rgba32 = default;
ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan);
@@ -404,6 +296,10 @@ internal static class PngScanlineProcessor
Unsafe.Add(ref rowSpanRef, x) = pixel;
}
}
+ else
+ {
+ PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width);
+ }
}
public static void ProcessInterlacedRgbScanline(
diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
index 78c243eee..0552e9a79 100644
--- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs
+++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Png;
@@ -38,6 +39,14 @@ internal static class PngThrowHelper
[DoesNotReturn]
public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
+ [DoesNotReturn]
+ public static void ThrowInvalidParameter(object value, string message, [CallerArgumentExpression(nameof(value))] string name = "")
+ => throw new NotSupportedException($"Invalid {name}. {message}. Was '{value}'.");
+
+ [DoesNotReturn]
+ public static void ThrowInvalidParameter(object value1, object value2, string message, [CallerArgumentExpression(nameof(value1))] string name1 = "", [CallerArgumentExpression(nameof(value1))] string name2 = "")
+ => throw new NotSupportedException($"Invalid {name1} or {name2}. {message}. Was '{value1}' and '{value2}'.");
+
[DoesNotReturn]
public static void ThrowNotSupportedColor() => throw new NotSupportedException("Unsupported PNG color type.");