Browse Source

Reduce png encoder memory usage by 3/4

af/merge-core
James Jackson-South 10 years ago
parent
commit
3aa152fd8d
  1. 13
      src/ImageSharp/Common/Extensions/ComparableExtensions.cs
  2. 7
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  3. 7
      src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
  4. 7
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  5. 8
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  6. 7
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  7. 81
      src/ImageSharp/Formats/Png/PngEncoderCore.cs

13
src/ImageSharp/Common/Extensions/ComparableExtensions.cs

@ -165,5 +165,18 @@ namespace ImageSharp
{ {
return (byte)value.Clamp(0, 255); return (byte)value.Clamp(0, 255);
} }
/// <summary>
/// Swaps the references to two objects in memory.
/// </summary>
/// <param name="first">The first reference.</param>
/// <param name="second">The second reference.</param>
/// <typeparam name="T">The type of object.</typeparam>
public static void Swap<T>(ref T first, ref T second)
{
T temp = second;
second = first;
first = temp;
}
} }
} }

7
src/ImageSharp/Formats/Png/Filters/AverageFilter.cs

@ -47,13 +47,12 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
{ {
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline) fixed (byte* prev = previousScanline)
fixed (byte* res = result) fixed (byte* res = result)
@ -68,8 +67,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256); res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256);
} }
} }
return result;
} }
/// <summary> /// <summary>

7
src/ImageSharp/Formats/Png/Filters/NoneFilter.cs

@ -29,14 +29,13 @@ namespace ImageSharp.Formats
/// Encodes the scanline /// Encodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Encode(byte[] scanline, byte[] result, int bytesPerScanline)
public static byte[] Encode(byte[] scanline, int bytesPerScanline)
{ {
// Insert a byte before the data. // Insert a byte before the data.
byte[] result = new byte[bytesPerScanline + 1]; result[0] = 0;
Buffer.BlockCopy(scanline, 0, result, 1, bytesPerScanline); Buffer.BlockCopy(scanline, 0, result, 1, bytesPerScanline);
return result;
} }
} }
} }

7
src/ImageSharp/Formats/Png/Filters/PaethFilter.cs

@ -47,13 +47,12 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel, int bytesPerScanline)
{ {
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) // 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* scan = scanline)
fixed (byte* prev = previousScanline) fixed (byte* prev = previousScanline)
fixed (byte* res = result) fixed (byte* res = result)
@ -69,8 +68,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256); res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256);
} }
} }
return result;
} }
/// <summary> /// <summary>

8
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -21,7 +21,6 @@ namespace ImageSharp.Formats
public static byte[] Decode(byte[] scanline, int bytesPerPixel) public static byte[] Decode(byte[] scanline, int bytesPerPixel)
{ {
// Sub(x) + Raw(x-bpp) // Sub(x) + Raw(x-bpp)
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
{ {
for (int x = 1; x < scanline.Length; x++) for (int x = 1; x < scanline.Length; x++)
@ -38,13 +37,12 @@ namespace ImageSharp.Formats
/// Encodes the scanline /// Encodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel, int bytesPerScanline)
public static byte[] Encode(byte[] scanline, int bytesPerPixel, int bytesPerScanline)
{ {
// Sub(x) = Raw(x) - Raw(x-bpp) // Sub(x) = Raw(x) - Raw(x-bpp)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
fixed (byte* res = result) fixed (byte* res = result)
{ {
@ -57,8 +55,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - priorRawByte) % 256); res[x + 1] = (byte)((scan[x] - priorRawByte) % 256);
} }
} }
return result;
} }
} }
} }

7
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -21,7 +21,6 @@ namespace ImageSharp.Formats
public static byte[] Decode(byte[] scanline, byte[] previousScanline) public static byte[] Decode(byte[] scanline, byte[] previousScanline)
{ {
// Up(x) + Prior(x) // Up(x) + Prior(x)
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline) fixed (byte* prev = previousScanline)
{ {
@ -41,12 +40,12 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
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) // Up(x) = Raw(x) - Prior(x)
byte[] result = new byte[bytesPerScanline + 1];
fixed (byte* scan = scanline) fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline) fixed (byte* prev = previousScanline)
fixed (byte* res = result) fixed (byte* res = result)
@ -60,8 +59,6 @@ namespace ImageSharp.Formats
res[x + 1] = (byte)((scan[x] - above) % 256); res[x + 1] = (byte)((scan[x] - above) % 256);
} }
} }
return result;
} }
} }
} }

81
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -13,6 +13,8 @@ namespace ImageSharp.Formats
using Quantizers; using Quantizers;
using static ComparableExtensions;
/// <summary> /// <summary>
/// Performs the png encoding operation. /// Performs the png encoding operation.
/// </summary> /// </summary>
@ -312,9 +314,10 @@ namespace ImageSharp.Formats
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="rawScanline">The raw scanline.</param> /// <param name="rawScanline">The raw scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline.</param> /// <param name="bytesPerScanline">The number of bytes per scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
private byte[] EncodePixelRow<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int row, byte[] previousScanline, byte[] rawScanline, int bytesPerScanline) private byte[] EncodePixelRow<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result, int bytesPerScanline)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct where TPacked : struct
{ {
@ -332,7 +335,7 @@ namespace ImageSharp.Formats
break; break;
} }
return this.GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerScanline); return this.GetOptimalFilteredScanline(rawScanline, previousScanline, result, bytesPerScanline);
} }
/// <summary> /// <summary>
@ -341,45 +344,66 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="rawScanline">The raw scanline</param> /// <param name="rawScanline">The raw scanline</param>
/// <param name="previousScanline">The previous scanline</param> /// <param name="previousScanline">The previous scanline</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
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. // Palette images don't compress well with adaptive filtering.
if (this.PngColorType == PngColorType.Palette) 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. byte[] sub = ArrayPool<byte>.Shared.Rent(bytesPerScanline + 1);
Tuple<byte[], int>[] candidates = new Tuple<byte[], int>[4]; byte[] up = ArrayPool<byte>.Shared.Rent(bytesPerScanline + 1);
byte[] average = ArrayPool<byte>.Shared.Rent(bytesPerScanline + 1);
byte[] paeth = ArrayPool<byte>.Shared.Rent(bytesPerScanline + 1);
byte[] sub = SubFilter.Encode(rawScanline, this.bytesPerPixel, bytesPerScanline); try
candidates[0] = new Tuple<byte[], int>(sub, this.CalculateTotalVariation(sub, bytesPerScanline)); {
SubFilter.Encode(rawScanline, sub, this.bytesPerPixel, bytesPerScanline);
int currentTotalVariation = this.CalculateTotalVariation(sub, bytesPerScanline);
int lowestTotalVariation = currentTotalVariation;
byte[] up = UpFilter.Encode(rawScanline, previousScanline, bytesPerScanline); result = sub;
candidates[1] = new Tuple<byte[], int>(up, this.CalculateTotalVariation(up, bytesPerScanline));
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline); UpFilter.Encode(rawScanline, previousScanline, up, bytesPerScanline);
candidates[2] = new Tuple<byte[], int>(average, this.CalculateTotalVariation(average, bytesPerScanline)); currentTotalVariation = this.CalculateTotalVariation(up, bytesPerScanline);
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, this.bytesPerPixel, bytesPerScanline); if (currentTotalVariation < lowestTotalVariation)
candidates[3] = new Tuple<byte[], int>(paeth, this.CalculateTotalVariation(paeth, bytesPerScanline)); {
lowestTotalVariation = currentTotalVariation;
result = up;
}
int lowestTotalVariation = int.MaxValue; AverageFilter.Encode(rawScanline, previousScanline, average, this.bytesPerPixel, bytesPerScanline);
int lowestTotalVariationIndex = 0; currentTotalVariation = this.CalculateTotalVariation(average, bytesPerScanline);
for (int i = 0; i < candidates.Length; i++) if (currentTotalVariation < lowestTotalVariation)
{
if (candidates[i].Item2 < lowestTotalVariation)
{ {
lowestTotalVariationIndex = i; lowestTotalVariation = currentTotalVariation;
lowestTotalVariation = candidates[i].Item2; result = average;
} }
}
// ReSharper disable once RedundantAssignment PaethFilter.Encode(rawScanline, previousScanline, paeth, this.bytesPerPixel, bytesPerScanline);
return candidates[lowestTotalVariationIndex].Item1; currentTotalVariation = this.CalculateTotalVariation(paeth, bytesPerScanline);
if (currentTotalVariation < lowestTotalVariation)
{
result = paeth;
}
return result;
}
finally
{
ArrayPool<byte>.Shared.Return(sub);
ArrayPool<byte>.Shared.Return(up);
ArrayPool<byte>.Shared.Return(average);
ArrayPool<byte>.Shared.Return(paeth);
}
} }
/// <summary> /// <summary>
@ -592,6 +616,7 @@ namespace ImageSharp.Formats
byte[] previousScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline); byte[] previousScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
byte[] rawScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline); byte[] rawScanline = ArrayPool<byte>.Shared.Rent(bytesPerScanline);
int resultLength = bytesPerScanline + 1; int resultLength = bytesPerScanline + 1;
byte[] result = ArrayPool<byte>.Shared.Rent(resultLength);
byte[] buffer; byte[] buffer;
int bufferLength; int bufferLength;
@ -603,12 +628,9 @@ namespace ImageSharp.Formats
{ {
for (int y = 0; y < this.height; y++) 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; Swap(ref rawScanline, ref previousScanline);
byte[] tmp = rawScanline;
rawScanline = previousScanline;
previousScanline = tmp;
} }
} }
@ -620,6 +642,7 @@ namespace ImageSharp.Formats
memoryStream?.Dispose(); memoryStream?.Dispose();
ArrayPool<byte>.Shared.Return(previousScanline); ArrayPool<byte>.Shared.Return(previousScanline);
ArrayPool<byte>.Shared.Return(rawScanline); ArrayPool<byte>.Shared.Return(rawScanline);
ArrayPool<byte>.Shared.Return(result);
} }
// Store the chunks in repeated 64k blocks. // Store the chunks in repeated 64k blocks.

Loading…
Cancel
Save