Browse Source

Refactor and cleanup

pull/2511/head
James Jackson-South 2 years ago
parent
commit
aada974cdb
  1. 119
      src/ImageSharp/Formats/Png/Chunks/FrameControl.cs
  2. 4
      src/ImageSharp/Formats/Png/PngBlendMethod.cs
  3. 57
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  4. 4
      src/ImageSharp/Formats/Png/PngDisposalMethod.cs
  5. 248
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  6. 48
      src/ImageSharp/Formats/Png/PngFrameMetadata.cs
  7. 176
      src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

119
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
/// <summary>
/// Gets the sequence number of the animation chunk, starting from 0
/// </summary>
public int SequenceNumber { get; }
public uint SequenceNumber { get; }
/// <summary>
/// Gets the width of the following frame
/// </summary>
public int Width { get; }
public uint Width { get; }
/// <summary>
/// Gets the height of the following frame
/// </summary>
public int Height { get; }
public uint Height { get; }
/// <summary>
/// Gets the X position at which to render the following frame
/// </summary>
public int XOffset { get; }
public uint XOffset { get; }
/// <summary>
/// Gets the Y position at which to render the following frame
/// </summary>
public int YOffset { get; }
public uint YOffset { get; }
/// <summary>
/// Gets the X limit at which to render the following frame
/// </summary>
public uint XLimit => (uint)(this.XOffset + this.Width);
public uint XMax => this.XOffset + this.Width;
/// <summary>
/// Gets the Y limit at which to render the following frame
/// </summary>
public uint YLimit => (uint)(this.YOffset + this.Height);
public uint YMax => this.YOffset + this.Height;
/// <summary>
/// Gets the frame delay fraction numerator
/// </summary>
public short DelayNumber { get; }
public ushort DelayNumerator { get; }
/// <summary>
/// Gets the frame delay fraction denominator
/// </summary>
public short DelayDenominator { get; }
public ushort DelayDenominator { get; }
/// <summary>
/// Gets the type of frame area disposal to be done after rendering this frame
/// </summary>
public PngDisposeOperation DisposeOperation { get; }
public PngDisposalMethod DisposeOperation { get; }
/// <summary>
/// Gets the type of frame area rendering for this frame
/// </summary>
public PngBlendOperation BlendOperation { get; }
public PngBlendMethod BlendOperation { get; }
/// <summary>
/// Validates the APng fcTL.
/// </summary>
/// <param name="header">The header.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the image does pass validation.
/// </exception>
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)}");
}
}
/// <summary>
/// Parses the APngFrameControl from the given metadata.
/// </summary>
/// <param name="frameMetadata">The metadata to parse.</param>
/// <param name="sequenceNumber">Sequence number.</param>
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;
}
/// <summary>
/// Writes the fcTL to the given buffer.
/// </summary>
/// <param name="buffer">The buffer to write to.</param>
public void WriteTo(Span<byte> 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
/// <returns>The parsed fcTL.</returns>
public static FrameControl Parse(ReadOnlySpan<byte> 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]);
}

4
src/ImageSharp/Formats/Png/PngBlendOperation.cs → 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;
/// <summary>
/// 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.
/// </summary>
public enum PngBlendOperation
public enum PngBlendMethod
{
/// <summary>
/// All color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region.

57
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -152,7 +152,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.currentStream = stream;
this.currentStream.Skip(8);
Image<TPixel>? image = null;
FrameControl? lastFrameControl = null;
FrameControl? previousFrameControl = null;
ImageFrame<TPixel>? currentFrame = null;
Span<byte> 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<TPixel>(FrameControl frameControl, DeflateStream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(in FrameControl frameControl, DeflateStream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> 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,

4
src/ImageSharp/Formats/Png/PngDisposeOperation.cs → 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;
/// <summary>
/// Specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).
/// </summary>
public enum PngDisposeOperation
public enum PngDisposalMethod
{
/// <summary>
/// No disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.

248
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -111,6 +111,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// </summary>
private const string ColorProfileName = "ICC Profile";
/// <summary>
/// The encoder quantizer, if present.
/// </summary>
private IQuantizer? quantizer;
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore" /> class.
/// </summary>
@ -121,6 +126,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.encoder = encoder;
this.quantizer = encoder.Quantizer;
}
/// <summary>
@ -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<TPixel>(this.encoder, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
Image<TPixel>? clonedImage = null;
Image<TPixel> targetImage = image;
stream.Write(PngConstants.HeaderBytes);
this.WriteHeaderChunk(stream);
this.WriteGammaChunk(stream);
this.WriteColorProfileChunk(stream, metadata);
ImageFrame<TPixel>? clonedFrame = null;
ImageFrame<TPixel> 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<TPixel>? rootQuantized = this.CreateQuantizedImageAndUpdateBitDepth(targetImage.Frames.RootFrame);
IndexedImageFrame<TPixel>? 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<TPixel>? previousPalette = quantized?.Palette.ToArray();
foreach (ImageFrame<TPixel> imageFrame in ((IEnumerable<ImageFrame<TPixel>>)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<TPixel>? 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();
}
/// <inheritdoc />
@ -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.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="image">The cloned image where the transparent pixels will be changed.</param>
private static void ClearTransparentPixels<TPixel>(Image<TPixel> image)
/// <param name="clone">The cloned image frame where the transparent pixels will be changed.</param>
private static void ClearTransparentPixels<TPixel>(ImageFrame<TPixel> clone)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (ImageFrame<TPixel> 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<TPixel> span = accessor.GetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
Span<TPixel> 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);
}
}
});
}
}
}
});
/// <summary>
/// Creates the quantized image and calculates and sets the bit depth.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="metadata">The image metadata.</param>
/// <param name="frame">The frame to quantize.</param>
/// <param name="previousPalette">Any previously derived palette.</param>
/// <returns>The quantized image.</returns>
private IndexedImageFrame<TPixel>? CreateQuantizedImageAndUpdateBitDepth<TPixel>(
ImageFrame<TPixel> frame)
PngMetadata metadata,
ImageFrame<TPixel> frame,
ReadOnlyMemory<TPixel>? previousPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
IndexedImageFrame<TPixel>? quantized = CreateQuantizedFrame(this.encoder, this.colorType, this.bitDepth, frame);
IndexedImageFrame<TPixel>? 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<byte> 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.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="frameMetadata">Provides APng specific metadata information for the image frame.</param>
/// <param name="sequenceNumber">Sequence number.</param>
private FrameControl WriteFrameControlChunk(Stream stream, PngFrameMetadata frameMetadata, int sequenceNumber)
/// <param name="imageFrame">The image frame.</param>
/// <param name="sequenceNumber">The frame sequence number.</param>
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<TPixel>(FrameControl frameControl, ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel>? quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
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<byte> filter = filterBuffer.GetSpan();
Span<byte> 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<TPixel>(FrameControl frameControl, ImageFrame<TPixel> frame, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = frameControl.Width;
int height = frameControl.Height;
int width = (int)frameControl.XMax;
int height = (int)frameControl.YMax;
Buffer2D<TPixel> 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<byte> filter = filterBuffer.GetSpan();
Span<byte> 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<TPixel> 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<TPixel>(FrameControl frameControl, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
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<byte> filter = filterBuffer.GetSpan();
Span<byte> 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<byte> 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
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="data">The <see cref="Span{Byte}"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int offset, int length)
@ -1255,6 +1284,38 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
stream.Write(buffer, 0, 4); // write the crc
}
/// <summary>
/// Writes a frame data chunk of a specified length to the stream at the given offset.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="sequenceNumber">The frame sequence number.</param>
/// <param name="data">The <see cref="Span{Byte}"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</param>
private void WriteFrameDataChunk(Stream stream, uint sequenceNumber, Span<byte> data, int offset, int length)
{
Span<byte> 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
}
/// <summary>
/// Calculates the scanline length.
/// </summary>
@ -1335,12 +1396,16 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// <param name="encoder">The png encoder.</param>
/// <param name="colorType">The color type.</param>
/// <param name="bitDepth">The bits per component.</param>
/// <param name="frame">The frame.</param>
private static IndexedImageFrame<TPixel>? CreateQuantizedFrame<TPixel>(
/// <param name="metadata">The image metadata.</param>
/// <param name="frame">The frame to quantize.</param>
/// <param name="previousPalette">Any previously derived palette.</param>
private IndexedImageFrame<TPixel>? CreateQuantizedFrame<TPixel>(
QuantizingImageEncoder encoder,
PngColorType colorType,
byte bitDepth,
ImageFrame<TPixel> frame)
PngMetadata metadata,
ImageFrame<TPixel> frame,
ReadOnlyMemory<TPixel>? previousPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> 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<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(frame.Configuration);
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(frame.Configuration);
frameQuantizer.BuildPalette(encoder.PixelSamplingStrategy, frame);
return frameQuantizer.QuantizeFrame(frame, frame.Bounds());

48
src/ImageSharp/Formats/Png/PngFrameMetadata.cs

@ -23,55 +23,31 @@ public class PngFrameMetadata : IDeepCloneable
/// <param name="other">The metadata to create an instance from.</param>
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;
}
/// <summary>
/// Gets or sets the width of the following frame
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the height of the following frame
/// </summary>
public int Height { get; set; }
/// <summary>
/// Gets or sets the X position at which to render the following frame
/// </summary>
public int XOffset { get; set; }
/// <summary>
/// Gets or sets the Y position at which to render the following frame
/// </summary>
public int YOffset { get; set; }
/// <summary>
/// Gets or sets the frame delay fraction numerator
/// </summary>
public short DelayNumber { get; set; }
public ushort DelayNumerator { get; set; }
/// <summary>
/// Gets or sets the frame delay fraction denominator
/// </summary>
public short DelayDenominator { get; set; }
public ushort DelayDenominator { get; set; }
/// <summary>
/// Gets or sets the type of frame area disposal to be done after rendering this frame
/// </summary>
public PngDisposeOperation DisposeOperation { get; set; }
public PngDisposalMethod DisposalMethod { get; set; }
/// <summary>
/// Gets or sets the type of frame area rendering for this frame
/// </summary>
public PngBlendOperation BlendOperation { get; set; }
public PngBlendMethod BlendMethod { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PngFrameMetadata"/> class.
@ -79,14 +55,10 @@ public class PngFrameMetadata : IDeepCloneable
/// <param name="frameControl">The chunk to create an instance from.</param>
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;
}
/// <inheritdoc/>

176
src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

@ -20,9 +20,7 @@ internal static class PngScanlineProcessor
in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan,
bool hasTrans,
L16 luminance16Trans,
L8 luminanceTrans)
Color? transparentColor)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedGrayscaleScanline(
bitDepth,
@ -31,9 +29,7 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
hasTrans,
luminance16Trans,
luminanceTrans);
transparentColor);
public static void ProcessInterlacedGrayscaleScanline<TPixel>(
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<ushort, L16>(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<byte, L8>(ref luminance));
@ -81,7 +77,7 @@ internal static class PngScanlineProcessor
L16 transparent = transparentColor.Value.ToPixel<L16>();
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<L8>().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<byte> scanlineSpan,
Span<TPixel> rowSpan,
ReadOnlySpan<byte> palette,
byte[] paletteAlpha)
ReadOnlyMemory<Color>? palette)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedPaletteScanline(
frameControl,
@ -184,8 +178,7 @@ internal static class PngScanlineProcessor
rowSpan,
0,
1,
palette,
paletteAlpha);
palette);
public static void ProcessInterlacedPaletteScanline<TPixel>(
in FrameControl frameControl,
@ -193,8 +186,7 @@ internal static class PngScanlineProcessor
Span<TPixel> rowSpan,
uint pixelOffset,
uint increment,
ReadOnlySpan<byte> palette,
byte[] paletteAlpha)
ReadOnlyMemory<Color>? palette)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(
Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan,
Span<TPixel> rowSpan,
int bytesPerPixel,
int bytesPerSample,
bool hasTrans,
Rgb48 rgb48Trans,
Rgb24 rgb24Trans)
Color? transparentColor)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbScanline(
configuration,
bitDepth,
frameControl,
scanlineSpan,
@ -257,11 +227,10 @@ internal static class PngScanlineProcessor
1,
bytesPerPixel,
bytesPerSample,
hasTrans,
rgb48Trans,
rgb24Trans);
transparentColor);
public static void ProcessInterlacedRgbScanline<TPixel>(
Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan,
@ -274,36 +243,18 @@ internal static class PngScanlineProcessor
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width);
PixelOperations<TPixel>.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 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<TPixel>(
Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan<byte> scanlineSpan,
@ -393,6 +334,7 @@ internal static class PngScanlineProcessor
int bytesPerSample)
where TPixel : unmanaged, IPixel<TPixel> =>
ProcessInterlacedRgbaScanline(
configuration,
bitDepth,
frameControl,
scanlineSpan,
@ -403,6 +345,7 @@ internal static class PngScanlineProcessor
bytesPerSample);
public static void ProcessInterlacedRgbaScanline<TPixel>(
Configuration configuration,
int bitDepth,
in FrameControl frameControl,
ReadOnlySpan<byte> 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<TPixel>.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan, header.Width);
PixelOperations<TPixel>.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan[(int)offset..], (int)frameControl.XMax);
}
}
}

Loading…
Cancel
Save