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;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp 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"/> /// <paramref name="target"/> has a different size than <paramref name="other"/>
/// </exception> /// </exception>
[Conditional("DEBUG")] [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 where T : struct
{ {
if (target.Length != other.Length) if (target.Length != other.Length)
@ -57,7 +57,7 @@ namespace SixLabors
/// <paramref name="target"/> has less items than <paramref name="minSpan"/> /// <paramref name="target"/> has less items than <paramref name="minSpan"/>
/// </exception> /// </exception>
[Conditional("DEBUG")] [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 where T : struct
{ {
if (target.Length < minSpan.Length) 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(Span<byte> scanline, Span<byte> previousScanline, int bytesPerPixel) 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 scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); 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="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(Span<byte> scanline, Span<byte> previousScanline, int bytesPerPixel) 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 scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); 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="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); 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="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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)); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(Span<byte> scanline, Span<byte> previousScanline) 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 scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); 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="result">The filtered scanline result.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory; using System.Buffers;
namespace SixLabors.ImageSharp.Formats.Png namespace SixLabors.ImageSharp.Formats.Png
{ {
@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
internal readonly struct PngChunk 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.Length = length;
this.Type = type; this.Type = type;
@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the data bytes appropriate to the chunk type, if any. /// Gets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length or null. /// This field can be of zero length or null.
/// </summary> /// </summary>
public IManagedByteBuffer Data { get; } public IMemoryOwner<byte> Data { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding /// 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -84,12 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Previous scanline processed. /// Previous scanline processed.
/// </summary> /// </summary>
private IManagedByteBuffer previousScanline; private IMemoryOwner<byte> previousScanline;
/// <summary> /// <summary>
/// The current scanline that is being processed. /// The current scanline that is being processed.
/// </summary> /// </summary>
private IManagedByteBuffer scanline; private IMemoryOwner<byte> scanline;
/// <summary> /// <summary>
/// The index of the current scanline being processed. /// The index of the current scanline being processed.
@ -149,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type) switch (chunk.Type)
{ {
case PngChunkType.Header: case PngChunkType.Header:
this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.Physical: case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
@ -168,29 +169,29 @@ namespace SixLabors.ImageSharp.Formats.Png
break; break;
case PngChunkType.Palette: case PngChunkType.Palette:
var pal = new byte[chunk.Length]; var pal = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); chunk.Data.GetSpan().CopyTo(pal);
this.palette = pal; this.palette = pal;
break; break;
case PngChunkType.Transparency: case PngChunkType.Transparency:
var alpha = new byte[chunk.Length]; 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.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata); this.AssignTransparentMarkers(alpha, pngMetadata);
break; break;
case PngChunkType.Text: case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.CompressedText: case PngChunkType.CompressedText:
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.InternationalText: case PngChunkType.InternationalText:
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.Exif: case PngChunkType.Exif:
if (!this.ignoreMetadata) if (!this.ignoreMetadata)
{ {
var exifData = new byte[chunk.Length]; 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); metadata.ExifProfile = new ExifProfile(exifData);
} }
@ -239,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type) switch (chunk.Type)
{ {
case PngChunkType.Header: case PngChunkType.Header:
this.ReadHeaderChunk(pngMetadata, chunk.Data.Array); this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.Physical: case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
@ -251,19 +252,19 @@ namespace SixLabors.ImageSharp.Formats.Png
this.SkipChunkDataAndCrc(chunk); this.SkipChunkDataAndCrc(chunk);
break; break;
case PngChunkType.Text: case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.CompressedText: case PngChunkType.CompressedText:
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.InternationalText: case PngChunkType.InternationalText:
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length)); this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan());
break; break;
case PngChunkType.Exif: case PngChunkType.Exif:
if (!this.ignoreMetadata) if (!this.ignoreMetadata)
{ {
var exifData = new byte[chunk.Length]; 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); 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="bits">The number of bits per value.</param>
/// <param name="buffer">The new array.</param> /// <param name="buffer">The new array.</param>
/// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns> /// <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) if (bits >= 8)
{ {
@ -320,9 +321,9 @@ namespace SixLabors.ImageSharp.Formats.Png
return false; 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 sourceRef = ref MemoryMarshal.GetReference(source);
ref byte resultRef = ref buffer.Array[0]; ref byte resultRef = ref buffer.GetReference();
int mask = 0xFF >> (8 - bits); int mask = 0xFF >> (8 - bits);
int resultOffset = 0; int resultOffset = 0;
@ -504,7 +505,8 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
while (this.currentRow < this.header.Height) 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; this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < this.bytesPerScanline) if (this.currentRowBytesRead < this.bytesPerScanline)
{ {
@ -512,7 +514,6 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
this.currentRowBytesRead = 0; this.currentRowBytesRead = 0;
Span<byte> scanlineSpan = this.scanline.GetSpan();
switch ((FilterType)scanlineSpan[0]) switch ((FilterType)scanlineSpan[0])
{ {
@ -542,7 +543,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata);
this.SwapBuffers(); this.SwapScanlineBuffers();
this.currentRow++; this.currentRow++;
} }
} }
@ -576,7 +577,7 @@ namespace SixLabors.ImageSharp.Formats.Png
while (this.currentRow < this.header.Height) 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; this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < bytesPerInterlaceScanline) if (this.currentRowBytesRead < bytesPerInterlaceScanline)
{ {
@ -617,7 +618,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow); Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]);
this.SwapBuffers(); this.SwapScanlineBuffers();
this.currentRow += Adam7.RowIncrement[pass]; this.currentRow += Adam7.RowIncrement[pass];
} }
@ -653,70 +654,80 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. // 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) IMemoryOwner<byte> buffer = null;
? buffer.GetSpan() try
: trimmed;
switch (this.pngColorType)
{ {
case PngColorType.Grayscale: ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(
PngScanlineProcessor.ProcessGrayscaleScanline( trimmed,
this.header, this.bytesPerScanline - 1,
scanlineSpan, this.header.BitDepth,
rowSpan, out buffer)
pngMetadata.HasTransparency, ? buffer.GetSpan()
pngMetadata.TransparentL16.GetValueOrDefault(), : trimmed;
pngMetadata.TransparentL8.GetValueOrDefault());
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: case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline(
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample); this.bytesPerSample);
break; break;
case PngColorType.Palette: case PngColorType.Palette:
PngScanlineProcessor.ProcessPaletteScanline( PngScanlineProcessor.ProcessPaletteScanline(
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
this.palette, this.palette,
this.paletteAlpha); this.paletteAlpha);
break; break;
case PngColorType.Rgb: case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline( PngScanlineProcessor.ProcessRgbScanline(
this.Configuration, this.Configuration,
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
pngMetadata.HasTransparency, pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(), pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault()); pngMetadata.TransparentRgb24.GetValueOrDefault());
break; break;
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline( PngScanlineProcessor.ProcessRgbaScanline(
this.Configuration, this.Configuration,
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample); this.bytesPerSample);
break; break;
}
}
finally
{
buffer?.Dispose();
} }
buffer?.Dispose();
} }
/// <summary> /// <summary>
@ -735,78 +746,88 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. // 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) IMemoryOwner<byte> buffer = null;
? buffer.GetSpan() try
: trimmed;
switch (this.pngColorType)
{ {
case PngColorType.Grayscale: ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( trimmed,
this.header, this.bytesPerScanline,
scanlineSpan, this.header.BitDepth,
rowSpan, out buffer)
pixelOffset, ? buffer.GetSpan()
increment, : trimmed;
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(), switch (this.pngColorType)
pngMetadata.TransparentL8.GetValueOrDefault()); {
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
break; break;
case PngColorType.GrayscaleWithAlpha: case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
pixelOffset, pixelOffset,
increment, increment,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample); this.bytesPerSample);
break; break;
case PngColorType.Palette: case PngColorType.Palette:
PngScanlineProcessor.ProcessInterlacedPaletteScanline( PngScanlineProcessor.ProcessInterlacedPaletteScanline(
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
pixelOffset, pixelOffset,
increment, increment,
this.palette, this.palette,
this.paletteAlpha); this.paletteAlpha);
break; break;
case PngColorType.Rgb: case PngColorType.Rgb:
PngScanlineProcessor.ProcessInterlacedRgbScanline( PngScanlineProcessor.ProcessInterlacedRgbScanline(
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
pixelOffset, pixelOffset,
increment, increment,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample, this.bytesPerSample,
pngMetadata.HasTransparency, pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(), pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault()); pngMetadata.TransparentRgb24.GetValueOrDefault());
break; break;
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessInterlacedRgbaScanline( PngScanlineProcessor.ProcessInterlacedRgbaScanline(
this.header, this.header,
scanlineSpan, scanlineSpan,
rowSpan, rowSpan,
pixelOffset, pixelOffset,
increment, increment,
this.bytesPerPixel, this.bytesPerPixel,
this.bytesPerSample); this.bytesPerSample);
break; break;
}
}
finally
{
buffer?.Dispose();
} }
buffer?.Dispose();
} }
/// <summary> /// <summary>
@ -1189,12 +1210,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
/// <param name="length">The length of the chunk data to read.</param> /// <param name="length">The length of the chunk data to read.</param>
[MethodImpl(InliningOptions.ShortMethod)] [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() // 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; return buffer;
} }
@ -1272,9 +1293,9 @@ namespace SixLabors.ImageSharp.Formats.Png
return true; return true;
} }
private void SwapBuffers() private void SwapScanlineBuffers()
{ {
IManagedByteBuffer temp = this.previousScanline; IMemoryOwner<byte> temp = this.previousScanline;
this.previousScanline = this.scanline; this.previousScanline = this.scanline;
this.scanline = temp; this.scanline = temp;
} }

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

@ -80,32 +80,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// The raw data of previous scanline. /// The raw data of previous scanline.
/// </summary> /// </summary>
private IManagedByteBuffer previousScanline; private IMemoryOwner<byte> previousScanline;
/// <summary> /// <summary>
/// The raw data of current scanline. /// The raw data of current scanline.
/// </summary> /// </summary>
private IManagedByteBuffer currentScanline; private IMemoryOwner<byte> 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;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore" /> class. /// Initializes a new instance of the <see cref="PngEncoderCore" /> class.
@ -173,17 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
this.previousScanline?.Dispose(); this.previousScanline?.Dispose();
this.currentScanline?.Dispose(); this.currentScanline?.Dispose();
this.subFilter?.Dispose();
this.averageFilter?.Dispose();
this.paethFilter?.Dispose();
this.filterBuffer?.Dispose();
this.previousScanline = null; this.previousScanline = null;
this.currentScanline = null; this.currentScanline = null;
this.subFilter = null;
this.averageFilter = null;
this.paethFilter = null;
this.filterBuffer = null;
} }
/// <summary> /// <summary>
@ -278,21 +249,17 @@ namespace SixLabors.ImageSharp.Formats.Png
else else
{ {
// 1, 2, and 4 bit grayscale // 1, 2, and 4 bit grayscale
using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer( using IMemoryOwner<byte> temp = this.memoryAllocator.Allocate<byte>(rowSpan.Length, AllocationOptions.Clean);
rowSpan.Length, int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1);
AllocationOptions.Clean)) Span<byte> tempSpan = temp.GetSpan();
{
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); // We need to first create an array of luminance bytes then scale them down to the correct bit depth.
Span<byte> tempSpan = temp.GetSpan(); PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
// We need to first create an array of luminance bytes then scale them down to the correct bit depth. rowSpan,
PixelOperations<TPixel>.Instance.ToL8Bytes( tempSpan,
this.configuration, rowSpan.Length);
rowSpan, PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor);
tempSpan,
rowSpan.Length);
PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor);
}
} }
} }
} }
@ -444,6 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.GrayscaleWithAlpha: case PngColorType.GrayscaleWithAlpha:
this.CollectGrayscaleBytes(rowSpan); this.CollectGrayscaleBytes(rowSpan);
break; break;
case PngColorType.Rgb:
case PngColorType.RgbWithAlpha:
default: default:
this.CollectTPixelBytes(rowSpan); this.CollectTPixelBytes(rowSpan);
break; break;
@ -451,124 +420,127 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
/// <summary> /// <summary>
/// Apply filter for the raw scanline. /// Apply the line filter for the raw scanline to enable better compression.
/// </summary> /// </summary>
private IManagedByteBuffer FilterPixelBytes() private void FilterPixelBytes(ref Span<byte> filter, ref Span<byte> attempt)
{ {
switch (this.options.FilterMethod) switch (this.options.FilterMethod)
{ {
case PngFilterMethod.None: case PngFilterMethod.None:
NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); NoneFilter.Encode(this.currentScanline.GetSpan(), filter);
return this.filterBuffer; break;
case PngFilterMethod.Sub: case PngFilterMethod.Sub:
SubFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _);
return this.filterBuffer; break;
case PngFilterMethod.Up: case PngFilterMethod.Up:
UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), out int _); UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _);
return this.filterBuffer; break;
case PngFilterMethod.Average: case PngFilterMethod.Average:
AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _);
return this.filterBuffer; break;
case PngFilterMethod.Paeth: case PngFilterMethod.Paeth:
PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _);
return this.filterBuffer; break;
case PngFilterMethod.Adaptive:
default: default:
return this.GetOptimalFilteredScanline(); this.ApplyOptimalFilteredScanline(ref filter, ref attempt);
break;
} }
} }
/// <summary> /// <summary>
/// Encodes the pixel data line by line. /// Collects the pixel data line by line for compressing.
/// Each scanline is encoded in the most optimal manner to improve compression. /// Each scanline is filtered in the most optimal manner to improve compression.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The row span.</param> /// <param name="rowSpan">The row span.</param>
/// <param name="quantized">The quantized pixels. Can be null.</param> /// <param name="filter">The filtered buffer.</param>
/// <param name="row">The row.</param> /// <param name="attempt">Used for attempting optimized filtering.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns> /// <param name="quantized">The quantized pixels. Can be <see langword="null"/>.</param>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row) /// <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> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.CollectPixelBytes(rowSpan, quantized, row); this.CollectPixelBytes(rowSpan, quantized, row);
return this.FilterPixelBytes(); this.FilterPixelBytes(ref filter, ref attempt);
} }
/// <summary> /// <summary>
/// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode.
/// </summary> /// </summary>
/// <param name="rowSpan">The row span.</param> /// <param name="row">The row span.</param>
private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan<byte> rowSpan) /// <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 // CollectPixelBytes
if (this.bitDepth < 8) if (this.bitDepth < 8)
{ {
PngEncoderHelpers.ScaleDownFrom8BitArray(rowSpan, this.currentScanline.GetSpan(), this.bitDepth); PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth);
} }
else else
{ {
rowSpan.CopyTo(this.currentScanline.GetSpan()); row.CopyTo(this.currentScanline.GetSpan());
} }
return this.FilterPixelBytes(); this.FilterPixelBytes(ref filter, ref attempt);
} }
/// <summary> /// <summary>
/// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// 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. /// to be most compressible, using lowest total variation as proxy for compressibility.
/// </summary> /// </summary>
/// <returns>The <see cref="T:byte[]"/></returns> private void ApplyOptimalFilteredScanline(ref Span<byte> filter, ref Span<byte> attempt)
private IManagedByteBuffer GetOptimalFilteredScanline()
{ {
// Palette images don't compress well with adaptive filtering. // 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()); NoneFilter.Encode(this.currentScanline.GetSpan(), filter);
return this.filterBuffer; return;
} }
this.AllocateExtBuffers(); Span<byte> current = this.currentScanline.GetSpan();
Span<byte> scanSpan = this.currentScanline.GetSpan(); Span<byte> previous = this.previousScanline.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);
// TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. int min = int.MaxValue;
// That way the above comment would actually be true. It used to be anyway... SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum);
// If we could use SIMD for none branching filters we could really speed it up. if (sum < min)
int lowestSum = currentSum;
IManagedByteBuffer actualResult = this.filterBuffer;
PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum);
if (currentSum < lowestSum)
{ {
lowestSum = currentSum; min = sum;
actualResult = this.paethFilter; SwapSpans(ref filter, ref attempt);
} }
SubFilter.Encode(scanSpan, this.subFilter.GetSpan(), this.bytesPerPixel, out currentSum); UpFilter.Encode(current, previous, attempt, out sum);
if (sum < min)
if (currentSum < lowestSum)
{ {
lowestSum = currentSum; min = sum;
actualResult = this.subFilter; SwapSpans(ref filter, ref attempt);
} }
AverageFilter.Encode(scanSpan, prevSpan, this.averageFilter.GetSpan(), this.bytesPerPixel, out currentSum); AverageFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum);
if (sum < min)
if (currentSum < lowestSum)
{ {
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> /// <summary>
@ -612,8 +584,8 @@ namespace SixLabors.ImageSharp.Formats.Png
int colorTableLength = paletteLength * Unsafe.SizeOf<Rgb24>(); int colorTableLength = paletteLength * Unsafe.SizeOf<Rgb24>();
bool hasAlpha = false; bool hasAlpha = false;
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); using IMemoryOwner<byte> colorTable = this.memoryAllocator.Allocate<byte>(colorTableLength);
using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); using IMemoryOwner<byte> alphaTable = this.memoryAllocator.Allocate<byte>(paletteLength);
ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, Rgb24>(colorTable.GetSpan())); ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, Rgb24>(colorTable.GetSpan()));
ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
@ -640,12 +612,12 @@ namespace SixLabors.ImageSharp.Formats.Png
Unsafe.Add(ref alphaTableRef, i) = alpha; 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 // Write the transparency data
if (hasAlpha) 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. /// Allocates the buffers for each scanline.
/// </summary> /// </summary>
/// <param name="bytesPerScanline">The bytes per scanline.</param> /// <param name="bytesPerScanline">The bytes per scanline.</param>
/// <param name="resultLength">Length of the result.</param> private void AllocateScanlineBuffers(int bytesPerScanline)
private void AllocateBuffers(int bytesPerScanline, int resultLength)
{ {
// Clean up from any potential previous runs. // 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.previousScanline?.Dispose();
this.currentScanline?.Dispose(); this.currentScanline?.Dispose();
this.filterBuffer?.Dispose(); this.previousScanline = this.memoryAllocator.Allocate<byte>(bytesPerScanline, AllocationOptions.Clean);
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); this.currentScanline = this.memoryAllocator.Allocate<byte>(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);
}
} }
/// <summary> /// <summary>
@ -969,17 +916,19 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int bytesPerScanline = this.CalculateScanlineLength(this.width); int bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = bytesPerScanline + 1; int filterLength = bytesPerScanline + 1;
this.AllocateBuffers(bytesPerScanline, resultLength); 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++) for (int y = 0; y < this.height; y++)
{ {
IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y); this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y);
deflateStream.Write(r.Array, 0, resultLength); deflateStream.Write(filter);
this.SwapScanlineBuffers();
IManagedByteBuffer temp = this.currentScanline;
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
} }
} }
@ -1004,36 +953,33 @@ namespace SixLabors.ImageSharp.Formats.Png
? ((blockWidth * this.bitDepth) + 7) / 8 ? ((blockWidth * this.bitDepth) + 7) / 8
: blockWidth * this.bytesPerPixel; : 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; // Collect pixel data
for (int row = startRow; Span<TPixel> srcRow = pixels.GetPixelRowSpan(row);
row < height; for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass])
row += Adam7.RowIncrement[pass])
{ {
// collect data block[i++] = srcRow[col];
Span<TPixel> srcRow = pixels.GetPixelRowSpan(row); }
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])
{
destSpan[i++] = srcRow[col];
}
// encode data // Encode data
// note: quantized parameter not used // Note: quantized parameter not used
// note: row parameter not used // Note: row parameter not used
IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan<TPixel>)destSpan, null, -1); this.CollectAndFilterPixelRow<TPixel>(block, ref filter, ref attempt, null, -1);
deflateStream.Write(r.Array, 0, resultLength); deflateStream.Write(filter);
IManagedByteBuffer temp = this.currentScanline; this.SwapScanlineBuffers();
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
}
} }
} }
} }
@ -1059,34 +1005,36 @@ namespace SixLabors.ImageSharp.Formats.Png
? ((blockWidth * this.bitDepth) + 7) / 8 ? ((blockWidth * this.bitDepth) + 7) / 8
: blockWidth * this.bytesPerPixel; : 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; // Collect data
for (int row = startRow; ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row);
row < height; for (int col = startCol, i = 0;
row += Adam7.RowIncrement[pass]) col < width;
col += Adam7.ColumnIncrement[pass])
{ {
// collect data block[i++] = srcRow[col];
ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row); }
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])
{
destSpan[i++] = srcRow[col];
}
// encode data // Encode data
IManagedByteBuffer r = this.EncodeAdam7IndexedPixelRow(destSpan); this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt);
deflateStream.Write(r.Array, 0, resultLength); deflateStream.Write(filter);
IManagedByteBuffer temp = this.currentScanline; this.SwapScanlineBuffers();
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
}
} }
} }
} }
@ -1103,7 +1051,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param> /// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param> /// <param name="data">The <see cref="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> /// <summary>
/// Writes a chunk of a specified length to the stream at the given offset. /// 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="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param> /// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</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.WriteInt32BigEndian(this.buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type); BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
@ -1126,7 +1075,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
stream.Write(data, offset, length); 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); BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
@ -1154,5 +1103,19 @@ namespace SixLabors.ImageSharp.Formats.Png
return scanlineLength / mod; 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="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); 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="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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)); 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="result">The filtered scanline result.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); 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="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param> /// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

Loading…
Cancel
Save