diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs index 8f056ff9d4..f6a05eefe3 100644 --- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs @@ -165,5 +165,18 @@ namespace ImageSharp { return (byte)value.Clamp(0, 255); } + + /// + /// Swaps the references to two objects in memory. + /// + /// The first reference. + /// The second reference. + /// The type of object. + public static void Swap(ref T first, ref T second) + { + T temp = second; + second = first; + first = temp; + } } } diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 09156a5066..f6e445e918 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -47,13 +47,12 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The previous scanline. + /// The filtered scanline result. /// The bytes per pixel. /// The number of bytes per scanline - /// The - public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline) + public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline) { // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - byte[] result = new byte[bytesPerScanline + 1]; fixed (byte* scan = scanline) fixed (byte* prev = previousScanline) fixed (byte* res = result) @@ -68,8 +67,6 @@ namespace ImageSharp.Formats res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256); } } - - return result; } /// diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs index e624420347..426f9f1d9b 100644 --- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs @@ -29,14 +29,13 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode + /// The filtered scanline result. /// The number of bytes per scanline - /// The - public static byte[] Encode(byte[] scanline, int bytesPerScanline) + public static void Encode(byte[] scanline, byte[] result, int bytesPerScanline) { // Insert a byte before the data. - byte[] result = new byte[bytesPerScanline + 1]; + result[0] = 0; Buffer.BlockCopy(scanline, 0, result, 1, bytesPerScanline); - return result; } } } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index af274a891b..fbb0f027e7 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -47,13 +47,12 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The previous scanline. + /// The filtered scanline result. /// The bytes per pixel. /// The number of bytes per scanline - /// The - public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline) + public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline) { // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) - byte[] result = new byte[bytesPerScanline + 1]; fixed (byte* scan = scanline) fixed (byte* prev = previousScanline) fixed (byte* res = result) @@ -69,8 +68,6 @@ namespace ImageSharp.Formats res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256); } } - - return result; } /// diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 6b8d8fbd51..222828884f 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -21,7 +21,6 @@ namespace ImageSharp.Formats public static byte[] Decode(byte[] scanline, int bytesPerPixel) { // Sub(x) + Raw(x-bpp) - fixed (byte* scan = scanline) { for (int x = 1; x < scanline.Length; x++) @@ -38,13 +37,12 @@ namespace ImageSharp.Formats /// Encodes the scanline /// /// The scanline to encode + /// The filtered scanline result. /// The bytes per pixel. /// The number of bytes per scanline - /// The - public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline) + public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel, int bytesPerScanline) { // Sub(x) = Raw(x) - Raw(x-bpp) - byte[] result = new byte[bytesPerScanline + 1]; fixed (byte* scan = scanline) fixed (byte* res = result) { @@ -57,8 +55,6 @@ namespace ImageSharp.Formats res[x + 1] = (byte)((scan[x] - priorRawByte) % 256); } } - - return result; } } } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 1b30cd24eb..90a2f7ff75 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -21,7 +21,6 @@ namespace ImageSharp.Formats public static byte[] Decode(byte[] scanline, byte[] previousScanline) { // Up(x) + Prior(x) - fixed (byte* scan = scanline) fixed (byte* prev = previousScanline) { @@ -41,12 +40,12 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The previous scanline. + /// The filtered scanline result. /// The number of bytes per scanline /// The - public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerScanline) + public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerScanline) { // Up(x) = Raw(x) - Prior(x) - byte[] result = new byte[bytesPerScanline + 1]; fixed (byte* scan = scanline) fixed (byte* prev = previousScanline) fixed (byte* res = result) @@ -60,8 +59,6 @@ namespace ImageSharp.Formats res[x + 1] = (byte)((scan[x] - above) % 256); } } - - return result; } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index f72fbc1808..0738602914 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -13,6 +13,8 @@ namespace ImageSharp.Formats using Quantizers; + using static ComparableExtensions; + /// /// Performs the png encoding operation. /// @@ -312,9 +314,10 @@ namespace ImageSharp.Formats /// The row. /// The previous scanline. /// The raw scanline. + /// The filtered scanline result. /// The number of bytes per scanline. /// The - private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline) + private byte[] EncodePixelRow(PixelAccessor pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result, int bytesPerScanline) where TColor : struct, IPackedPixel where TPacked : struct { @@ -332,7 +335,7 @@ namespace ImageSharp.Formats break; } - return this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline); + return this.GetOptimalFilteredScanline(rawScanline, previousScanline, result, bytesPerScanline); } /// @@ -341,45 +344,66 @@ namespace ImageSharp.Formats /// /// The raw scanline /// The previous scanline + /// The filtered scanline result. /// The number of bytes per scanline /// The - private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerScanline) + private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result, int bytesPerScanline) { // Palette images don't compress well with adaptive filtering. if (this.PngColorType == PngColorType.Palette) { - return NoneFilter.Encode(rawScanline, bytesPerScanline); + NoneFilter.Encode(rawScanline, result, bytesPerScanline); + return result; } - // TODO: it would be nice to avoid the memory allocation here. - Tuple[] candidates = new Tuple[4]; + byte[] sub = ArrayPool.Shared.Rent(bytesPerScanline + 1); + byte[] up = ArrayPool.Shared.Rent(bytesPerScanline + 1); + byte[] average = ArrayPool.Shared.Rent(bytesPerScanline + 1); + byte[] paeth = ArrayPool.Shared.Rent(bytesPerScanline + 1); - byte[] sub = SubFilter.Encode(rawScanline, this.bytesPerPixel, bytesPerScanline); - candidates[0] = new Tuple(sub, this.CalculateTotalVariation(sub, bytesPerScanline)); + try + { + SubFilter.Encode(rawScanline, sub, this.bytesPerPixel, bytesPerScanline); + int currentTotalVariation = this.CalculateTotalVariation(sub, bytesPerScanline); + int lowestTotalVariation = currentTotalVariation; - byte[] up = UpFilter.Encode(rawScanline, previousScanline, bytesPerScanline); - candidates[1] = new Tuple(up, this.CalculateTotalVariation(up, bytesPerScanline)); + result = sub; - byte[] average = AverageFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline); - candidates[2] = new Tuple(average, this.CalculateTotalVariation(average, bytesPerScanline)); + UpFilter.Encode(rawScanline, previousScanline, up, bytesPerScanline); + currentTotalVariation = this.CalculateTotalVariation(up, bytesPerScanline); - byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline); - candidates[3] = new Tuple(paeth, this.CalculateTotalVariation(paeth, bytesPerScanline)); + if (currentTotalVariation < lowestTotalVariation) + { + lowestTotalVariation = currentTotalVariation; + result = up; + } - int lowestTotalVariation = int.MaxValue; - int lowestTotalVariationIndex = 0; + AverageFilter.Encode(rawScanline, previousScanline, average, this.bytesPerPixel, bytesPerScanline); + currentTotalVariation = this.CalculateTotalVariation(average, bytesPerScanline); - for (int i = 0; i < candidates.Length; i++) - { - if (candidates[i].Item2 < lowestTotalVariation) + if (currentTotalVariation < lowestTotalVariation) { - lowestTotalVariationIndex = i; - lowestTotalVariation = candidates[i].Item2; + lowestTotalVariation = currentTotalVariation; + result = average; } - } - // ReSharper disable once RedundantAssignment - return candidates[lowestTotalVariationIndex].Item1; + PaethFilter.Encode(rawScanline, previousScanline, paeth, this.bytesPerPixel, bytesPerScanline); + currentTotalVariation = this.CalculateTotalVariation(paeth, bytesPerScanline); + + if (currentTotalVariation < lowestTotalVariation) + { + result = paeth; + } + + return result; + } + finally + { + ArrayPool.Shared.Return(sub); + ArrayPool.Shared.Return(up); + ArrayPool.Shared.Return(average); + ArrayPool.Shared.Return(paeth); + } } /// @@ -592,6 +616,7 @@ namespace ImageSharp.Formats byte[] previousScanline = ArrayPool.Shared.Rent(bytesPerScanline); byte[] rawScanline = ArrayPool.Shared.Rent(bytesPerScanline); int resultLength = bytesPerScanline + 1; + byte[] result = ArrayPool.Shared.Rent(resultLength); byte[] buffer; int bufferLength; @@ -603,12 +628,9 @@ namespace ImageSharp.Formats { for (int y = 0; y < this.height; y++) { - deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, bytesPerScanline), 0, resultLength); + deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, result, bytesPerScanline), 0, resultLength); - // Do a bit of shuffling; - byte[] tmp = rawScanline; - rawScanline = previousScanline; - previousScanline = tmp; + Swap(ref rawScanline, ref previousScanline); } } @@ -620,6 +642,7 @@ namespace ImageSharp.Formats memoryStream?.Dispose(); ArrayPool.Shared.Return(previousScanline); ArrayPool.Shared.Return(rawScanline); + ArrayPool.Shared.Return(result); } // Store the chunks in repeated 64k blocks.