Browse Source

Merge branch 'master' into webp

pull/1552/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
0cf97bc31e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  2. 4
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  3. 4
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  4. 4
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  5. 2
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  6. 4
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  7. 6
      src/ImageSharp/Formats/Png/PngChunk.cs
  8. 303
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  9. 353
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  10. 10
      tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs

1
src/ImageSharp/Common/Extensions/StreamExtensions.cs

@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp
{

4
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -37,7 +37,7 @@ namespace SixLabors
/// <paramref name="target"/> has a different size than <paramref name="other"/>
/// </exception>
[Conditional("DEBUG")]
public static void MustBeSameSized<T>(Span<T> target, Span<T> other, string parameterName)
public static void MustBeSameSized<T>(ReadOnlySpan<T> target, ReadOnlySpan<T> other, string parameterName)
where T : struct
{
if (target.Length != other.Length)
@ -57,7 +57,7 @@ namespace SixLabors
/// <paramref name="target"/> has less items than <paramref name="minSpan"/>
/// </exception>
[Conditional("DEBUG")]
public static void MustBeSizedAtLeast<T>(Span<T> target, Span<T> minSpan, string parameterName)
public static void MustBeSizedAtLeast<T>(ReadOnlySpan<T> target, ReadOnlySpan<T> minSpan, string parameterName)
where T : struct
{
if (target.Length < minSpan.Length)

4
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<byte> scanline, Span<byte> previousScanline, int bytesPerPixel)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSameSized<byte>(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
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

4
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<byte> scanline, Span<byte> previousScanline, int bytesPerPixel)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSameSized<byte>(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
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

2
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

4
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<byte> scanline, Span<byte> previousScanline)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSameSized<byte>(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
/// <param name="result">The filtered scanline result.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

6
src/ImageSharp/Formats/Png/PngChunk.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using System.Buffers;
namespace SixLabors.ImageSharp.Formats.Png
{
@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal readonly struct PngChunk
{
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null)
public PngChunk(int length, PngChunkType type, IMemoryOwner<byte> data = null)
{
this.Length = length;
this.Type = type;
@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length or null.
/// </summary>
public IManagedByteBuffer Data { get; }
public IMemoryOwner<byte> Data { get; }
/// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
@ -84,12 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Previous scanline processed.
/// </summary>
private IManagedByteBuffer previousScanline;
private IMemoryOwner<byte> previousScanline;
/// <summary>
/// The current scanline that is being processed.
/// </summary>
private IManagedByteBuffer scanline;
private IMemoryOwner<byte> scanline;
/// <summary>
/// The index of the current scanline being processed.
@ -149,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
@ -168,29 +169,29 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.Palette:
var pal = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(pal);
this.palette = pal;
break;
case PngChunkType.Transparency:
var alpha = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(alpha);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
break;
case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.CompressedText:
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.InternationalText:
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
@ -239,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
@ -251,19 +252,19 @@ namespace SixLabors.ImageSharp.Formats.Png
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.CompressedText:
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.InternationalText:
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
@ -312,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="bits">The number of bits per value.</param>
/// <param name="buffer">The new array.</param>
/// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns>
private bool TryScaleUpTo8BitArray(ReadOnlySpan<byte> source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer)
private bool TryScaleUpTo8BitArray(ReadOnlySpan<byte> source, int bytesPerScanline, int bits, out IMemoryOwner<byte> buffer)
{
if (bits >= 8)
{
@ -320,9 +321,9 @@ namespace SixLabors.ImageSharp.Formats.Png
return false;
}
buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
buffer = this.memoryAllocator.Allocate<byte>(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
ref byte sourceRef = ref MemoryMarshal.GetReference(source);
ref byte resultRef = ref buffer.Array[0];
ref byte resultRef = ref buffer.GetReference();
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
@ -504,7 +505,8 @@ namespace SixLabors.ImageSharp.Formats.Png
{
while (this.currentRow < this.header.Height)
{
int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead);
Span<byte> scanlineSpan = this.scanline.GetSpan();
int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead);
this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < this.bytesPerScanline)
{
@ -512,7 +514,6 @@ namespace SixLabors.ImageSharp.Formats.Png
}
this.currentRowBytesRead = 0;
Span<byte> scanlineSpan = this.scanline.GetSpan();
switch ((FilterType)scanlineSpan[0])
{
@ -542,7 +543,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata);
this.SwapBuffers();
this.SwapScanlineBuffers();
this.currentRow++;
}
}
@ -576,7 +577,7 @@ namespace SixLabors.ImageSharp.Formats.Png
while (this.currentRow < this.header.Height)
{
int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead);
int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead);
this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < bytesPerInterlaceScanline)
{
@ -617,7 +618,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Span<TPixel> 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];
}
@ -653,70 +654,80 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType)
IMemoryOwner<byte> buffer = null;
try
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
ReadOnlySpan<byte> 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();
}
/// <summary>
@ -735,78 +746,88 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType)
IMemoryOwner<byte> buffer = null;
try
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(
trimmed,
this.bytesPerScanline,
this.header.BitDepth,
out buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType)
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
break;
break;
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
break;
break;
case PngColorType.Palette:
PngScanlineProcessor.ProcessInterlacedPaletteScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.palette,
this.paletteAlpha);
case PngColorType.Palette:
PngScanlineProcessor.ProcessInterlacedPaletteScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.palette,
this.paletteAlpha);
break;
break;
case PngColorType.Rgb:
PngScanlineProcessor.ProcessInterlacedRgbScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
case PngColorType.Rgb:
PngScanlineProcessor.ProcessInterlacedRgbScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
break;
break;
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessInterlacedRgbaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessInterlacedRgbaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
break;
break;
}
}
finally
{
buffer?.Dispose();
}
buffer?.Dispose();
}
/// <summary>
@ -1189,12 +1210,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
/// <param name="length">The length of the chunk data to read.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private IManagedByteBuffer ReadChunkData(int length)
private IMemoryOwner<byte> ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
IManagedByteBuffer buffer = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.currentStream.Read(buffer.Array, 0, length);
this.currentStream.Read(buffer.GetSpan(), 0, length);
return buffer;
}
@ -1272,9 +1293,9 @@ namespace SixLabors.ImageSharp.Formats.Png
return true;
}
private void SwapBuffers()
private void SwapScanlineBuffers()
{
IManagedByteBuffer temp = this.previousScanline;
IMemoryOwner<byte> temp = this.previousScanline;
this.previousScanline = this.scanline;
this.scanline = temp;
}

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

@ -80,32 +80,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// The raw data of previous scanline.
/// </summary>
private IManagedByteBuffer previousScanline;
private IMemoryOwner<byte> previousScanline;
/// <summary>
/// The raw data of current scanline.
/// </summary>
private IManagedByteBuffer currentScanline;
/// <summary>
/// The common buffer for the filters.
/// </summary>
private IManagedByteBuffer filterBuffer;
/// <summary>
/// The ext buffer for the sub filter, <see cref="PngFilterMethod.Adaptive"/>.
/// </summary>
private IManagedByteBuffer subFilter;
/// <summary>
/// The ext buffer for the average filter, <see cref="PngFilterMethod.Adaptive"/>.
/// </summary>
private IManagedByteBuffer averageFilter;
/// <summary>
/// The ext buffer for the Paeth filter, <see cref="PngFilterMethod.Adaptive"/>.
/// </summary>
private IManagedByteBuffer paethFilter;
private IMemoryOwner<byte> currentScanline;
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore" /> 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;
}
/// <summary>
@ -278,21 +249,17 @@ namespace SixLabors.ImageSharp.Formats.Png
else
{
// 1, 2, and 4 bit grayscale
using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer(
rowSpan.Length,
AllocationOptions.Clean))
{
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1);
Span<byte> tempSpan = temp.GetSpan();
// We need to first create an array of luminance bytes then scale them down to the correct bit depth.
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
rowSpan,
tempSpan,
rowSpan.Length);
PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor);
}
using IMemoryOwner<byte> temp = this.memoryAllocator.Allocate<byte>(rowSpan.Length, AllocationOptions.Clean);
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1);
Span<byte> tempSpan = temp.GetSpan();
// We need to first create an array of luminance bytes then scale them down to the correct bit depth.
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
rowSpan,
tempSpan,
rowSpan.Length);
PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor);
}
}
}
@ -444,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;
@ -451,124 +420,127 @@ namespace SixLabors.ImageSharp.Formats.Png
}
/// <summary>
/// Apply filter for the raw scanline.
/// Apply the line filter for the raw scanline to enable better compression.
/// </summary>
private IManagedByteBuffer FilterPixelBytes()
private void FilterPixelBytes(ref Span<byte> filter, ref Span<byte> 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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The row span.</param>
/// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
/// <param name="filter">The filtered buffer.</param>
/// <param name="attempt">Used for attempting optimized filtering.</param>
/// <param name="quantized">The quantized pixels. Can be <see langword="null"/>.</param>
/// <param name="row">The row number.</param>
private void CollectAndFilterPixelRow<TPixel>(
ReadOnlySpan<TPixel> rowSpan,
ref Span<byte> filter,
ref Span<byte> attempt,
IndexedImageFrame<TPixel> quantized,
int row)
where TPixel : unmanaged, IPixel<TPixel>
{
this.CollectPixelBytes(rowSpan, quantized, row);
return this.FilterPixelBytes();
this.FilterPixelBytes(ref filter, ref attempt);
}
/// <summary>
/// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode.
/// </summary>
/// <param name="rowSpan">The row span.</param>
private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan<byte> rowSpan)
/// <param name="row">The row span.</param>
/// <param name="filter">The filtered buffer.</param>
/// <param name="attempt">Used for attempting optimized filtering.</param>
private void EncodeAdam7IndexedPixelRow(
ReadOnlySpan<byte> row,
ref Span<byte> filter,
ref Span<byte> 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);
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
private IManagedByteBuffer GetOptimalFilteredScanline()
private void ApplyOptimalFilteredScanline(ref Span<byte> filter, ref Span<byte> 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<byte> scanSpan = this.currentScanline.GetSpan();
Span<byte> 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<byte> current = this.currentScanline.GetSpan();
Span<byte> 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;
IManagedByteBuffer 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);
}
}
/// <summary>
@ -612,8 +584,8 @@ namespace SixLabors.ImageSharp.Formats.Png
int colorTableLength = paletteLength * Unsafe.SizeOf<Rgb24>();
bool hasAlpha = false;
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength);
using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength);
using IMemoryOwner<byte> colorTable = this.memoryAllocator.Allocate<byte>(colorTableLength);
using IMemoryOwner<byte> alphaTable = this.memoryAllocator.Allocate<byte>(paletteLength);
ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, Rgb24>(colorTable.GetSpan()));
ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
@ -640,12 +612,12 @@ namespace SixLabors.ImageSharp.Formats.Png
Unsafe.Add(ref alphaTableRef, i) = alpha;
}
this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength);
this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength);
// Write the transparency data
if (hasAlpha)
{
this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength);
this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength);
}
}
@ -924,38 +896,13 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Allocates the buffers for each scanline.
/// </summary>
/// <param name="bytesPerScanline">The bytes per scanline.</param>
/// <param name="resultLength">Length of the result.</param>
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.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean);
this.currentScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean);
this.filterBuffer = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
}
/// <summary>
/// Allocates the ext buffers for adaptive filter.
/// </summary>
private void AllocateExtBuffers()
{
if (this.subFilter == null)
{
int resultLength = this.filterBuffer.Length();
this.subFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.averageFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.paethFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
}
this.previousScanline = this.memoryAllocator.Allocate<byte>(bytesPerScanline, AllocationOptions.Clean);
this.currentScanline = this.memoryAllocator.Allocate<byte>(bytesPerScanline, AllocationOptions.Clean);
}
/// <summary>
@ -969,17 +916,19 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = bytesPerScanline + 1;
this.AllocateBuffers(bytesPerScanline, resultLength);
int filterLength = bytesPerScanline + 1;
this.AllocateScanlineBuffers(bytesPerScanline);
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int y = 0; y < this.height; y++)
{
IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y);
deflateStream.Write(r.Array, 0, resultLength);
IManagedByteBuffer 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();
}
}
@ -1004,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<TPixel> blockBuffer = this.memoryAllocator.Allocate<TPixel>(blockWidth);
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using (IMemoryOwner<TPixel> passData = this.memoryAllocator.Allocate<TPixel>(blockWidth))
Span<TPixel> block = blockBuffer.GetSpan();
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int row = startRow; row < height; row += Adam7.RowIncrement[pass])
{
Span<TPixel> destSpan = passData.Memory.Span;
for (int row = startRow;
row < height;
row += Adam7.RowIncrement[pass])
// Collect pixel data
Span<TPixel> srcRow = pixels.GetPixelRowSpan(row);
for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass])
{
// collect data
Span<TPixel> 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
IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan<TPixel>)destSpan, null, -1);
deflateStream.Write(r.Array, 0, resultLength);
// Encode data
// Note: quantized parameter not used
// Note: row parameter not used
this.CollectAndFilterPixelRow<TPixel>(block, ref filter, ref attempt, null, -1);
deflateStream.Write(filter);
IManagedByteBuffer temp = this.currentScanline;
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
}
this.SwapScanlineBuffers();
}
}
}
@ -1059,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.AllocateBuffers(bytesPerScanline, resultLength);
this.AllocateScanlineBuffers(bytesPerScanline);
using (IMemoryOwner<byte> passData = this.memoryAllocator.Allocate<byte>(blockWidth))
using IMemoryOwner<byte> blockBuffer = this.memoryAllocator.Allocate<byte>(blockWidth);
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
Span<byte> block = blockBuffer.GetSpan();
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int row = startRow;
row < height;
row += Adam7.RowIncrement[pass])
{
Span<byte> destSpan = passData.Memory.Span;
for (int row = startRow;
row < height;
row += Adam7.RowIncrement[pass])
// Collect data
ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row);
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])
{
// collect data
ReadOnlySpan<byte> 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
IManagedByteBuffer r = this.EncodeAdam7IndexedPixelRow(destSpan);
deflateStream.Write(r.Array, 0, resultLength);
// Encode data
this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt);
deflateStream.Write(filter);
IManagedByteBuffer temp = this.currentScanline;
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
}
this.SwapScanlineBuffers();
}
}
}
@ -1103,7 +1051,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <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>
private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data)
=> this.WriteChunk(stream, type, data, 0, data.Length);
/// <summary>
/// Writes a chunk of a specified length to the stream at the given offset.
@ -1113,7 +1062,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T: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, byte[] data, int offset, int length)
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int offset, int length)
{
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
@ -1126,7 +1075,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
stream.Write(data, offset, length);
crc = Crc32.Calculate(crc, data.AsSpan(offset, length));
crc = Crc32.Calculate(crc, data.Slice(offset, length));
}
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
@ -1154,5 +1103,19 @@ namespace SixLabors.ImageSharp.Formats.Png
return scanlineLength / mod;
}
private void SwapScanlineBuffers()
{
IMemoryOwner<byte> temp = this.previousScanline;
this.previousScanline = this.currentScanline;
this.currentScanline = temp;
}
private static void SwapSpans<T>(ref Span<T> a, ref Span<T> b)
{
Span<T> t = b;
b = a;
a = t;
}
}
}

10
tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs

@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodePaethFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void EncodePaethFilter(ReadOnlySpan<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSameSized<byte>(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
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeSubFilter(Span<byte> scanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void EncodeSubFilter(ReadOnlySpan<byte> scanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
/// <param name="result">The filtered scanline result.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeUpFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, out int sum)
public static void EncodeUpFilter(ReadOnlySpan<byte> scanline, Span<byte> previousScanline, Span<byte> 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
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EncodeAverageFilter(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void EncodeAverageFilter(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

Loading…
Cancel
Save