From c253f39a323ccd463f766a12598cd467550c6dd7 Mon Sep 17 00:00:00 2001 From: Poker Date: Fri, 18 Aug 2023 01:38:30 +0800 Subject: [PATCH] Fix offset --- .../Formats/Png/Chunks/FrameControl.cs | 18 ++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 73 +++++----- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 71 +++++----- .../Formats/Png/PngScanlineProcessor.cs | 130 +++++++++++------- 4 files changed, 174 insertions(+), 118 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs index 0414840a85..bb75cbabf8 100644 --- a/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs +++ b/src/ImageSharp/Formats/Png/Chunks/FrameControl.cs @@ -56,6 +56,16 @@ internal readonly struct FrameControl /// public int YOffset { get; } + /// + /// Gets the X limit at which to render the following frame + /// + public uint XLimit => (uint)(this.XOffset + this.Width); + + /// + /// Gets the Y limit at which to render the following frame + /// + public uint YLimit => (uint)(this.YOffset + this.Height); + /// /// Gets the frame delay fraction numerator /// @@ -104,14 +114,14 @@ internal readonly struct FrameControl PngThrowHelper.ThrowInvalidParameter(this.Height, "Expected > 0"); } - if (this.XOffset + this.Width > hdr.Width) + if (this.XLimit > hdr.Width) { - PngThrowHelper.ThrowInvalidParameter(this.XOffset, this.Width, $"The sum > {nameof(PngHeader)}.{nameof(PngHeader.Width)}"); + PngThrowHelper.ThrowInvalidParameter(this.XOffset, this.Width, $"The sum of them > {nameof(PngHeader)}.{nameof(PngHeader.Width)}"); } - if (this.YOffset + this.Height > hdr.Height) + if (this.YLimit > hdr.Height) { - PngThrowHelper.ThrowInvalidParameter(this.YOffset, this.Height, "The sum > PngHeader.Height"); + PngThrowHelper.ThrowInvalidParameter(this.YOffset, this.Height, $"The sum of them > {nameof(PngHeader)}.{nameof(PngHeader.Height)}"); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 618ca42dfa..a53844b4a7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -228,6 +228,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals this.currentStream.Position += 4; // Skip sequence number return length - 4; }, + lastFrameControl.Value, cancellationToken); lastFrameControl = null; break; @@ -237,7 +238,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals this.InitializeImage(metadata, lastFrameControl, out image); } - this.ReadScanlines(chunk.Length, image.Frames.RootFrame, pngMetadata, this.ReadNextDataChunk, cancellationToken); + 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; break; case PngChunkType.Palette: @@ -682,8 +685,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals /// The pixel data. /// The png metadata /// A delegate to get more data from the inner stream for . + /// The frame control /// The cancellation token. - private void ReadScanlines(int chunkLength, ImageFrame image, PngMetadata pngMetadata, Func getData, CancellationToken cancellationToken) + private void ReadScanlines(int chunkLength, ImageFrame image, PngMetadata pngMetadata, Func getData, FrameControl frameControl, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { using ZlibInflateStream deframeStream = new(this.currentStream, getData); @@ -692,11 +696,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals if (this.header.InterlaceMethod is PngInterlaceMode.Adam7) { - this.DecodeInterlacedPixelData(dataStream, image, pngMetadata, cancellationToken); + this.DecodeInterlacedPixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); } else { - this.DecodePixelData(dataStream, image, pngMetadata, cancellationToken); + this.DecodePixelData(frameControl, dataStream, image, pngMetadata, cancellationToken); } } @@ -704,16 +708,17 @@ internal sealed class PngDecoderCore : IImageDecoderInternals /// Decodes the raw pixel data row by row /// /// The pixel format. + /// The frame control /// The compressed pixel data stream. /// The image to decode to. /// The png metadata /// The CancellationToken - private void DecodePixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken) + private void DecodePixelData(FrameControl frameControl, DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - int currentRow = Adam7.FirstRow[0]; + int currentRow = frameControl.YOffset; int currentRowBytesRead = 0; - int height = image.Metadata.TryGetPngFrameMetadata(out PngFrameMetadata? frameMetadata) ? frameMetadata.Height : this.header.Height; + int height = frameControl.Height; while (currentRow < height) { cancellationToken.ThrowIfCancellationRequested(); @@ -757,7 +762,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals break; } - this.ProcessDefilteredScanline(currentRow, scanlineSpan, image, pngMetadata); + this.ProcessDefilteredScanline(frameControl, currentRow, scanlineSpan, image, pngMetadata); this.SwapScanlineBuffers(); currentRow++; @@ -769,23 +774,19 @@ internal sealed class PngDecoderCore : IImageDecoderInternals /// /// /// The pixel format. + /// The frame control /// The compressed pixel data stream. /// The current image. /// The png metadata. /// The cancellation token. - private void DecodeInterlacedPixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken) + private void DecodeInterlacedPixelData(FrameControl frameControl, DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - int currentRow = Adam7.FirstRow[0]; + int currentRow = Adam7.FirstRow[0] + frameControl.YOffset; int currentRowBytesRead = 0; int pass = 0; - int width = this.header.Width; - int height = this.header.Height; - if (image.Metadata.TryGetPngFrameMetadata(out PngFrameMetadata? frameMetadata)) - { - width = frameMetadata.Width; - height = frameMetadata.Height; - } + int width = frameControl.Width; + int height = frameControl.Height; Buffer2D imageBuffer = image.PixelBuffer; while (true) @@ -848,7 +849,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals } Span rowSpan = imageBuffer.DangerousGetRowSpan(currentRow); - this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); + this.ProcessInterlacedDefilteredScanline(frameControl, this.scanline.GetSpan(), rowSpan, pngMetadata, pixelOffset: Adam7.FirstColumn[pass], increment: Adam7.ColumnIncrement[pass]); this.SwapScanlineBuffers(); @@ -874,11 +875,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals /// Processes the de-filtered scanline filling the image pixel data /// /// The pixel format. + /// The frame control /// The index of the current scanline being processed. /// The de-filtered scanline /// The image /// The png metadata. - private void ProcessDefilteredScanline(int currentRow, ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) + private void ProcessDefilteredScanline(FrameControl frameControl, int currentRow, ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) where TPixel : unmanaged, IPixel { Span rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(currentRow); @@ -902,7 +904,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { case PngColorType.Grayscale: PngScanlineProcessor.ProcessGrayscaleScanline( - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, pngMetadata.HasTransparency, @@ -913,7 +916,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.GrayscaleWithAlpha: PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, (uint)this.bytesPerPixel, @@ -923,7 +927,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.Palette: PngScanlineProcessor.ProcessPaletteScanline( - this.header, + frameControl, scanlineSpan, rowSpan, this.palette, @@ -933,8 +937,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.Rgb: PngScanlineProcessor.ProcessRgbScanline( - this.configuration, - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, this.bytesPerPixel, @@ -947,8 +951,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.RgbWithAlpha: PngScanlineProcessor.ProcessRgbaScanline( - this.configuration, - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, this.bytesPerPixel, @@ -967,12 +971,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals /// Processes the interlaced de-filtered scanline filling the image pixel data /// /// The pixel format. + /// The frame control /// The de-filtered scanline /// The current image row. /// The png metadata. /// The column start index. Always 0 for none interlaced images. /// The column increment. Always 1 for none interlaced images. - private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1) + private void ProcessInterlacedDefilteredScanline(FrameControl frameControl, ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1) where TPixel : unmanaged, IPixel { // Trim the first marker byte from the buffer @@ -994,7 +999,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals { case PngColorType.Grayscale: PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, (uint)pixelOffset, @@ -1007,7 +1013,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.GrayscaleWithAlpha: PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, (uint)pixelOffset, @@ -1019,7 +1026,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.Palette: PngScanlineProcessor.ProcessInterlacedPaletteScanline( - this.header, + frameControl, scanlineSpan, rowSpan, (uint)pixelOffset, @@ -1031,7 +1038,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.Rgb: PngScanlineProcessor.ProcessInterlacedRgbScanline( - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, (uint)pixelOffset, @@ -1046,7 +1054,8 @@ internal sealed class PngDecoderCore : IImageDecoderInternals case PngColorType.RgbWithAlpha: PngScanlineProcessor.ProcessInterlacedRgbaScanline( - this.header, + this.header.BitDepth, + frameControl, scanlineSpan, rowSpan, (uint)pixelOffset, diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index cc654b2e75..1e7426226a 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -172,23 +172,24 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { this.WriteAnimationControlChunk(stream, targetImage.Frames.Count, pngMetadata.NumberPlays); - this.WriteFrameControlChunk(stream, targetImage.Frames.RootFrame.Metadata.GetPngFrameMetadata(), 0); - _ = this.WriteDataChunks(targetImage.Frames.RootFrame, rootQuantized, stream, false); + FrameControl frameControl = this.WriteFrameControlChunk(stream, targetImage.Frames.RootFrame.Metadata.GetPngFrameMetadata(), 0); + _ = this.WriteDataChunks(frameControl, targetImage.Frames.RootFrame, rootQuantized, stream, false); int index = 1; foreach (ImageFrame imageFrame in ((IEnumerable>)targetImage.Frames).Skip(1)) { - this.WriteFrameControlChunk(stream, imageFrame.Metadata.GetPngFrameMetadata(), index); + frameControl = this.WriteFrameControlChunk(stream, imageFrame.Metadata.GetPngFrameMetadata(), index); index++; IndexedImageFrame? quantized = this.CreateQuantizedImageAndUpdateBitDepth(imageFrame); - index += this.WriteDataChunks(imageFrame, quantized, stream, true, index); + index += this.WriteDataChunks(frameControl, imageFrame, quantized, stream, true); quantized?.Dispose(); } } else { - _ = this.WriteDataChunks(targetImage.Frames.RootFrame, rootQuantized, stream, false); + 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(); } @@ -962,25 +963,27 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// The containing image data. /// Provides APng specific metadata information for the image frame. /// Sequence number. - private void WriteFrameControlChunk(Stream stream, PngFrameMetadata frameMetadata, int sequenceNumber) + private FrameControl WriteFrameControlChunk(Stream stream, PngFrameMetadata frameMetadata, int sequenceNumber) { FrameControl fcTL = FrameControl.FromMetadata(frameMetadata, sequenceNumber); fcTL.WriteTo(this.chunkDataBuffer.Span); this.WriteChunk(stream, PngChunkType.FrameControl, this.chunkDataBuffer.Span, 0, FrameControl.Size); + + return fcTL; } /// /// Writes the pixel information to the stream. /// /// The pixel format. + /// The frame control /// The frame. /// The quantized pixel data. Can be null. /// The stream. /// Is writing fdAT or IDAT. - /// Start sequence number. - private int WriteDataChunks(ImageFrame pixels, IndexedImageFrame? quantized, Stream stream, bool isFrame, int startSequenceNumber = 0) + private int WriteDataChunks(FrameControl frameControl, ImageFrame pixels, IndexedImageFrame? quantized, Stream stream, bool isFrame) where TPixel : unmanaged, IPixel { byte[] buffer; @@ -994,16 +997,16 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { if (quantized is not null) { - this.EncodeAdam7IndexedPixels(quantized, deflateStream); + this.EncodeAdam7IndexedPixels(frameControl, quantized, deflateStream); } else { - this.EncodeAdam7Pixels(pixels, deflateStream); + this.EncodeAdam7Pixels(frameControl, pixels, deflateStream); } } else { - this.EncodePixels(pixels, quantized, deflateStream); + this.EncodePixels(frameControl, pixels, quantized, deflateStream); } } @@ -1038,7 +1041,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable if (isFrame) { byte[] chunkBuffer = new byte[MaxBlockSize]; - BinaryPrimitives.WriteInt32BigEndian(chunkBuffer, startSequenceNumber + i); + 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); @@ -1069,19 +1072,15 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// Encodes the pixels. /// /// The type of the pixel. + /// The frame control /// The pixels. /// The quantized pixels span. /// The deflate stream. - private void EncodePixels(ImageFrame pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream) + private void EncodePixels(FrameControl frameControl, ImageFrame pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = this.width; - int height = this.height; - if (pixels.Metadata.TryGetPngFrameMetadata(out PngFrameMetadata? pngMetadata)) - { - width = pngMetadata.Width; - height = pngMetadata.Height; - } + int width = frameControl.Width; + int height = frameControl.Height; int bytesPerScanline = this.CalculateScanlineLength(width); int filterLength = bytesPerScanline + 1; @@ -1094,7 +1093,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { Span filter = filterBuffer.GetSpan(); Span attempt = attemptBuffer.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = frameControl.YOffset; y < frameControl.YLimit; y++) { this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y); deflateStream.Write(filter); @@ -1107,18 +1106,19 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// Interlaced encoding the pixels. /// /// The type of the pixel. + /// The frame control /// The image frame. /// The deflate stream. - private void EncodeAdam7Pixels(ImageFrame frame, ZlibDeflateStream deflateStream) + private void EncodeAdam7Pixels(FrameControl frameControl, ImageFrame frame, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = frame.Width; - int height = frame.Height; + int width = frameControl.Width; + int height = frameControl.Height; Buffer2D pixelBuffer = frame.PixelBuffer; for (int pass = 0; pass < 7; pass++) { - int startRow = Adam7.FirstRow[pass]; - int startCol = Adam7.FirstColumn[pass]; + int startRow = Adam7.FirstRow[pass] + frameControl.YOffset; + int startCol = Adam7.FirstColumn[pass] + frameControl.XOffset; int blockWidth = Adam7.ComputeBlockWidth(width, pass); int bytesPerScanline = this.bytesPerPixel <= 1 @@ -1136,11 +1136,11 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable Span filter = filterBuffer.GetSpan(); Span attempt = attemptBuffer.GetSpan(); - for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) + for (int row = startRow; row < frameControl.YLimit; row += Adam7.RowIncrement[pass]) { // Collect pixel data Span srcRow = pixelBuffer.DangerousGetRowSpan(row); - for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) + for (int col = startCol, i = 0; col < frameControl.XLimit; col += Adam7.ColumnIncrement[pass]) { block[i++] = srcRow[col]; } @@ -1160,17 +1160,18 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable /// Interlaced encoding the quantized (indexed, with palette) pixels. /// /// The type of the pixel. + /// The frame control /// The quantized. /// The deflate stream. - private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + private void EncodeAdam7IndexedPixels(FrameControl frameControl, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = quantized.Width; - int height = quantized.Height; + int width = frameControl.Width; + int height = frameControl.Height; for (int pass = 0; pass < 7; pass++) { - int startRow = Adam7.FirstRow[pass]; - int startCol = Adam7.FirstColumn[pass]; + int startRow = Adam7.FirstRow[pass] + frameControl.YOffset; + int startCol = Adam7.FirstColumn[pass] + frameControl.XOffset; int blockWidth = Adam7.ComputeBlockWidth(width, pass); int bytesPerScanline = this.bytesPerPixel <= 1 @@ -1190,13 +1191,13 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable Span attempt = attemptBuffer.GetSpan(); for (int row = startRow; - row < height; + row < frameControl.YLimit; row += Adam7.RowIncrement[pass]) { // Collect data ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row); for (int col = startCol, i = 0; - col < width; + col < frameControl.XLimit; col += Adam7.ColumnIncrement[pass]) { block[i] = srcRow[col]; diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index 125aa75b75..67a1f7c82d 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Formats.Png; internal static class PngScanlineProcessor { public static void ProcessGrayscaleScanline( - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, bool hasTrans, @@ -24,7 +25,8 @@ internal static class PngScanlineProcessor L8 luminanceTrans) where TPixel : unmanaged, IPixel => ProcessInterlacedGrayscaleScanline( - header, + bitDepth, + frameControl, scanlineSpan, rowSpan, 0, @@ -34,7 +36,8 @@ internal static class PngScanlineProcessor luminanceTrans); public static void ProcessInterlacedGrayscaleScanline( - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, uint pixelOffset, @@ -44,17 +47,18 @@ internal static class PngScanlineProcessor L8 luminanceTrans) 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); - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1); + int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(bitDepth) - 1); if (!hasTrans) { - if (header.BitDepth == 16) + if (bitDepth == 16) { int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += 2) + for (nuint x = offset; x < frameControl.XLimit; x += increment, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); pixel.FromL16(Unsafe.As(ref luminance)); @@ -63,7 +67,7 @@ internal static class PngScanlineProcessor } else { - for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++) + for (nuint x = offset, o = 0; x < frameControl.XLimit; x += increment, o++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); pixel.FromL8(Unsafe.As(ref luminance)); @@ -74,11 +78,11 @@ internal static class PngScanlineProcessor return; } - if (header.BitDepth == 16) + if (bitDepth == 16) { La32 source = default; int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += 2) + for (nuint x = offset; x < frameControl.XLimit; x += increment, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); source.L = luminance; @@ -92,7 +96,7 @@ internal static class PngScanlineProcessor { La16 source = default; byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); - for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++) + for (nuint x = offset, o = 0; x < frameControl.XLimit; x += increment, o++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); source.L = luminance; @@ -105,14 +109,16 @@ internal static class PngScanlineProcessor } public static void ProcessGrayscaleWithAlphaScanline( - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, uint bytesPerPixel, uint bytesPerSample) where TPixel : unmanaged, IPixel => ProcessInterlacedGrayscaleWithAlphaScanline( - header, + bitDepth, + frameControl, scanlineSpan, rowSpan, 0, @@ -121,7 +127,8 @@ internal static class PngScanlineProcessor bytesPerSample); public static void ProcessInterlacedGrayscaleWithAlphaScanline( - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, uint pixelOffset, @@ -130,15 +137,16 @@ internal static class PngScanlineProcessor uint bytesPerSample) 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); - if (header.BitDepth == 16) + if (bitDepth == 16) { La32 source = default; int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += 4) + for (nuint x = offset; x < frameControl.XLimit; x += increment, o += 4) { source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); @@ -150,27 +158,28 @@ internal static class PngScanlineProcessor else { La16 source = default; - nuint offset = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment) + nuint offset2 = 0; + for (nuint x = offset; x < frameControl.XLimit; x += increment) { - source.L = Unsafe.Add(ref scanlineSpanRef, offset); - source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); + source.L = Unsafe.Add(ref scanlineSpanRef, offset2); + source.A = Unsafe.Add(ref scanlineSpanRef, offset2 + bytesPerSample); pixel.FromLa16(source); Unsafe.Add(ref rowSpanRef, x) = pixel; - offset += bytesPerPixel; + offset2 += bytesPerPixel; } } } public static void ProcessPaletteScanline( - in PngHeader header, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, ReadOnlySpan palette, byte[] paletteAlpha) where TPixel : unmanaged, IPixel => - ProcessInterlacedPaletteScanline(header, + ProcessInterlacedPaletteScanline( + frameControl, scanlineSpan, rowSpan, 0, @@ -179,7 +188,7 @@ internal static class PngScanlineProcessor paletteAlpha); public static void ProcessInterlacedPaletteScanline( - in PngHeader header, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, uint pixelOffset, @@ -193,6 +202,7 @@ 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); @@ -205,7 +215,7 @@ internal static class PngScanlineProcessor // channel and we should try to read it. Rgba32 rgba = default; ref byte paletteAlphaRef = ref MemoryMarshal.GetArrayDataReference(paletteAlpha); - for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++) + 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; @@ -217,7 +227,7 @@ internal static class PngScanlineProcessor } else { - for (nuint x = pixelOffset, o = 0; x < (uint)header.Width; x += increment, o++) + 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); @@ -229,8 +239,8 @@ internal static class PngScanlineProcessor } public static void ProcessRgbScanline( - Configuration configuration, - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, int bytesPerPixel, @@ -240,16 +250,17 @@ internal static class PngScanlineProcessor Rgb24 rgb24Trans) where TPixel : unmanaged, IPixel { + uint offset = (uint)frameControl.XOffset; TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - if (header.BitDepth == 16) + if (bitDepth == 16) { if (!hasTrans) { Rgb48 rgb48 = default; int o = 0; - for (nuint x = 0; x < (uint)header.Width; x++, o += bytesPerPixel) + for (nuint x = offset; x < frameControl.XLimit; x++, o += bytesPerPixel) { rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); @@ -264,7 +275,7 @@ internal static class PngScanlineProcessor Rgb48 rgb48 = default; Rgba64 rgba64 = default; int o = 0; - for (nuint x = 0; x < (uint)header.Width; x++, o += bytesPerPixel) + for (nuint x = offset; x < frameControl.XLimit; x++, o += bytesPerPixel) { rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); @@ -286,7 +297,7 @@ internal static class PngScanlineProcessor Rgba32 rgba32 = default; ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); - for (nuint x = 0; x < (uint)header.Width; x++) + for (nuint x = offset; x < frameControl.XLimit; x++) { ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x); rgba32.Rgb = rgb24; @@ -298,12 +309,23 @@ internal static class PngScanlineProcessor } else { - PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width); + ReadOnlySpan source = MemoryMarshal.Cast(scanlineSpan)[..frameControl.Width]; + ref Rgb24 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(rowSpan); + + for (nuint i = offset; i < frameControl.XLimit; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromRgb24(sp); + } } } public static void ProcessInterlacedRgbScanline( - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, uint pixelOffset, @@ -315,18 +337,19 @@ internal static class PngScanlineProcessor Rgb24 rgb24Trans) 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); - if (header.BitDepth == 16) + if (bitDepth == 16) { if (hasTrans) { 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.XLimit; x += increment, o += bytesPerPixel) { rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); @@ -343,7 +366,7 @@ internal static class PngScanlineProcessor { Rgb48 rgb48 = default; int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) + 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)); @@ -361,7 +384,7 @@ internal static class PngScanlineProcessor { Rgba32 rgba = default; int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) + 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)); @@ -376,7 +399,7 @@ internal static class PngScanlineProcessor { Rgb24 rgb = default; int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) + 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)); @@ -389,22 +412,23 @@ internal static class PngScanlineProcessor } public static void ProcessRgbaScanline( - Configuration configuration, - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, int bytesPerPixel, int bytesPerSample) where TPixel : unmanaged, IPixel { + uint offset = (uint)frameControl.XOffset; TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - if (header.BitDepth == 16) + if (bitDepth == 16) { Rgba64 rgba64 = default; int o = 0; - for (nuint x = 0; x < (uint)header.Width; x++, o += bytesPerPixel) + for (nuint x = offset; x < frameControl.XLimit; x++, o += bytesPerPixel) { rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); @@ -417,12 +441,23 @@ internal static class PngScanlineProcessor } else { - PixelOperations.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan, header.Width); + ReadOnlySpan source = MemoryMarshal.Cast(scanlineSpan)[..frameControl.Width]; + ref Rgba32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(rowSpan); + + for (nuint i = offset; i < frameControl.XLimit; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromRgba32(sp); + } } } public static void ProcessInterlacedRgbaScanline( - in PngHeader header, + int bitDepth, + FrameControl frameControl, ReadOnlySpan scanlineSpan, Span rowSpan, uint pixelOffset, @@ -431,15 +466,16 @@ internal static class PngScanlineProcessor int bytesPerSample) 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); - if (header.BitDepth == 16) + if (bitDepth == 16) { Rgba64 rgba64 = default; int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) + for (nuint x = offset; x < frameControl.XLimit; x += increment, o += bytesPerPixel) { rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); @@ -454,7 +490,7 @@ internal static class PngScanlineProcessor { Rgba32 rgba = default; int o = 0; - for (nuint x = pixelOffset; x < (uint)header.Width; x += increment, o += bytesPerPixel) + 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));