diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
index bb75cbabf..c7233ada1 100644
--- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
+++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
@@ -10,22 +10,22 @@ internal readonly struct FrameControl
public const int Size = 26;
public FrameControl(
- int sequenceNumber,
- int width,
- int height,
- int xOffset,
- int yOffset,
- short delayNumber,
- short delayDenominator,
- PngDisposeOperation disposeOperation,
- PngBlendOperation blendOperation)
+ uint sequenceNumber,
+ uint width,
+ uint height,
+ uint xOffset,
+ uint yOffset,
+ ushort delayNumerator,
+ ushort delayDenominator,
+ PngDisposalMethod disposeOperation,
+ PngBlendMethod blendOperation)
{
this.SequenceNumber = sequenceNumber;
this.Width = width;
this.Height = height;
this.XOffset = xOffset;
this.YOffset = yOffset;
- this.DelayNumber = delayNumber;
+ this.DelayNumerator = delayNumerator;
this.DelayDenominator = delayDenominator;
this.DisposeOperation = disposeOperation;
this.BlendOperation = blendOperation;
@@ -34,130 +34,101 @@ internal readonly struct FrameControl
///
/// Gets the sequence number of the animation chunk, starting from 0
///
- public int SequenceNumber { get; }
+ public uint SequenceNumber { get; }
///
/// Gets the width of the following frame
///
- public int Width { get; }
+ public uint Width { get; }
///
/// Gets the height of the following frame
///
- public int Height { get; }
+ public uint Height { get; }
///
/// Gets the X position at which to render the following frame
///
- public int XOffset { get; }
+ public uint XOffset { get; }
///
/// Gets the Y position at which to render the following frame
///
- public int YOffset { get; }
+ public uint YOffset { get; }
///
/// Gets the X limit at which to render the following frame
///
- public uint XLimit => (uint)(this.XOffset + this.Width);
+ public uint XMax => this.XOffset + this.Width;
///
/// Gets the Y limit at which to render the following frame
///
- public uint YLimit => (uint)(this.YOffset + this.Height);
+ public uint YMax => this.YOffset + this.Height;
///
/// Gets the frame delay fraction numerator
///
- public short DelayNumber { get; }
+ public ushort DelayNumerator { get; }
///
/// Gets the frame delay fraction denominator
///
- public short DelayDenominator { get; }
+ public ushort DelayDenominator { get; }
///
/// Gets the type of frame area disposal to be done after rendering this frame
///
- public PngDisposeOperation DisposeOperation { get; }
+ public PngDisposalMethod DisposeOperation { get; }
///
/// Gets the type of frame area rendering for this frame
///
- public PngBlendOperation BlendOperation { get; }
+ public PngBlendMethod BlendOperation { get; }
///
/// Validates the APng fcTL.
///
+ /// The header.
///
/// Thrown if the image does pass validation.
///
- public void Validate(PngHeader hdr)
+ public void Validate(PngHeader header)
{
- if (this.XOffset < 0)
- {
- PngThrowHelper.ThrowInvalidParameter(this.XOffset, "Expected >= 0");
- }
-
- if (this.YOffset < 0)
- {
- PngThrowHelper.ThrowInvalidParameter(this.YOffset, "Expected >= 0");
- }
-
- if (this.Width <= 0)
+ if (this.Width == 0)
{
PngThrowHelper.ThrowInvalidParameter(this.Width, "Expected > 0");
}
- if (this.Height <= 0)
+ if (this.Height == 0)
{
PngThrowHelper.ThrowInvalidParameter(this.Height, "Expected > 0");
}
- if (this.XLimit > hdr.Width)
+ if (this.XMax > header.Width)
{
- PngThrowHelper.ThrowInvalidParameter(this.XOffset, this.Width, $"The sum of them > {nameof(PngHeader)}.{nameof(PngHeader.Width)}");
+ PngThrowHelper.ThrowInvalidParameter(this.XOffset, this.Width, $"The x-offset plus width > {nameof(PngHeader)}.{nameof(PngHeader.Width)}");
}
- if (this.YLimit > hdr.Height)
+ if (this.YMax > header.Height)
{
- PngThrowHelper.ThrowInvalidParameter(this.YOffset, this.Height, $"The sum of them > {nameof(PngHeader)}.{nameof(PngHeader.Height)}");
+ PngThrowHelper.ThrowInvalidParameter(this.YOffset, this.Height, $"The y-offset plus height > {nameof(PngHeader)}.{nameof(PngHeader.Height)}");
}
}
- ///
- /// Parses the APngFrameControl from the given metadata.
- ///
- /// The metadata to parse.
- /// Sequence number.
- public static FrameControl FromMetadata(PngFrameMetadata frameMetadata, int sequenceNumber)
- {
- FrameControl fcTL = new(
- sequenceNumber,
- frameMetadata.Width,
- frameMetadata.Height,
- frameMetadata.XOffset,
- frameMetadata.YOffset,
- frameMetadata.DelayNumber,
- frameMetadata.DelayDenominator,
- frameMetadata.DisposeOperation,
- frameMetadata.BlendOperation);
- return fcTL;
- }
-
///
/// Writes the fcTL to the given buffer.
///
/// The buffer to write to.
public void WriteTo(Span buffer)
{
- BinaryPrimitives.WriteInt32BigEndian(buffer[..4], this.SequenceNumber);
- BinaryPrimitives.WriteInt32BigEndian(buffer[4..8], this.Width);
- BinaryPrimitives.WriteInt32BigEndian(buffer[8..12], this.Height);
- BinaryPrimitives.WriteInt32BigEndian(buffer[12..16], this.XOffset);
- BinaryPrimitives.WriteInt32BigEndian(buffer[16..20], this.YOffset);
- BinaryPrimitives.WriteInt16BigEndian(buffer[20..22], this.DelayNumber);
- BinaryPrimitives.WriteInt16BigEndian(buffer[22..24], this.DelayDenominator);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer[..4], this.SequenceNumber);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer[4..8], this.Width);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer[8..12], this.Height);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer[12..16], this.XOffset);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer[16..20], this.YOffset);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer[20..22], this.DelayNumerator);
+ BinaryPrimitives.WriteUInt16BigEndian(buffer[22..24], this.DelayDenominator);
buffer[24] = (byte)this.DisposeOperation;
buffer[25] = (byte)this.BlendOperation;
@@ -170,13 +141,13 @@ internal readonly struct FrameControl
/// The parsed fcTL.
public static FrameControl Parse(ReadOnlySpan data)
=> new(
- sequenceNumber: BinaryPrimitives.ReadInt32BigEndian(data[..4]),
- width: BinaryPrimitives.ReadInt32BigEndian(data[4..8]),
- height: BinaryPrimitives.ReadInt32BigEndian(data[8..12]),
- xOffset: BinaryPrimitives.ReadInt32BigEndian(data[12..16]),
- yOffset: BinaryPrimitives.ReadInt32BigEndian(data[16..20]),
- delayNumber: BinaryPrimitives.ReadInt16BigEndian(data[20..22]),
- delayDenominator: BinaryPrimitives.ReadInt16BigEndian(data[22..24]),
- disposeOperation: (PngDisposeOperation)data[24],
- blendOperation: (PngBlendOperation)data[25]);
+ sequenceNumber: BinaryPrimitives.ReadUInt32BigEndian(data[..4]),
+ width: BinaryPrimitives.ReadUInt32BigEndian(data[4..8]),
+ height: BinaryPrimitives.ReadUInt32BigEndian(data[8..12]),
+ xOffset: BinaryPrimitives.ReadUInt32BigEndian(data[12..16]),
+ yOffset: BinaryPrimitives.ReadUInt32BigEndian(data[16..20]),
+ delayNumerator: BinaryPrimitives.ReadUInt16BigEndian(data[20..22]),
+ delayDenominator: BinaryPrimitives.ReadUInt16BigEndian(data[22..24]),
+ disposeOperation: (PngDisposalMethod)data[24],
+ blendOperation: (PngBlendMethod)data[25]);
}
diff --git a/src/ImageSharp/Formats/Png/PngBlendOperation.cs b/src/ImageSharp/Formats/Png/PngBlendMethod.cs
similarity index 93%
rename from src/ImageSharp/Formats/Png/PngBlendOperation.cs
rename to src/ImageSharp/Formats/Png/PngBlendMethod.cs
index b8a84a933..b7ace9ccf 100644
--- a/src/ImageSharp/Formats/Png/PngBlendOperation.cs
+++ b/src/ImageSharp/Formats/Png/PngBlendMethod.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
///
/// Specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer.
///
-public enum PngBlendOperation
+public enum PngBlendMethod
{
///
/// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 23942dd98..deb01289e 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -152,7 +152,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream = stream;
this.currentStream.Skip(8);
Image? image = null;
- FrameControl? lastFrameControl = null;
+ FrameControl? previousFrameControl = null;
ImageFrame? currentFrame = null;
Span buffer = stackalloc byte[20];
@@ -182,14 +182,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.FrameControl:
- ++frameCount;
+ frameCount++;
if (frameCount == this.maxFrames)
{
break;
}
currentFrame = null;
- lastFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
+ previousFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan());
break;
case PngChunkType.FrameData:
if (frameCount == this.maxFrames)
@@ -202,33 +202,32 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngThrowHelper.ThrowMissingDefaultData();
}
- if (lastFrameControl is null)
+ if (previousFrameControl is null)
{
PngThrowHelper.ThrowMissingFrameControl();
}
if (currentFrame is null)
{
- this.InitializeFrame(lastFrameControl.Value, image, out currentFrame);
+ this.InitializeFrame(previousFrameControl.Value, image, out currentFrame);
}
this.currentStream.Position += 4;
- this.ReadScanlines(chunk.Length - 4, currentFrame, pngMetadata, this.ReadNextDataChunkAndSkipSeq, lastFrameControl.Value, cancellationToken);
- lastFrameControl = null;
+ this.ReadScanlines(chunk.Length - 4, currentFrame, pngMetadata, this.ReadNextDataChunkAndSkipSeq, previousFrameControl.Value, cancellationToken);
+ previousFrameControl = null;
break;
case PngChunkType.Data:
if (image is null)
{
- this.InitializeImage(metadata, lastFrameControl, out image);
+ this.InitializeImage(metadata, previousFrameControl, out image);
// Both PLTE and tRNS chunks, if present, have been read at this point as per spec.
AssignColorPalette(this.palette, this.paletteAlpha, pngMetadata);
}
- FrameControl frameControl = lastFrameControl ?? new(0, this.header.Width, this.header.Height, 0, 0, 0, 0, default, default);
-
- this.ReadScanlines(chunk.Length, image.Frames.RootFrame, pngMetadata, this.ReadNextDataChunk, frameControl, cancellationToken);
- lastFrameControl = null;
+ FrameControl frameControl = previousFrameControl ?? new(0, (uint)this.header.Width, (uint)this.header.Height, 0, 0, 0, 0, default, default);
+ this.ReadScanlines(chunk.Length, image.Frames.RootFrame, pngMetadata, this.ReadNextDataChunk, in frameControl, cancellationToken);
+ previousFrameControl = null;
break;
case PngChunkType.Palette:
this.palette = chunk.Data.GetSpan().ToArray();
@@ -705,9 +704,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private void DecodePixelData(FrameControl frameControl, DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- int currentRow = frameControl.YOffset;
+ int currentRow = (int)frameControl.YOffset;
int currentRowBytesRead = 0;
- int height = frameControl.Height;
+ int height = (int)frameControl.YMax;
while (currentRow < height)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -771,11 +770,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private void DecodeInterlacedPixelData(in FrameControl frameControl, DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- int currentRow = Adam7.FirstRow[0] + frameControl.YOffset;
+ int currentRow = Adam7.FirstRow[0] + (int)frameControl.YOffset;
int currentRowBytesRead = 0;
int pass = 0;
- int width = frameControl.Width;
- int height = frameControl.Height;
+ int width = (int)frameControl.Width;
+ int endRow = (int)frameControl.YMax;
Buffer2D imageBuffer = image.PixelBuffer;
while (true)
@@ -792,7 +791,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1;
- while (currentRow < height)
+ while (currentRow < endRow)
{
cancellationToken.ThrowIfCancellationRequested();
while (currentRowBytesRead < bytesPerInterlaceScanline)
@@ -894,7 +893,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessGrayscaleScanline(
this.header.BitDepth,
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
pngMetadata.TransparentColor);
@@ -904,7 +903,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline(
this.header.BitDepth,
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
(uint)this.bytesPerPixel,
@@ -914,7 +913,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.Palette:
PngScanlineProcessor.ProcessPaletteScanline(
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
pngMetadata.ColorTable);
@@ -923,6 +922,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline(
+ this.configuration,
this.header.BitDepth,
frameControl,
scanlineSpan,
@@ -935,8 +935,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline(
+ this.configuration,
this.header.BitDepth,
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
this.bytesPerPixel,
@@ -984,7 +985,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
this.header.BitDepth,
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
(uint)pixelOffset,
@@ -996,7 +997,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
this.header.BitDepth,
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
(uint)pixelOffset,
@@ -1008,7 +1009,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.Palette:
PngScanlineProcessor.ProcessInterlacedPaletteScanline(
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
(uint)pixelOffset,
@@ -1019,8 +1020,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.Rgb:
PngScanlineProcessor.ProcessInterlacedRgbScanline(
+ this.configuration,
this.header.BitDepth,
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
(uint)pixelOffset,
@@ -1033,8 +1035,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessInterlacedRgbaScanline(
+ this.configuration,
this.header.BitDepth,
- frameControl,
+ in frameControl,
scanlineSpan,
rowSpan,
(uint)pixelOffset,
diff --git a/src/ImageSharp/Formats/Png/PngDisposeOperation.cs b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs
similarity index 92%
rename from src/ImageSharp/Formats/Png/PngDisposeOperation.cs
rename to src/ImageSharp/Formats/Png/PngDisposalMethod.cs
index 17a509125..17391de95 100644
--- a/src/ImageSharp/Formats/Png/PngDisposeOperation.cs
+++ b/src/ImageSharp/Formats/Png/PngDisposalMethod.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
///
/// Specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
///
-public enum PngDisposeOperation
+public enum PngDisposalMethod
{
///
/// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 0eabeeb85..6c86d1b10 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -111,6 +111,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
///
private const string ColorProfileName = "ICC Profile";
+ ///
+ /// The encoder quantizer, if present.
+ ///
+ private IQuantizer? quantizer;
+
///
/// Initializes a new instance of the class.
///
@@ -121,6 +126,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.encoder = encoder;
+ this.quantizer = encoder.Quantizer;
}
///
@@ -140,63 +146,81 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
this.height = image.Height;
ImageMetadata metadata = image.Metadata;
-
PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);
this.SanitizeAndSetEncoderOptions(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
- Image? clonedImage = null;
- Image targetImage = image;
+
+ stream.Write(PngConstants.HeaderBytes);
+ this.WriteHeaderChunk(stream);
+ this.WriteGammaChunk(stream);
+ this.WriteColorProfileChunk(stream, metadata);
+
+ ImageFrame? clonedFrame = null;
+ ImageFrame currentFrame = image.Frames.RootFrame;
+
bool clearTransparency = this.encoder.TransparentColorMode is PngTransparentColorMode.Clear;
if (clearTransparency)
{
- targetImage = clonedImage = image.Clone();
- ClearTransparentPixels(targetImage);
+ currentFrame = clonedFrame = currentFrame.Clone();
+ ClearTransparentPixels(currentFrame);
}
- IndexedImageFrame? rootQuantized = this.CreateQuantizedImageAndUpdateBitDepth(targetImage.Frames.RootFrame);
+ IndexedImageFrame? quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, currentFrame, null);
+ this.WritePaletteChunk(stream, quantized);
- stream.Write(PngConstants.HeaderBytes);
-
- this.WriteHeaderChunk(stream);
- this.WriteGammaChunk(stream);
- this.WriteColorProfileChunk(stream, metadata);
- this.WritePaletteChunk(stream, rootQuantized);
this.WriteTransparencyChunk(stream, pngMetadata);
this.WritePhysicalChunk(stream, metadata);
this.WriteExifChunk(stream, metadata);
this.WriteXmpChunk(stream, metadata);
this.WriteTextChunks(stream, pngMetadata);
- if (targetImage.Frames.Count > 1)
+ if (image.Frames.Count > 1)
{
- this.WriteAnimationControlChunk(stream, targetImage.Frames.Count, pngMetadata.NumberPlays);
+ this.WriteAnimationControlChunk(stream, image.Frames.Count, pngMetadata.NumberPlays);
+
+ // TODO: We should attempt to optimize the output by clipping the indexed result to
+ // non-transparent bounds. That way we can assign frame control bounds and encode
+ // less data. See GifEncoder for the implementation there.
- FrameControl frameControl = this.WriteFrameControlChunk(stream, targetImage.Frames.RootFrame.Metadata.GetPngFrameMetadata(), 0);
- _ = this.WriteDataChunks(frameControl, targetImage.Frames.RootFrame, rootQuantized, stream, false);
+ // Write the first frame.
+ FrameControl frameControl = this.WriteFrameControlChunk(stream, currentFrame, 0);
+ this.WriteDataChunks(frameControl, currentFrame, quantized, stream, false);
- int index = 1;
+ // Capture the global palette for reuse on subsequent frames.
+ ReadOnlyMemory? previousPalette = quantized?.Palette.ToArray();
- foreach (ImageFrame imageFrame in ((IEnumerable>)targetImage.Frames).Skip(1))
+ // Write following frames.
+ for (int i = 1; i < image.Frames.Count; i++)
{
- frameControl = this.WriteFrameControlChunk(stream, imageFrame.Metadata.GetPngFrameMetadata(), index);
- index++;
- IndexedImageFrame? quantized = this.CreateQuantizedImageAndUpdateBitDepth(imageFrame);
- index += this.WriteDataChunks(frameControl, imageFrame, quantized, stream, true);
+ currentFrame = image.Frames[i];
+ if (clearTransparency)
+ {
+ // Dispose of previous clone and reassign.
+ clonedFrame?.Dispose();
+ currentFrame = clonedFrame = currentFrame.Clone();
+ ClearTransparentPixels(currentFrame);
+ }
+
+ frameControl = this.WriteFrameControlChunk(stream, currentFrame, (uint)i);
+
+ // Dispose of previous quantized frame and reassign.
quantized?.Dispose();
+ quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, currentFrame, previousPalette);
+ this.WriteDataChunks(frameControl, currentFrame, quantized, stream, true);
}
}
else
{
- FrameControl frameControl = new(0, this.width, this.height, 0, 0, 0, 0, default, default);
- _ = this.WriteDataChunks(frameControl, targetImage.Frames.RootFrame, rootQuantized, stream, false);
- rootQuantized?.Dispose();
+ FrameControl frameControl = new(0, (uint)this.width, (uint)this.height, 0, 0, 0, 0, default, default);
+ this.WriteDataChunks(frameControl, currentFrame, quantized, stream, false);
}
this.WriteEndChunk(stream);
stream.Flush();
- clonedImage?.Dispose();
- rootQuantized?.Dispose();
+ // Dispose of allocations from final frame.
+ clonedFrame?.Dispose();
+ quantized?.Dispose();
}
///
@@ -210,46 +234,44 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases.
///
/// The type of the pixel.
- /// The cloned image where the transparent pixels will be changed.
- private static void ClearTransparentPixels(Image image)
+ /// The cloned image frame where the transparent pixels will be changed.
+ private static void ClearTransparentPixels(ImageFrame clone)
where TPixel : unmanaged, IPixel
- {
- foreach (ImageFrame imageFrame in image.Frames)
+ => clone.ProcessPixelRows(accessor =>
{
- imageFrame.ProcessPixelRows(accessor =>
+ // TODO: We should be able to speed this up with SIMD and masking.
+ Rgba32 rgba32 = default;
+ Rgba32 transparent = Color.Transparent;
+ for (int y = 0; y < accessor.Height; y++)
{
- // TODO: We should be able to speed this up with SIMD and masking.
- Rgba32 rgba32 = default;
- Rgba32 transparent = Color.Transparent;
- for (int y = 0; y < accessor.Height; y++)
+ Span span = accessor.GetRowSpan(y);
+ for (int x = 0; x < accessor.Width; x++)
{
- Span span = accessor.GetRowSpan(y);
- for (int x = 0; x < accessor.Width; x++)
- {
- span[x].ToRgba32(ref rgba32);
+ span[x].ToRgba32(ref rgba32);
- if (rgba32.A is 0)
- {
- span[x].FromRgba32(transparent);
- }
+ if (rgba32.A is 0)
+ {
+ span[x].FromRgba32(transparent);
}
}
- });
- }
- }
+ }
+ });
///
/// Creates the quantized image and calculates and sets the bit depth.
///
/// The type of the pixel.
+ /// The image metadata.
/// The frame to quantize.
+ /// Any previously derived palette.
/// The quantized image.
private IndexedImageFrame? CreateQuantizedImageAndUpdateBitDepth(
- ImageFrame frame)
+ PngMetadata metadata,
+ ImageFrame frame,
+ ReadOnlyMemory? previousPalette)
where TPixel : unmanaged, IPixel
{
- IndexedImageFrame? quantized = CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, frame);
-
+ IndexedImageFrame? quantized = this.CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, metadata, frame, previousPalette);
this.bitDepth = CalculateBitDepth(this.colorType, this.bitDepth, quantized);
return quantized;
}
@@ -914,7 +936,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
}
Span alpha = this.chunkDataBuffer.Span;
- switch (pngMetadata.ColorType)
+ if (pngMetadata.ColorType == PngColorType.Rgb)
{
if (this.use16Bit)
{
@@ -957,11 +979,23 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// Writes the animation control chunk to the stream.
///
/// The containing image data.
- /// Provides APng specific metadata information for the image frame.
- /// Sequence number.
- private FrameControl WriteFrameControlChunk(Stream stream, PngFrameMetadata frameMetadata, int sequenceNumber)
+ /// The image frame.
+ /// The frame sequence number.
+ private FrameControl WriteFrameControlChunk(Stream stream, ImageFrame imageFrame, uint sequenceNumber)
{
- FrameControl fcTL = FrameControl.FromMetadata(frameMetadata, sequenceNumber);
+ PngFrameMetadata frameMetadata = imageFrame.Metadata.GetPngFrameMetadata();
+
+ // TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
+ FrameControl fcTL = new(
+ sequenceNumber: sequenceNumber,
+ width: (uint)imageFrame.Width,
+ height: (uint)imageFrame.Height,
+ xOffset: 0,
+ yOffset: 0,
+ delayNumerator: frameMetadata.DelayNumerator,
+ delayDenominator: frameMetadata.DelayDenominator,
+ disposeOperation: frameMetadata.DisposalMethod,
+ blendOperation: frameMetadata.BlendMethod);
fcTL.WriteTo(this.chunkDataBuffer.Span);
@@ -1036,11 +1070,8 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
if (isFrame)
{
- byte[] chunkBuffer = new byte[MaxBlockSize];
- BinaryPrimitives.WriteInt32BigEndian(chunkBuffer, frameControl.SequenceNumber + 1 + i);
- buffer.AsSpan().Slice(i * maxBlockSize, length).CopyTo(chunkBuffer.AsSpan(4, length));
-
- this.WriteChunk(stream, PngChunkType.FrameData, chunkBuffer, 0, length + 4);
+ uint sequenceNumber = (uint)(frameControl.SequenceNumber + i);
+ this.WriteFrameDataChunk(stream, sequenceNumber, buffer, i * maxBlockSize, length);
}
else
{
@@ -1075,8 +1106,8 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private void EncodePixels(FrameControl frameControl, ImageFrame pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel
{
- int width = frameControl.Width;
- int height = frameControl.Height;
+ int width = (int)frameControl.Width;
+ int height = (int)frameControl.Height;
int bytesPerScanline = this.CalculateScanlineLength(width);
int filterLength = bytesPerScanline + 1;
@@ -1089,7 +1120,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{
Span filter = filterBuffer.GetSpan();
Span attempt = attemptBuffer.GetSpan();
- for (int y = frameControl.YOffset; y < frameControl.YLimit; y++)
+ for (int y = (int)frameControl.YOffset; y < frameControl.YMax; y++)
{
this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y);
deflateStream.Write(filter);
@@ -1108,13 +1139,13 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private void EncodeAdam7Pixels(FrameControl frameControl, ImageFrame frame, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel
{
- int width = frameControl.Width;
- int height = frameControl.Height;
+ int width = (int)frameControl.XMax;
+ int height = (int)frameControl.YMax;
Buffer2D pixelBuffer = frame.PixelBuffer;
for (int pass = 0; pass < 7; pass++)
{
- int startRow = Adam7.FirstRow[pass] + frameControl.YOffset;
- int startCol = Adam7.FirstColumn[pass] + frameControl.XOffset;
+ int startRow = Adam7.FirstRow[pass] + (int)frameControl.YOffset;
+ int startCol = Adam7.FirstColumn[pass] + (int)frameControl.XOffset;
int blockWidth = Adam7.ComputeBlockWidth(width, pass);
int bytesPerScanline = this.bytesPerPixel <= 1
@@ -1132,11 +1163,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
Span filter = filterBuffer.GetSpan();
Span attempt = attemptBuffer.GetSpan();
- for (int row = startRow; row < frameControl.YLimit; row += Adam7.RowIncrement[pass])
+ for (int row = startRow; row < height; row += Adam7.RowIncrement[pass])
{
// Collect pixel data
Span srcRow = pixelBuffer.DangerousGetRowSpan(row);
- for (int col = startCol, i = 0; col < frameControl.XLimit; col += Adam7.ColumnIncrement[pass])
+ for (int col = startCol, i = 0; col < frameControl.XMax; col += Adam7.ColumnIncrement[pass])
{
block[i++] = srcRow[col];
}
@@ -1162,12 +1193,12 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
private void EncodeAdam7IndexedPixels(FrameControl frameControl, IndexedImageFrame quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel
{
- int width = frameControl.Width;
- int height = frameControl.Height;
+ int width = (int)frameControl.Width;
+ int endRow = (int)frameControl.YMax;
for (int pass = 0; pass < 7; pass++)
{
- int startRow = Adam7.FirstRow[pass] + frameControl.YOffset;
- int startCol = Adam7.FirstColumn[pass] + frameControl.XOffset;
+ int startRow = Adam7.FirstRow[pass] + (int)frameControl.YOffset;
+ int startCol = Adam7.FirstColumn[pass] + (int)frameControl.XOffset;
int blockWidth = Adam7.ComputeBlockWidth(width, pass);
int bytesPerScanline = this.bytesPerPixel <= 1
@@ -1186,14 +1217,12 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
Span filter = filterBuffer.GetSpan();
Span attempt = attemptBuffer.GetSpan();
- for (int row = startRow;
- row < frameControl.YLimit;
- row += Adam7.RowIncrement[pass])
+ for (int row = startRow; row < endRow; row += Adam7.RowIncrement[pass])
{
// Collect data
ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row);
for (int col = startCol, i = 0;
- col < frameControl.XLimit;
+ col < frameControl.XMax;
col += Adam7.ColumnIncrement[pass])
{
block[i] = srcRow[col];
@@ -1229,7 +1258,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
///
/// The to write to.
/// The type of chunk to write.
- /// The containing data.
+ /// The containing data.
/// The position to offset the data at.
/// The of the data to write.
private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length)
@@ -1255,6 +1284,38 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
stream.Write(buffer, 0, 4); // write the crc
}
+ ///
+ /// Writes a frame data chunk of a specified length to the stream at the given offset.
+ ///
+ /// The to write to.
+ /// The frame sequence number.
+ /// The containing data.
+ /// The position to offset the data at.
+ /// The of the data to write.
+ private void WriteFrameDataChunk(Stream stream, uint sequenceNumber, Span data, int offset, int length)
+ {
+ Span buffer = stackalloc byte[12];
+
+ BinaryPrimitives.WriteInt32BigEndian(buffer, length + 4);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), (uint)PngChunkType.FrameData);
+ BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(8, 4), sequenceNumber);
+
+ stream.Write(buffer);
+
+ uint crc = Crc32.Calculate(buffer[4..]); // Write the type buffer
+
+ if (data.Length > 0 && length > 0)
+ {
+ stream.Write(data, offset, length);
+
+ crc = Crc32.Calculate(crc, data.Slice(offset, length));
+ }
+
+ BinaryPrimitives.WriteUInt32BigEndian(buffer, crc);
+
+ stream.Write(buffer, 0, 4); // write the crc
+ }
+
///
/// Calculates the scanline length.
///
@@ -1335,12 +1396,16 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// The png encoder.
/// The color type.
/// The bits per component.
- /// The frame.
- private static IndexedImageFrame? CreateQuantizedFrame(
+ /// The image metadata.
+ /// The frame to quantize.
+ /// Any previously derived palette.
+ private IndexedImageFrame? CreateQuantizedFrame(
QuantizingImageEncoder encoder,
PngColorType colorType,
byte bitDepth,
- ImageFrame frame)
+ PngMetadata metadata,
+ ImageFrame frame,
+ ReadOnlyMemory? previousPalette)
where TPixel : unmanaged, IPixel
{
if (colorType is not PngColorType.Palette)
@@ -1348,25 +1413,30 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
return null;
}
+ if (previousPalette is not null)
+ {
+ // Use the previously derived palette created by quantizing the root frame to quantize the current frame.
+ using PaletteQuantizer paletteQuantizer = new(this.configuration, this.quantizer!.Options, previousPalette.Value, -1);
+ paletteQuantizer.BuildPalette(encoder.PixelSamplingStrategy, frame);
+ return paletteQuantizer.QuantizeFrame(frame, frame.Bounds());
+ }
+
// Use the metadata to determine what quantization depth to use if no quantizer has been set.
- IQuantizer quantizer = encoder.Quantizer;
- if (quantizer is null)
+ if (this.quantizer is null)
{
- // TODO: Can APNG have per-frame color tables?
- PngMetadata metadata = image.Metadata.GetPngMetadata();
if (metadata.ColorTable is not null)
{
- // Use the provided palette in total. The caller is responsible for setting values.
- quantizer = new PaletteQuantizer(metadata.ColorTable.Value);
+ // Use the provided palette. The caller is responsible for setting values.
+ this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value);
}
else
{
- quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) });
+ this.quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = ColorNumerics.GetColorCountForBitDepth(bitDepth) });
}
}
// Create quantized frame returning the palette and set the bit depth.
- using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(frame.Configuration);
+ using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(frame.Configuration);
frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, frame);
return frameQuantizer.QuantizeFrame(frame, frame.Bounds());
diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
index 76d433056..a68d45ae0 100644
--- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
@@ -23,55 +23,31 @@ public class PngFrameMetadata : IDeepCloneable
/// The metadata to create an instance from.
private PngFrameMetadata(PngFrameMetadata other)
{
- this.Width = other.Width;
- this.Height = other.Height;
- this.XOffset = other.XOffset;
- this.YOffset = other.YOffset;
- this.DelayNumber = other.DelayNumber;
+ this.DelayNumerator = other.DelayNumerator;
this.DelayDenominator = other.DelayDenominator;
- this.DisposeOperation = other.DisposeOperation;
- this.BlendOperation = other.BlendOperation;
+ this.DisposalMethod = other.DisposalMethod;
+ this.BlendMethod = other.BlendMethod;
}
- ///
- /// Gets or sets the width of the following frame
- ///
- public int Width { get; set; }
-
- ///
- /// Gets or sets the height of the following frame
- ///
- public int Height { get; set; }
-
- ///
- /// Gets or sets the X position at which to render the following frame
- ///
- public int XOffset { get; set; }
-
- ///
- /// Gets or sets the Y position at which to render the following frame
- ///
- public int YOffset { get; set; }
-
///
/// Gets or sets the frame delay fraction numerator
///
- public short DelayNumber { get; set; }
+ public ushort DelayNumerator { get; set; }
///
/// Gets or sets the frame delay fraction denominator
///
- public short DelayDenominator { get; set; }
+ public ushort DelayDenominator { get; set; }
///
/// Gets or sets the type of frame area disposal to be done after rendering this frame
///
- public PngDisposeOperation DisposeOperation { get; set; }
+ public PngDisposalMethod DisposalMethod { get; set; }
///
/// Gets or sets the type of frame area rendering for this frame
///
- public PngBlendOperation BlendOperation { get; set; }
+ public PngBlendMethod BlendMethod { get; set; }
///
/// Initializes a new instance of the class.
@@ -79,14 +55,10 @@ public class PngFrameMetadata : IDeepCloneable
/// The chunk to create an instance from.
internal void FromChunk(FrameControl frameControl)
{
- this.Width = frameControl.Width;
- this.Height = frameControl.Height;
- this.XOffset = frameControl.XOffset;
- this.YOffset = frameControl.YOffset;
- this.DelayNumber = frameControl.DelayNumber;
+ this.DelayNumerator = frameControl.DelayNumerator;
this.DelayDenominator = frameControl.DelayDenominator;
- this.DisposeOperation = frameControl.DisposeOperation;
- this.BlendOperation = frameControl.BlendOperation;
+ this.DisposalMethod = frameControl.DisposeOperation;
+ this.BlendMethod = frameControl.BlendOperation;
}
///
diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
index 9d219e1de..31a59188e 100644
--- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
+++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
@@ -20,9 +20,7 @@ internal static class PngScanlineProcessor
in FrameControl frameControl,
ReadOnlySpan scanlineSpan,
Span rowSpan,
- bool hasTrans,
- L16 luminance16Trans,
- L8 luminanceTrans)
+ Color? transparentColor)
where TPixel : unmanaged, IPixel =>
ProcessInterlacedGrayscaleScanline(
bitDepth,
@@ -31,9 +29,7 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
- hasTrans,
- luminance16Trans,
- luminanceTrans);
+ transparentColor);
public static void ProcessInterlacedGrayscaleScanline(
int bitDepth,
@@ -56,7 +52,7 @@ internal static class PngScanlineProcessor
if (bitDepth == 16)
{
int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += 2)
+ for (nuint x = offset; x < frameControl.XMax; x += increment, o += 2)
{
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
pixel.FromL16(Unsafe.As(ref luminance));
@@ -65,7 +61,7 @@ internal static class PngScanlineProcessor
}
else
{
- for (nuint x = offset, o = 0; x < frameControl.XLimit; x += increment, o++)
+ for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++)
{
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor);
pixel.FromL8(Unsafe.As(ref luminance));
@@ -81,7 +77,7 @@ internal static class PngScanlineProcessor
L16 transparent = transparentColor.Value.ToPixel();
La32 source = default;
int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += 2)
+ for (nuint x = offset; x < frameControl.XMax; x += increment, o += 2)
{
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
source.L = luminance;
@@ -95,8 +91,7 @@ internal static class PngScanlineProcessor
{
byte transparent = (byte)(transparentColor.Value.ToPixel().PackedValue * scaleFactor);
La16 source = default;
- byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor);
- for (nuint x = offset, o = 0; x < frameControl.XLimit; x += increment, o++)
+ for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++)
{
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor);
source.L = luminance;
@@ -146,7 +141,7 @@ internal static class PngScanlineProcessor
{
La32 source = default;
int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += 4)
+ for (nuint x = offset; x < frameControl.XMax; x += increment, o += 4)
{
source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
@@ -159,7 +154,7 @@ internal static class PngScanlineProcessor
{
La16 source = default;
nuint offset2 = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment)
+ for (nuint x = offset; x < frameControl.XMax; x += increment)
{
source.L = Unsafe.Add(ref scanlineSpanRef, offset2);
source.A = Unsafe.Add(ref scanlineSpanRef, offset2 + bytesPerSample);
@@ -175,8 +170,7 @@ internal static class PngScanlineProcessor
in FrameControl frameControl,
ReadOnlySpan scanlineSpan,
Span rowSpan,
- ReadOnlySpan palette,
- byte[] paletteAlpha)
+ ReadOnlyMemory? palette)
where TPixel : unmanaged, IPixel =>
ProcessInterlacedPaletteScanline(
frameControl,
@@ -184,8 +178,7 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
- palette,
- paletteAlpha);
+ palette);
public static void ProcessInterlacedPaletteScanline(
in FrameControl frameControl,
@@ -193,8 +186,7 @@ internal static class PngScanlineProcessor
Span rowSpan,
uint pixelOffset,
uint increment,
- ReadOnlySpan palette,
- byte[] paletteAlpha)
+ ReadOnlyMemory? palette)
where TPixel : unmanaged, IPixel
{
if (palette is null)
@@ -202,53 +194,31 @@ internal static class PngScanlineProcessor
PngThrowHelper.ThrowMissingPalette();
}
- uint offset = pixelOffset + (uint)frameControl.XOffset;
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
ref Color paletteBase = ref MemoryMarshal.GetReference(palette.Value.Span);
- for (nuint x = 0; x < (uint)header.Width; x++)
+ for (nuint x = pixelOffset, o = 0; x < frameControl.XMax; x += increment, o++)
{
- // 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 = offset, o = 0; x < frameControl.XLimit; x += increment, o++)
- {
- uint index = Unsafe.Add(ref scanlineSpanRef, o);
- rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue;
- rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index);
-
- pixel.FromRgba32(rgba);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
- else
- {
- for (nuint x = offset, o = 0; x < frameControl.XLimit; x += increment, o++)
- {
- int index = Unsafe.Add(ref scanlineSpanRef, o);
- Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index);
-
- pixel.FromRgb24(rgb);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
+ uint index = Unsafe.Add(ref scanlineSpanRef, o);
+ pixel.FromRgba32(Unsafe.Add(ref paletteBase, index).ToRgba32());
+ Unsafe.Add(ref rowSpanRef, x) = pixel;
}
}
public static void ProcessRgbScanline(
+ Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan scanlineSpan,
Span rowSpan,
int bytesPerPixel,
int bytesPerSample,
- bool hasTrans,
- Rgb48 rgb48Trans,
- Rgb24 rgb24Trans)
+ Color? transparentColor)
where TPixel : unmanaged, IPixel =>
ProcessInterlacedRgbScanline(
+ configuration,
bitDepth,
frameControl,
scanlineSpan,
@@ -257,11 +227,10 @@ internal static class PngScanlineProcessor
1,
bytesPerPixel,
bytesPerSample,
- hasTrans,
- rgb48Trans,
- rgb24Trans);
+ transparentColor);
public static void ProcessInterlacedRgbScanline(
+ Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan scanlineSpan,
@@ -274,36 +243,18 @@ internal static class PngScanlineProcessor
where TPixel : unmanaged, IPixel
{
uint offset = pixelOffset + (uint)frameControl.XOffset;
+
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
- bool hasTransparency = transparentColor is not null;
- if (bitDepth == 16)
+ if (transparentColor is null)
{
- if (hasTrans)
- {
- Rgb48 rgb48 = default;
- Rgba64 rgba64 = default;
- int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, 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 (bitDepth == 16)
{
Rgb48 rgb48 = default;
int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel)
+ for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
@@ -315,30 +266,33 @@ internal static class PngScanlineProcessor
}
else
{
- Rgb24 rgb = default;
- int o = 0;
- for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel)
- {
- rgb.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
- rgb.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
- rgb.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
-
- pixel.FromRgb24(rgb);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
+ // Rgb24 rgb = default;
+ // int o = 0;
+ // for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel)
+ // {
+ // rgb.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
+ // rgb.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
+ // rgb.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
+
+ // pixel.FromRgb24(rgb);
+ // Unsafe.Add(ref rowSpanRef, x) = pixel;
+ // }
+
+ // PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width);
+ PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan[(int)offset..], (int)frameControl.XMax);
}
return;
}
- if (header.BitDepth == 16)
+ if (bitDepth == 16)
{
Rgb48 transparent = transparentColor.Value.ToPixel();
Rgb48 rgb48 = default;
Rgba64 rgba64 = default;
int o = 0;
- for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel)
+ for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
@@ -357,7 +311,7 @@ internal static class PngScanlineProcessor
Rgba32 rgba = default;
int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel)
+ for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgba.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
@@ -368,23 +322,10 @@ internal static class PngScanlineProcessor
Unsafe.Add(ref rowSpanRef, x) = pixel;
}
}
- else
- {
- Rgb24 rgb = default;
- int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel)
- {
- rgb.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
- rgb.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
- rgb.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
-
- pixel.FromRgb24(rgb);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
- }
}
public static void ProcessRgbaScanline(
+ Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan scanlineSpan,
@@ -393,6 +334,7 @@ internal static class PngScanlineProcessor
int bytesPerSample)
where TPixel : unmanaged, IPixel =>
ProcessInterlacedRgbaScanline(
+ configuration,
bitDepth,
frameControl,
scanlineSpan,
@@ -403,6 +345,7 @@ internal static class PngScanlineProcessor
bytesPerSample);
public static void ProcessInterlacedRgbaScanline(
+ Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan scanlineSpan,
@@ -421,7 +364,7 @@ internal static class PngScanlineProcessor
{
Rgba64 rgba64 = default;
int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel)
+ for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
@@ -434,19 +377,22 @@ internal static class PngScanlineProcessor
}
else
{
- ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
- Rgba32 rgba = default;
- int o = 0;
- for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel)
- {
- rgba.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
- rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
- rgba.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
- rgba.A = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample)));
-
- pixel.FromRgba32(rgba);
- Unsafe.Add(ref rowSpanRef, x) = pixel;
- }
+ // ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
+ // Rgba32 rgba = default;
+ // int o = 0;
+ // for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel)
+ // {
+ // rgba.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
+ // rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
+ // rgba.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
+ // rgba.A = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample)));
+
+ // pixel.FromRgba32(rgba);
+ // Unsafe.Add(ref rowSpanRef, x) = pixel;
+ // }
+
+ // PixelOperations.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan, header.Width);
+ PixelOperations.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan[(int)offset..], (int)frameControl.XMax);
}
}
}