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));