From bdb53d88ca7cec94943ea2d7a5908a89ef87d0bd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 10 Nov 2016 00:51:32 +1100 Subject: [PATCH] Use ArrayPool --- .../Formats/Png/Filters/NoneFilter.cs | 9 ++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 73 +++++++++++-------- src/ImageSharp/project.json | 6 +- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs index 2ac590648..175e5affa 100644 --- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Formats { + using System; + /// /// The None filter, the scanline is transmitted unmodified; it is only necessary to /// insert a filter type byte before the data. @@ -27,13 +29,14 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode + /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline) + public static byte[] Encode(byte[] scanline, int bytesPerScanline) { // Insert a byte before the data. - byte[] encodedScanline = new byte[scanline.Length + 1]; + byte[] encodedScanline = new byte[bytesPerScanline + 1]; encodedScanline[0] = (byte)FilterType.None; - scanline.CopyTo(encodedScanline, 1); + Buffer.BlockCopy(scanline, 0, encodedScanline, 1, bytesPerScanline); return encodedScanline; } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 912e6532e..a06e306f5 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -6,10 +6,10 @@ namespace ImageSharp.Formats { using System; + using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; - using System.Threading.Tasks; using Quantizers; @@ -125,7 +125,7 @@ namespace ImageSharp.Formats this.chunkDataBuffer[4] = 0x0D; // Line ending CRLF this.chunkDataBuffer[5] = 0x0A; // Line ending CRLF this.chunkDataBuffer[6] = 0x1A; // EOF - this.chunkDataBuffer[7] = 0x0A; // LF + this.chunkDataBuffer[7] = 0x0A; // LF stream.Write(this.chunkDataBuffer, 0, 8); @@ -139,6 +139,11 @@ namespace ImageSharp.Formats this.PngColorType = PngColorType.Palette; } + if (this.PngColorType == PngColorType.Palette && this.Quality > 256) + { + this.Quality = 256; + } + // Set correct bit depth. this.bitDepth = this.Quality <= 256 ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8) @@ -169,8 +174,7 @@ namespace ImageSharp.Formats this.WriteHeaderChunk(stream, header); - // Collect the pixel data - // TODO: Avoid doing this all at once and try row by row. + // Collect the indexed pixel data if (this.PngColorType == PngColorType.Palette) { this.CollectIndexedBytes(image, stream, header); @@ -235,8 +239,7 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel where TPacked : struct { - // Quatize the image and get the pixels. - // TODO: It might be an idea to add a pixel accessor to QuantizedImage to allow us to work by row. + // Quantize the image and get the pixels. QuantizedImage quantized = this.WritePaletteChunk(stream, header, image); this.palettePixelData = quantized.Pixels; } @@ -254,7 +257,7 @@ namespace ImageSharp.Formats where TPacked : struct { // Copy the pixels across from the image. - byte[] bytes = new byte[4]; + // Reuse the chunk type buffer. using (PixelAccessor pixels = image.Lock()) { for (int x = 0; x < this.width; x++) @@ -262,8 +265,8 @@ namespace ImageSharp.Formats // Convert the color to YCbCr and store the luminance // Optionally store the original color alpha. int offset = x * this.bytesPerPixel; - pixels[x, row].ToBytes(bytes, 0, ComponentOrder.XYZW); - byte luminance = (byte)((0.299F * bytes[0]) + (0.587F * bytes[1]) + (0.114F * bytes[2])); + pixels[x, row].ToBytes(this.chunkTypeBuffer, 0, ComponentOrder.XYZW); + byte luminance = (byte)((0.299F * this.chunkTypeBuffer[0]) + (0.587F * this.chunkTypeBuffer[1]) + (0.114F * this.chunkTypeBuffer[2])); for (int i = 0; i < this.bytesPerPixel; i++) { @@ -273,7 +276,7 @@ namespace ImageSharp.Formats } else { - rawScanline[offset + i] = bytes[3]; + rawScanline[offset + i] = this.chunkTypeBuffer[3]; } } } @@ -355,7 +358,7 @@ namespace ImageSharp.Formats { candidates = new Tuple[1]; - byte[] none = NoneFilter.Encode(rawScanline); + byte[] none = NoneFilter.Encode(rawScanline, bytesPerScanline); candidates[0] = new Tuple(none, this.CalculateTotalVariation(none)); } else @@ -488,36 +491,44 @@ namespace ImageSharp.Formats // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; - byte[] colorTable = new byte[colorTableLength]; - - // TODO: Optimize this. - Parallel.For( - 0, - pixelCount, - Bootstrapper.Instance.ParallelOptions, - i => + byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); + byte[] bytes = ArrayPool.Shared.Rent(4); + + try + { + for (int i = 0; i < pixelCount; i++) { int offset = i * 3; - Color color = new Color(palette[i].ToVector4()); - int alpha = color.A; + palette[i].ToBytes(bytes, 0, ComponentOrder.XYZW); + + int alpha = bytes[3]; // Premultiply the color. This helps prevent banding. + // TODO: Vector? if (alpha < 255 && alpha > this.Threshold) { - color = Color.Multiply(color, new Color(alpha, alpha, alpha, 255)); + bytes[0] = (byte)(bytes[0] * alpha).Clamp(0, 255); + bytes[1] = (byte)(bytes[1] * alpha).Clamp(0, 255); + bytes[2] = (byte)(bytes[2] * alpha).Clamp(0, 255); } - colorTable[offset] = color.R; - colorTable[offset + 1] = color.G; - colorTable[offset + 2] = color.B; + colorTable[offset] = bytes[0]; + colorTable[offset + 1] = bytes[1]; + colorTable[offset + 2] = bytes[2]; if (alpha <= this.Threshold) { transparentPixels.Add((byte)offset); } - }); + } - this.WriteChunk(stream, PngChunkTypes.Palette, colorTable); + this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); + } + finally + { + ArrayPool.Shared.Return(colorTable); + ArrayPool.Shared.Return(bytes); + } // Write the transparency data if (transparentPixels.Any()) @@ -588,10 +599,8 @@ namespace ImageSharp.Formats where TPacked : struct { int bytesPerScanline = this.width * this.bytesPerPixel; - - // TODO: These could be rented - byte[] previousScanline = new byte[bytesPerScanline]; - byte[] rawScanline = new byte[bytesPerScanline]; + byte[] previousScanline = ArrayPool.Shared.Rent(bytesPerScanline); + byte[] rawScanline = ArrayPool.Shared.Rent(bytesPerScanline); byte[] buffer; int bufferLength; @@ -619,6 +628,8 @@ namespace ImageSharp.Formats } finally { + ArrayPool.Shared.Return(previousScanline); + ArrayPool.Shared.Return(rawScanline); memoryStream?.Dispose(); } diff --git a/src/ImageSharp/project.json b/src/ImageSharp/project.json index d620bc368..9b8366265 100644 --- a/src/ImageSharp/project.json +++ b/src/ImageSharp/project.json @@ -45,7 +45,11 @@ "System.Threading": "4.0.11", "System.Threading.Tasks": "4.0.11", "System.Threading.Tasks.Parallel": "4.0.1", - "StyleCop.Analyzers": { "version": "1.0.0", "type": "build" } + "StyleCop.Analyzers": { + "version": "1.0.0", + "type": "build" + }, + "System.Buffers": "4.0.0" }, "frameworks": { "netstandard1.1": {}