diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs index 0f67ba5fb..87d794902 100644 --- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs @@ -21,11 +21,12 @@ namespace ImageSharp.Formats /// The scanline to encode /// The filtered scanline result. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(byte[] scanline, byte[] result) + public static void Encode(BufferSpan scanline, BufferSpan result) { // Insert a byte before the data. result[0] = 0; - Buffer.BlockCopy(scanline, 0, result, 1, scanline.Length); + result = result.Slice(1); + BufferSpan.Copy(scanline, result); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index b165d0935..8153d61bb 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -131,12 +131,12 @@ namespace ImageSharp.Formats /// /// Previous scanline processed /// - private byte[] previousScanline; + private Buffer previousScanline; /// /// The current scanline that is being processed /// - private byte[] scanline; + private Buffer scanline; /// /// The index of the current scanline being processed @@ -252,11 +252,8 @@ namespace ImageSharp.Formats finally { pixels?.Dispose(); - if (this.previousScanline != null) - { - ArrayPool.Shared.Return(this.previousScanline); - ArrayPool.Shared.Return(this.scanline); - } + this.scanline?.Dispose(); + this.previousScanline?.Dispose(); } } @@ -345,12 +342,8 @@ namespace ImageSharp.Formats this.bytesPerSample = this.header.BitDepth / 8; } - this.previousScanline = ArrayPool.Shared.Rent(this.bytesPerScanline); - this.scanline = ArrayPool.Shared.Rent(this.bytesPerScanline); - - // Zero out the scanlines, because the bytes that are rented from the arraypool may not be zero. - Array.Clear(this.scanline, 0, this.bytesPerScanline); - Array.Clear(this.previousScanline, 0, this.bytesPerScanline); + this.previousScanline = Buffer.CreateClean(this.bytesPerScanline); + this.scanline = Buffer.CreateClean(this.bytesPerScanline); } /// @@ -429,7 +422,7 @@ namespace ImageSharp.Formats { while (this.currentRow < this.header.Height) { - int bytesRead = compressedStream.Read(this.scanline, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); + int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < this.bytesPerScanline) { @@ -438,8 +431,8 @@ namespace ImageSharp.Formats this.currentRowBytesRead = 0; - var scanSpan = new BufferSpan(this.scanline); - var prevSpan = new BufferSpan(this.previousScanline); + BufferSpan scanSpan = this.scanline.Span; + BufferSpan prevSpan = this.previousScanline.Span; var filterType = (FilterType)scanSpan[0]; switch (filterType) @@ -471,7 +464,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Unknown filter type."); } - this.ProcessDefilteredScanline(this.scanline, pixels); + this.ProcessDefilteredScanline(this.scanline.Array, pixels); Swap(ref this.scanline, ref this.previousScanline); this.currentRow++; @@ -504,7 +497,7 @@ namespace ImageSharp.Formats while (this.currentRow < this.header.Height) { - int bytesRead = compressedStream.Read(this.scanline, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); + int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); this.currentRowBytesRead += bytesRead; if (this.currentRowBytesRead < bytesPerInterlaceScanline) { @@ -513,8 +506,8 @@ namespace ImageSharp.Formats this.currentRowBytesRead = 0; - var scanSpan = new BufferSpan(this.scanline); - var prevSpan = new BufferSpan(this.previousScanline); + BufferSpan scanSpan = this.scanline.Span; + BufferSpan prevSpan = this.previousScanline.Span; var filterType = (FilterType)scanSpan[0]; switch (filterType) @@ -546,7 +539,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Unknown filter type."); } - this.ProcessInterlacedDefilteredScanline(this.scanline, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); + this.ProcessInterlacedDefilteredScanline(this.scanline.Array, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); Swap(ref this.scanline, ref this.previousScanline); diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index e7b6bf30e..f89b624f7 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -33,8 +33,10 @@ namespace ImageSharp.Formats public void Encode(Image image, Stream stream, IPngEncoderOptions options) where TPixel : struct, IPixel { - PngEncoderCore encode = new PngEncoderCore(options); - encode.Encode(image, stream); + using (var encode = new PngEncoderCore(options)) + { + encode.Encode(image, stream); + } } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index bd3cd5fe7..e7ec7d243 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Formats /// /// Performs the png encoding operation. /// - internal sealed class PngEncoderCore + internal sealed class PngEncoderCore : IDisposable { /// /// The maximum block size, defaults at 64k for uncompressed blocks. @@ -72,29 +72,44 @@ namespace ImageSharp.Formats private int bytesPerPixel; /// - /// The number of bytes per scanline + /// The number of bytes per scanline. /// private int bytesPerScanline; + /// + /// The previous scanline. + /// + private Buffer previousScanline; + + /// + /// The raw scanline. + /// + private Buffer rawScanline; + + /// + /// The filtered scanline result. + /// + private Buffer result; + /// /// The buffer for the sub filter /// - private byte[] sub; + private Buffer sub; /// /// The buffer for the up filter /// - private byte[] up; + private Buffer up; /// /// The buffer for the average filter /// - private byte[] average; + private Buffer average; /// /// The buffer for the paeth filter /// - private byte[] paeth; + private Buffer paeth; /// /// The quality of output for images. @@ -212,6 +227,20 @@ namespace ImageSharp.Formats stream.Flush(); } + /// + /// Disposes PngEncoderCore instance, disposing it's internal buffers. + /// + public void Dispose() + { + this.previousScanline?.Dispose(); + this.rawScanline?.Dispose(); + this.result?.Dispose(); + this.sub?.Dispose(); + this.up?.Dispose(); + this.average?.Dispose(); + this.paeth?.Dispose(); + } + /// /// Writes an integer to the byte array. /// @@ -273,10 +302,11 @@ namespace ImageSharp.Formats /// The pixel format. /// The image pixels accessor. /// The row index. - /// The raw scanline. - private void CollectGrayscaleBytes(PixelAccessor pixels, int row, byte[] rawScanline) + private void CollectGrayscaleBytes(PixelAccessor pixels, int row) where TPixel : struct, IPixel { + byte[] rawScanlineArray = this.rawScanline.Array; + // Copy the pixels across from the image. // Reuse the chunk type buffer. for (int x = 0; x < this.width; x++) @@ -291,11 +321,11 @@ namespace ImageSharp.Formats { if (i == 0) { - rawScanline[offset] = luminance; + rawScanlineArray[offset] = luminance; } else { - rawScanline[offset + i] = this.chunkTypeBuffer[3]; + rawScanlineArray[offset + i] = this.chunkTypeBuffer[3]; } } } @@ -307,14 +337,18 @@ namespace ImageSharp.Formats /// The pixel format. /// The image pixel accessor. /// The row index. - /// The raw scanline. - private void CollecTPixelBytes(PixelAccessor pixels, int row, byte[] rawScanline) + private void CollecTPixelBytes(PixelAccessor pixels, int row) where TPixel : struct, IPixel { - // We can use the optimized PixelAccessor here and copy the bytes in unmanaged memory. - using (var pixelRow = new PixelArea(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz)) + BufferSpan rowSpan = pixels.GetRowSpan(row); + + if (this.bytesPerPixel == 4) + { + PixelOperations.Instance.ToXyzwBytes(rowSpan, this.rawScanline, this.width); + } + else { - pixels.CopyTo(pixelRow, row); + PixelOperations.Instance.ToXyzBytes(rowSpan, this.rawScanline, this.width); } } @@ -325,89 +359,83 @@ namespace ImageSharp.Formats /// The pixel format. /// The image pixel accessor. /// The row. - /// The previous scanline. - /// The raw scanline. - /// The filtered scanline result. /// The - private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result) + private Buffer EncodePixelRow(PixelAccessor pixels, int row) where TPixel : struct, IPixel { switch (this.pngColorType) { case PngColorType.Palette: - Buffer.BlockCopy(this.palettePixelData, row * rawScanline.Length, rawScanline, 0, rawScanline.Length); + Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length, this.rawScanline.Array, 0, this.rawScanline.Length); break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: - this.CollectGrayscaleBytes(pixels, row, rawScanline); + this.CollectGrayscaleBytes(pixels, row); break; default: - this.CollecTPixelBytes(pixels, row, rawScanline); + this.CollecTPixelBytes(pixels, row); break; } - return this.GetOptimalFilteredScanline(rawScanline, previousScanline, result); + return this.GetOptimalFilteredScanline(); } /// /// 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 raw scanline - /// The previous scanline - /// The filtered scanline result. /// The - private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) + private Buffer GetOptimalFilteredScanline() { - var scanSpan = new BufferSpan(rawScanline); - var prevSpan = new BufferSpan(previousScanline); + BufferSpan scanSpan = this.rawScanline.Span; + BufferSpan prevSpan = this.previousScanline.Span; // Palette images don't compress well with adaptive filtering. if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { - NoneFilter.Encode(rawScanline, result); - return result; + NoneFilter.Encode(this.rawScanline, this.result); + return this.result; } // 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. - var upSpan = new BufferSpan(this.up); + BufferSpan upSpan = this.up.Span; UpFilter.Encode(scanSpan, prevSpan, upSpan, this.bytesPerScanline); int currentSum = this.CalculateTotalVariation(upSpan, int.MaxValue); int lowestSum = currentSum; - result = this.up; + Buffer actualResult = this.up; - var paethSpan = new BufferSpan(this.paeth); + BufferSpan paethSpan = this.paeth.Span; PaethFilter.Encode(scanSpan, prevSpan, paethSpan, this.bytesPerScanline, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(paethSpan, currentSum); if (currentSum < lowestSum) { lowestSum = currentSum; - result = this.paeth; + actualResult = this.paeth; } - var subSpan = new BufferSpan(this.sub); + BufferSpan subSpan = this.sub.Span; SubFilter.Encode(scanSpan, subSpan, this.bytesPerScanline, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(subSpan, int.MaxValue); if (currentSum < lowestSum) { lowestSum = currentSum; - result = this.sub; + actualResult = this.sub; } - var averageSpan = new BufferSpan(this.average); + BufferSpan averageSpan = this.average.Span; AverageFilter.Encode(scanSpan, prevSpan, averageSpan, this.bytesPerScanline, this.bytesPerPixel); currentSum = this.CalculateTotalVariation(averageSpan, currentSum); if (currentSum < lowestSum) { - result = this.average; + actualResult = this.average; } - return result; + return actualResult; } /// @@ -617,17 +645,18 @@ namespace ImageSharp.Formats where TPixel : struct, IPixel { this.bytesPerScanline = this.width * this.bytesPerPixel; - byte[] previousScanline = new byte[this.bytesPerScanline]; - byte[] rawScanline = new byte[this.bytesPerScanline]; int resultLength = this.bytesPerScanline + 1; - byte[] result = new byte[resultLength]; + + this.previousScanline = new Buffer(this.bytesPerScanline); + this.rawScanline = new Buffer(this.bytesPerScanline); + this.result = new Buffer(resultLength); if (this.pngColorType != PngColorType.Palette) { - this.sub = new byte[resultLength]; - this.up = new byte[resultLength]; - this.average = new byte[resultLength]; - this.paeth = new byte[resultLength]; + this.sub = Buffer.CreateClean(resultLength); + this.up = Buffer.CreateClean(resultLength); + this.average = Buffer.CreateClean(resultLength); + this.paeth = Buffer.CreateClean(resultLength); } byte[] buffer; @@ -640,9 +669,10 @@ namespace ImageSharp.Formats { for (int y = 0; y < this.height; y++) { - deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, result), 0, resultLength); + Buffer r = this.EncodePixelRow(pixels, y); + deflateStream.Write(r.Array, 0, resultLength); - Swap(ref rawScanline, ref previousScanline); + Swap(ref this.rawScanline, ref this.previousScanline); } }