diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 47a0e0bbf0..1193eccee3 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/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 { diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 9ef7c01c61..f56cb37a81 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -37,7 +37,7 @@ namespace SixLabors /// has a different size than /// [Conditional("DEBUG")] - public static void MustBeSameSized(Span target, Span other, string parameterName) + public static void MustBeSameSized(ReadOnlySpan target, ReadOnlySpan other, string parameterName) where T : struct { if (target.Length != other.Length) @@ -57,7 +57,7 @@ namespace SixLabors /// has less items than /// [Conditional("DEBUG")] - public static void MustBeSizedAtLeast(Span target, Span minSpan, string parameterName) + public static void MustBeSizedAtLeast(ReadOnlySpan target, ReadOnlySpan minSpan, string parameterName) where T : struct { if (target.Length < minSpan.Length) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 0ab1413974..83c6389348 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index e8e0aa7043..6a89a1122a 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 116154836e..c28b877e41 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index e0f35293a4..7e0286991b 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, Span previousScanline) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(Span scanline, Span previousScanline, Span result, out int sum) + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index fd11ba1b6b..7b5f390f12 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/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 /// internal readonly struct PngChunk { - public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null) + public PngChunk(int length, PngChunkType type, IMemoryOwner 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. /// - public IManagedByteBuffer Data { get; } + public IMemoryOwner Data { get; } /// /// Gets a value indicating whether the given chunk is critical to decoding diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index c2c336c039..36d7001038 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/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 /// /// Previous scanline processed. /// - private IManagedByteBuffer previousScanline; + private IMemoryOwner previousScanline; /// /// The current scanline that is being processed. /// - private IManagedByteBuffer scanline; + private IMemoryOwner scanline; /// /// 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 /// The number of bits per value. /// The new array. /// The resulting array. - private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer) + private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IMemoryOwner 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(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 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 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 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 trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + IMemoryOwner buffer = null; + try { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline - 1, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; + + switch (this.pngColorType) + { + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - break; + break; - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; - case PngColorType.Palette: - PngScanlineProcessor.ProcessPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - this.palette, - this.paletteAlpha); + case PngColorType.Palette: + PngScanlineProcessor.ProcessPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + this.palette, + this.paletteAlpha); - break; + break; - case PngColorType.Rgb: - PngScanlineProcessor.ProcessRgbScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + case PngColorType.Rgb: + PngScanlineProcessor.ProcessRgbScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - break; + break; - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessRgbaScanline( - this.Configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessRgbaScanline( + this.Configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; + } + } + finally + { + buffer?.Dispose(); } - - buffer?.Dispose(); } /// @@ -735,78 +746,88 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + IMemoryOwner buffer = null; + try { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + ReadOnlySpan 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(); } /// @@ -1189,12 +1210,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The length of the chunk data to read. [MethodImpl(InliningOptions.ShortMethod)] - private IManagedByteBuffer ReadChunkData(int length) + private IMemoryOwner ReadChunkData(int length) { // We rent the buffer here to return it afterwards in Decode() - IManagedByteBuffer buffer = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); + IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(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 temp = this.previousScanline; this.previousScanline = this.scanline; this.scanline = temp; } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 7a285eb70b..4f6fb73567 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -80,32 +80,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The raw data of previous scanline. /// - private IManagedByteBuffer previousScanline; + private IMemoryOwner previousScanline; /// /// The raw data of current scanline. /// - private IManagedByteBuffer currentScanline; - - /// - /// The common buffer for the filters. - /// - private IManagedByteBuffer filterBuffer; - - /// - /// The ext buffer for the sub filter, . - /// - private IManagedByteBuffer subFilter; - - /// - /// The ext buffer for the average filter, . - /// - private IManagedByteBuffer averageFilter; - - /// - /// The ext buffer for the Paeth filter, . - /// - private IManagedByteBuffer paethFilter; + private IMemoryOwner currentScanline; /// /// Initializes a new instance of the class. @@ -173,17 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Png { this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.filterBuffer?.Dispose(); - this.previousScanline = null; this.currentScanline = null; - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.filterBuffer = null; } /// @@ -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 tempSpan = temp.GetSpan(); - - // We need to first create an array of luminance bytes then scale them down to the correct bit depth. - PixelOperations.Instance.ToL8Bytes( - this.configuration, - rowSpan, - tempSpan, - rowSpan.Length); - PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); - } + using IMemoryOwner temp = this.memoryAllocator.Allocate(rowSpan.Length, AllocationOptions.Clean); + int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); + Span tempSpan = temp.GetSpan(); + + // We need to first create an array of luminance bytes then scale them down to the correct bit depth. + PixelOperations.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 } /// - /// Apply filter for the raw scanline. + /// Apply the line filter for the raw scanline to enable better compression. /// - private IManagedByteBuffer FilterPixelBytes() + private void FilterPixelBytes(ref Span filter, ref Span attempt) { switch (this.options.FilterMethod) { case PngFilterMethod.None: - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; - + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + break; case PngFilterMethod.Sub: - SubFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Up: - UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), out int _); - return this.filterBuffer; + UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _); + break; case PngFilterMethod.Average: - AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; + AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; case PngFilterMethod.Paeth: - PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _); - return this.filterBuffer; - + PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; + case PngFilterMethod.Adaptive: default: - return this.GetOptimalFilteredScanline(); + this.ApplyOptimalFilteredScanline(ref filter, ref attempt); + break; } } /// - /// Encodes the pixel data line by line. - /// Each scanline is encoded in the most optimal manner to improve compression. + /// Collects the pixel data line by line for compressing. + /// Each scanline is filtered in the most optimal manner to improve compression. /// /// The pixel format. /// The row span. - /// The quantized pixels. Can be null. - /// The row. - /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + /// The filtered buffer. + /// Used for attempting optimized filtering. + /// The quantized pixels. Can be . + /// The row number. + private void CollectAndFilterPixelRow( + ReadOnlySpan rowSpan, + ref Span filter, + ref Span attempt, + IndexedImageFrame quantized, + int row) where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. /// - /// The row span. - private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan rowSpan) + /// The row span. + /// The filtered buffer. + /// Used for attempting optimized filtering. + private void EncodeAdam7IndexedPixelRow( + ReadOnlySpan row, + ref Span filter, + ref Span attempt) { // CollectPixelBytes if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(rowSpan, this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth); } else { - rowSpan.CopyTo(this.currentScanline.GetSpan()); + row.CopyTo(this.currentScanline.GetSpan()); } - return this.FilterPixelBytes(); + this.FilterPixelBytes(ref filter, ref attempt); } /// /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// to be most compressible, using lowest total variation as proxy for compressibility. /// - /// The - private IManagedByteBuffer GetOptimalFilteredScanline() + private void ApplyOptimalFilteredScanline(ref Span filter, ref Span attempt) { // Palette images don't compress well with adaptive filtering. - if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8) + // Nor do images comprising a single row. + if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8) { - NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan()); - return this.filterBuffer; + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + return; } - this.AllocateExtBuffers(); - Span scanSpan = this.currentScanline.GetSpan(); - Span prevSpan = this.previousScanline.GetSpan(); - - // This order, while different to the enumerated order is more likely to produce a smaller sum - // early on which shaves a couple of milliseconds off the processing time. - UpFilter.Encode(scanSpan, prevSpan, this.filterBuffer.GetSpan(), out int currentSum); + Span current = this.currentScanline.GetSpan(); + Span previous = this.previousScanline.GetSpan(); - // TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum. - // That way the above comment would actually be true. It used to be anyway... - // If we could use SIMD for none branching filters we could really speed it up. - int lowestSum = currentSum; - 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); + } } /// @@ -612,8 +584,8 @@ namespace SixLabors.ImageSharp.Formats.Png int colorTableLength = paletteLength * Unsafe.SizeOf(); bool hasAlpha = false; - using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); - using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); + using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength); + using IMemoryOwner alphaTable = this.memoryAllocator.Allocate(paletteLength); ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(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. /// /// The bytes per scanline. - /// Length of the result. - private void AllocateBuffers(int bytesPerScanline, int resultLength) + private void AllocateScanlineBuffers(int bytesPerScanline) { // Clean up from any potential previous runs. - this.subFilter?.Dispose(); - this.averageFilter?.Dispose(); - this.paethFilter?.Dispose(); - this.subFilter = null; - this.averageFilter = null; - this.paethFilter = null; - this.previousScanline?.Dispose(); this.currentScanline?.Dispose(); - this.filterBuffer?.Dispose(); - this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); - this.currentScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean); - this.filterBuffer = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); - } - - /// - /// Allocates the ext buffers for adaptive filter. - /// - private void AllocateExtBuffers() - { - if (this.subFilter == null) - { - int resultLength = this.filterBuffer.Length(); - - this.subFilter = this.memoryAllocator.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(bytesPerScanline, AllocationOptions.Clean); + this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); } /// @@ -969,17 +916,19 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); - int resultLength = bytesPerScanline + 1; - this.AllocateBuffers(bytesPerScanline, resultLength); + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); for (int y = 0; y < this.height; y++) { - 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 blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using (IMemoryOwner passData = this.memoryAllocator.Allocate(blockWidth)) + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect pixel data + Span srcRow = pixels.GetPixelRowSpan(row); + for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { - // collect data - Span srcRow = pixels.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - // note: quantized parameter not used - // note: row parameter not used - IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)destSpan, null, -1); - deflateStream.Write(r.Array, 0, resultLength); + // Encode data + // Note: quantized parameter not used + // Note: row parameter not used + this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); + deflateStream.Write(filter); - 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 passData = this.memoryAllocator.Allocate(blockWidth)) + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + + for (int row = startRow; + row < height; + row += Adam7.RowIncrement[pass]) { - Span destSpan = passData.Memory.Span; - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + // Collect data + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); + for (int col = startCol, i = 0; + col < width; + col += Adam7.ColumnIncrement[pass]) { - // collect data - ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - destSpan[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // encode data - 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 /// The to write to. /// The type of chunk to write. /// The containing data. - 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 data) + => this.WriteChunk(stream, type, data, 0, data.Length); /// /// Writes a chunk of a specified length to the stream at the given offset. @@ -1113,7 +1062,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing data. /// The position to offset the data at. /// The of the data to write. - private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offset, int length) + private void WriteChunk(Stream stream, PngChunkType type, Span 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 temp = this.previousScanline; + this.previousScanline = this.currentScanline; + this.currentScanline = temp; + } + + private static void SwapSpans(ref Span a, ref Span b) + { + Span t = b; + b = a; + a = t; + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs index a9b53e16e8..be9883a700 100644 --- a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs @@ -22,9 +22,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodePaethFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeSubFilter(Span scanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The filtered scanline result. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeUpFilter(Span scanline, Span previousScanline, Span result, out int sum) + public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// The bytes per pixel. /// The sum of the total variance of the filtered row [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeAverageFilter(Span scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));