From 043427ac7f6b4818787a189e3b75ce0db4e9cdd9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 2 Jul 2021 16:08:14 +1000 Subject: [PATCH] Refactor to avoid all the weird allocations --- src/ImageSharp/Common/Helpers/DebugGuard.cs | 4 +- .../Formats/Png/Filters/AverageFilter.cs | 4 +- .../Formats/Png/Filters/PaethFilter.cs | 4 +- .../Formats/Png/Filters/SubFilter.cs | 2 +- .../Formats/Png/Filters/UpFilter.cs | 4 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 122 +++---- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 304 ++++++++---------- .../Formats/Png/ReferenceImplementations.cs | 10 +- 8 files changed, 215 insertions(+), 239 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 9ef7c01c61..f56cb37a81 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -37,7 +37,7 @@ namespace SixLabors /// has a different size than /// [Conditional("DEBUG")] - public static void MustBeSameSized(Span target, Span other, string parameterName) + public static void MustBeSameSized(ReadOnlySpan target, ReadOnlySpan other, string parameterName) where T : struct { if (target.Length != other.Length) @@ -57,7 +57,7 @@ namespace SixLabors /// has less items than /// [Conditional("DEBUG")] - public static void MustBeSizedAtLeast(Span target, Span minSpan, string parameterName) + public static void MustBeSizedAtLeast(ReadOnlySpan target, ReadOnlySpan minSpan, string parameterName) where T : struct { if (target.Length < minSpan.Length) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 0ab1413974..83c6389348 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index e8e0aa7043..6a89a1122a 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 116154836e..c28b877e41 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index e0f35293a4..7e0286991b 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index a80cea7f92..80ce5e6bdd 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -543,7 +543,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); - this.SwapBuffers(); + this.SwapScanlineBuffers(); this.currentRow++; } } @@ -618,7 +618,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan = image.GetPixelRowSpan(this.currentRow); this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); - this.SwapBuffers(); + this.SwapScanlineBuffers(); this.currentRow += Adam7.RowIncrement[pass]; } @@ -654,70 +654,80 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IMemoryOwner buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + IMemoryOwner buffer = null; + try { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline - 1, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; + + switch (this.pngColorType) + { + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - break; + break; - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; - case PngColorType.Palette: - PngScanlineProcessor.ProcessPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - this.palette, - this.paletteAlpha); + case PngColorType.Palette: + PngScanlineProcessor.ProcessPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + this.palette, + this.paletteAlpha); - break; + break; - case PngColorType.Rgb: - PngScanlineProcessor.ProcessRgbScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + case PngColorType.Rgb: + PngScanlineProcessor.ProcessRgbScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - break; + break; - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessRgbaScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessRgbaScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; + } + } + finally + { + buffer?.Dispose(); } - - buffer?.Dispose(); } /// @@ -1273,7 +1283,7 @@ namespace SixLabors.ImageSharp.Formats.Png return true; } - private void SwapBuffers() + private void SwapScanlineBuffers() { IMemoryOwner temp = this.previousScanline; this.previousScanline = this.scanline; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 9814d34447..4f6fb73567 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -87,26 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// private IMemoryOwner currentScanline; - /// - /// The common buffer for the filters. - /// - private IMemoryOwner filterBuffer; - - /// - /// The ext buffer for the sub filter, . - /// - private IMemoryOwner subFilter; - - /// - /// The ext buffer for the average filter, . - /// - private IMemoryOwner averageFilter; - - /// - /// The ext buffer for the Paeth filter, . - /// - private IMemoryOwner paethFilter; - /// /// Initializes a new instance of the class. /// @@ -173,17 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Png { this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.filterBuffer?.Dispose(); - this.previousScanline = null; this.currentScanline = null; - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.filterBuffer = null; } /// @@ -440,6 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.GrayscaleWithAlpha: this.CollectGrayscaleBytes(rowSpan); break; + case PngColorType.Rgb: + case PngColorType.RgbWithAlpha: default: this.CollectTPixelBytes(rowSpan); break; @@ -447,124 +420,127 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// Apply filter for the raw scanline. + /// Apply the line filter for the raw scanline to enable better compression. /// - private IMemoryOwner FilterPixelBytes() + private void FilterPixelBytes(ref Span filter, ref Span attempt) { switch (this.options.FilterMethod) { case PngFilterMethod.None: - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; - + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + break; case PngFilterMethod.Sub: - SubFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Up: - UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), out int _); - return this.filterBuffer; + UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _); + break; case PngFilterMethod.Average: - AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Paeth: - PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; - + PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; + case PngFilterMethod.Adaptive: default: - return this.GetOptimalFilteredScanline(); + this.ApplyOptimalFilteredScanline(ref filter, ref attempt); + break; } } /// - /// Encodes the pixel data line by line. - /// Each scanline is encoded in the most optimal manner to improve compression. + /// Collects the pixel data line by line for compressing. + /// Each scanline is filtered in the most optimal manner to improve compression. /// /// The pixel format. /// The row span. - /// The quantized pixels. Can be null. - /// The row. - /// The - private IMemoryOwner EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + /// The filtered buffer. + /// Used for attempting optimized filtering. + /// The quantized pixels. Can be . + /// The row number. + private void CollectAndFilterPixelRow( + ReadOnlySpan rowSpan, + ref Span filter, + ref Span attempt, + IndexedImageFrame quantized, + int row) where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. /// - /// The row span. - private IMemoryOwner EncodeAdam7IndexedPixelRow(ReadOnlySpan rowSpan) + /// The row span. + /// The filtered buffer. + /// Used for attempting optimized filtering. + private void EncodeAdam7IndexedPixelRow( + ReadOnlySpan row, + ref Span filter, + ref Span attempt) { // CollectPixelBytes if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(rowSpan, this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth); } else { - rowSpan.CopyTo(this.currentScanline.GetSpan()); + row.CopyTo(this.currentScanline.GetSpan()); } - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// - /// The - private IMemoryOwner GetOptimalFilteredScanline() + private void ApplyOptimalFilteredScanline(ref Span filter, ref Span attempt) { // Palette images don't compress well with adaptive filtering. - if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8) + // Nor do images comprising a single row. + if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8) { - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + return; } - this.AllocateExtBuffers(); - Span scanSpan = this.currentScanline.GetSpan(); - Span prevSpan = this.previousScanline.GetSpan(); - - // This order, while different to the enumerated order is more likely to produce a smaller sum - // early on which shaves a couple of milliseconds off the processing time. - UpFilter.Encode(scanSpan, prevSpan, this.filterBuffer.GetSpan(), out int currentSum); + Span current = this.currentScanline.GetSpan(); + Span previous = this.previousScanline.GetSpan(); - // TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. - // That way the above comment would actually be true. It used to be anyway... - // If we could use SIMD for none branching filters we could really speed it up. - int lowestSum = currentSum; - IMemoryOwner actualResult = this.filterBuffer; - - PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + int min = int.MaxValue; + SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum); + if (sum < min) { - lowestSum = currentSum; - actualResult = this.paethFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - SubFilter.Encode(scanSpan, this.subFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + UpFilter.Encode(current, previous, attempt, out sum); + if (sum < min) { - lowestSum = currentSum; - actualResult = this.subFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - AverageFilter.Encode(scanSpan, prevSpan, this.averageFilter.GetSpan(), this.bytesPerPixel, out currentSum); - - if (currentSum < lowestSum) + AverageFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) { - actualResult = this.averageFilter; + min = sum; + SwapSpans(ref filter, ref attempt); } - return actualResult; + PaethFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) + { + SwapSpans(ref filter, ref attempt); + } } /// @@ -920,38 +896,13 @@ namespace SixLabors.ImageSharp.Formats.Png /// Allocates the buffers for each scanline. /// /// The bytes per scanline. - /// Length of the result. - private void AllocateBuffers(int bytesPerScanline, int resultLength) + private void AllocateScanlineBuffers(int bytesPerScanline) { // Clean up from any potential previous runs. - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.filterBuffer?.Dispose(); this.previousScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); - this.filterBuffer = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - } - - /// - /// Allocates the ext buffers for adaptive filter. - /// - private void AllocateExtBuffers() - { - if (this.subFilter == null) - { - int resultLength = this.filterBuffer.Length(); - - this.subFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - this.averageFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - this.paethFilter = this.memoryAllocator.Allocate(resultLength, AllocationOptions.Clean); - } } /// @@ -965,17 +916,19 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); - int resultLength = bytesPerScanline + 1; - this.AllocateBuffers(bytesPerScanline, resultLength); + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); for (int y = 0; y < this.height; y++) { - IMemoryOwner r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); - deflateStream.Write(r.GetSpan(), 0, resultLength); - - IMemoryOwner temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; + this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y); + deflateStream.Write(filter); + this.SwapScanlineBuffers(); } } @@ -1000,36 +953,33 @@ namespace SixLabors.ImageSharp.Formats.Png ? ((blockWidth * this.bitDepth) + 7) / 8 : blockWidth * this.bytesPerPixel; - int resultLength = bytesPerScanline + 1; + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); - this.AllocateBuffers(bytesPerScanline, resultLength); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect pixel data + Span srcRow = pixels.GetPixelRowSpan(row); + for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { - // collect data - Span srcRow = pixels.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - // note: quantized parameter not used - // note: row parameter not used - IMemoryOwner r = this.EncodePixelRow((ReadOnlySpan)destSpan, null, -1); - deflateStream.Write(r.GetSpan(), 0, resultLength); + // Encode data + // Note: quantized parameter not used + // Note: row parameter not used + this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); + deflateStream.Write(filter); - IMemoryOwner temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; - } + this.SwapScanlineBuffers(); } } } @@ -1055,34 +1005,36 @@ namespace SixLabors.ImageSharp.Formats.Png ? ((blockWidth * this.bitDepth) + 7) / 8 : blockWidth * this.bytesPerPixel; - int resultLength = bytesPerScanline + 1; + int filterLength = bytesPerScanline + 1; + + this.AllocateScanlineBuffers(bytesPerScanline); - this.AllocateBuffers(bytesPerScanline, resultLength); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; + row < height; + row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect data + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); + for (int col = startCol, i = 0; + col < width; + col += Adam7.ColumnIncrement[pass]) { - // collect data - ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - IMemoryOwner r = this.EncodeAdam7IndexedPixelRow(destSpan); - deflateStream.Write(r.GetSpan(), 0, resultLength); + // Encode data + this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt); + deflateStream.Write(filter); - IMemoryOwner temp = this.currentScanline; - this.currentScanline = this.previousScanline; - this.previousScanline = temp; - } + this.SwapScanlineBuffers(); } } } @@ -1151,5 +1103,19 @@ namespace SixLabors.ImageSharp.Formats.Png return scanlineLength / mod; } + + private void SwapScanlineBuffers() + { + IMemoryOwner temp = this.previousScanline; + this.previousScanline = this.currentScanline; + this.currentScanline = temp; + } + + private static void SwapSpans(ref Span a, ref Span b) + { + Span t = b; + b = a; + a = t; + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs index a9b53e16e8..be9883a700 100644 --- a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs @@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodePaethFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeSubFilter(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeUpFilter(Span scanline, Span previousScanline, Span result, out int sum) + public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeAverageFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));