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.