Browse Source

Don't allocate memory for png decode filters, reuse scanline buffers

- Added png test images with different filters for local tests
af/merge-core
Oleg Bogdanov 10 years ago
parent
commit
e9ccc93da4
  1. 8
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  2. 8
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  3. 9
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  4. 6
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  5. 24
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  6. 6
      tests/ImageSharp.Tests/FileTestBase.cs
  7. 9
      tests/ImageSharp.Tests/TestImages.cs
  8. 3
      tests/ImageSharp.Tests/TestImages/Formats/Png/filter0.png
  9. 3
      tests/ImageSharp.Tests/TestImages/Formats/Png/filter1.png
  10. 3
      tests/ImageSharp.Tests/TestImages/Formats/Png/filter2.png
  11. 3
      tests/ImageSharp.Tests/TestImages/Formats/Png/filter3.png
  12. 3
      tests/ImageSharp.Tests/TestImages/Formats/Png/filter4.png
  13. 3
      tests/ImageSharp.Tests/TestImages/Formats/Png/filterVar.png

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

@ -26,22 +26,20 @@ namespace ImageSharp.Formats
public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
byte[] result = new byte[scanline.Length];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
for (int x = 1; x < scanline.Length; x++)
{
byte left = (x - bytesPerPixel < 1) ? (byte)0 : res[x - bytesPerPixel];
byte left = (x - bytesPerPixel < 1) ? (byte)0 : scan[x - bytesPerPixel];
byte above = prev[x];
res[x] = (byte)((scan[x] + Average(left, above)) % 256);
scan[x] = (byte)((scan[x] + Average(left, above)) % 256);
}
}
return result;
return scanline;
}
/// <summary>

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

@ -25,23 +25,21 @@ namespace ImageSharp.Formats
public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
byte[] result = new byte[scanline.Length];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
for (int x = 1; x < scanline.Length; x++)
{
byte left = (x - bytesPerPixel < 1) ? (byte)0 : res[x - bytesPerPixel];
byte left = (x - bytesPerPixel < 1) ? (byte)0 : scan[x - bytesPerPixel];
byte above = prev[x];
byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : prev[x - bytesPerPixel];
res[x] = (byte)((scan[x] + PaethPredicator(left, above, upperLeft)) % 256);
scan[x] = (byte)((scan[x] + PaethPredicator(left, above, upperLeft)) % 256);
}
}
return result;
return scanline;
}
/// <summary>

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

@ -21,20 +21,17 @@ namespace ImageSharp.Formats
public static byte[] Decode(byte[] scanline, int bytesPerPixel)
{
// Sub(x) + Raw(x-bpp)
byte[] result = new byte[scanline.Length];
fixed (byte* scan = scanline)
fixed (byte* res = result)
{
for (int x = 1; x < scanline.Length; x++)
{
byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : res[x - bytesPerPixel];
res[x] = (byte)((scan[x] + priorRawByte) % 256);
byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : scan[x - bytesPerPixel];
scan[x] = (byte)((scan[x] + priorRawByte) % 256);
}
}
return result;
return scanline;
}
/// <summary>

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

@ -21,21 +21,19 @@ namespace ImageSharp.Formats
public static byte[] Decode(byte[] scanline, byte[] previousScanline)
{
// Up(x) + Prior(x)
byte[] result = new byte[scanline.Length];
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
for (int x = 1; x < scanline.Length; x++)
{
byte above = prev[x];
res[x] = (byte)((scan[x] + above) % 256);
scan[x] = (byte)((scan[x] + above) % 256);
}
}
return result;
return scanline;
}
/// <summary>

24
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -10,7 +10,7 @@ namespace ImageSharp.Formats
using System.IO;
using System.Linq;
using System.Text;
/// <summary>
/// Performs the png decoding operation.
/// </summary>
@ -276,45 +276,42 @@ namespace ImageSharp.Formats
// TODO: ArrayPool<byte>.Shared.Rent(this.bytesPerScanline)
byte[] previousScanline = new byte[this.bytesPerScanline];
byte[] scanline = new byte[this.bytesPerScanline];
for (int y = 0; y < this.header.Height; y++)
{
compressedStream.Read(scanline, 0, this.bytesPerScanline);
FilterType filterType = (FilterType)scanline[0];
byte[] defilteredScanline;
// TODO: It would be good if we can reduce the memory usage here - Each filter is creating a new row.
// Every time I try to use the same approach as I have in the encoder though I keep messing up.
// Fingers crossed someone with a big brain and a kind heart will come along and finish optimizing this for me.
switch (filterType)
{
case FilterType.None:
defilteredScanline = NoneFilter.Decode(scanline);
NoneFilter.Decode(scanline);
break;
case FilterType.Sub:
defilteredScanline = SubFilter.Decode(scanline, this.bytesPerPixel);
SubFilter.Decode(scanline, this.bytesPerPixel);
break;
case FilterType.Up:
defilteredScanline = UpFilter.Decode(scanline, previousScanline);
UpFilter.Decode(scanline, previousScanline);
break;
case FilterType.Average:
defilteredScanline = AverageFilter.Decode(scanline, previousScanline, this.bytesPerPixel);
AverageFilter.Decode(scanline, previousScanline, this.bytesPerPixel);
break;
case FilterType.Paeth:
defilteredScanline = PaethFilter.Decode(scanline, previousScanline, this.bytesPerPixel);
PaethFilter.Decode(scanline, previousScanline, this.bytesPerPixel);
break;
@ -322,8 +319,11 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Unknown filter type.");
}
previousScanline = defilteredScanline;
this.ProcessDefilteredScanline(defilteredScanline, y, pixels);
this.ProcessDefilteredScanline(scanline, y, pixels);
byte[] temp = previousScanline;
previousScanline = scanline;
scanline = temp;
}
}

6
tests/ImageSharp.Tests/FileTestBase.cs

@ -32,6 +32,12 @@ namespace ImageSharp.Tests
// new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only
// new TestFile(TestImages.Png.Indexed), // Perf: Enable for local testing only
new TestFile(TestImages.Png.Splash),
// new TestFile(TestImages.Png.Filter0), // Perf: Enable for local testing only
// new TestFile(TestImages.Png.Filter1), // Perf: Enable for local testing only
// new TestFile(TestImages.Png.Filter2), // Perf: Enable for local testing only
// new TestFile(TestImages.Png.Filter3), // Perf: Enable for local testing only
// new TestFile(TestImages.Png.Filter4), // Perf: Enable for local testing only
// new TestFile(TestImages.Png.FilterVar), // Perf: Enable for local testing only
new TestFile(TestImages.Gif.Rings),
// new TestFile(TestImages.Gif.Giphy) // Perf: Enable for local testing only
};

9
tests/ImageSharp.Tests/TestImages.cs

@ -19,6 +19,15 @@ namespace ImageSharp.Tests
public static string Blur => folder + "blur.png";
public static string Indexed => folder + "indexed.png";
public static string Splash => folder + "splash.png";
// filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html
public static string Filter0 => folder + "filter0.png";
public static string Filter1 => folder + "filter1.png";
public static string Filter2 => folder + "filter2.png";
public static string Filter3 => folder + "filter3.png";
public static string Filter4 => folder + "filter4.png";
// filter changing per scanline
public static string FilterVar => folder + "filterVar.png";
}
public static class Jpeg

3
tests/ImageSharp.Tests/TestImages/Formats/Png/filter0.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0fe92b2aa2da04c885d1dbd85c834716f6cdd946364d97dcd597bb79d9e14427
size 2475

3
tests/ImageSharp.Tests/TestImages/Formats/Png/filter1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ef072ec6815ebf9b33e0553d2e4e4e7ed6911860a2512c67bcd10a9f0f09b9de
size 1180

3
tests/ImageSharp.Tests/TestImages/Formats/Png/filter2.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca4b937b3c587d5c007f193a2eec14dc96b0d23ff7d6aa9004e3badd1af9fe8f
size 1729

3
tests/ImageSharp.Tests/TestImages/Formats/Png/filter3.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2d101e3ef4f78a69437034671e93fe11faac0cfc4d44210dcca1b944caa886f7
size 1291

3
tests/ImageSharp.Tests/TestImages/Formats/Png/filter4.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c365c24153cb69fd3c162f00b296ae23a71a1595645d1aeb0ad23af680d7b4be
size 985

3
tests/ImageSharp.Tests/TestImages/Formats/Png/filterVar.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8ac0f095d2a943157e820fa121bccde08d5230af1b5830c3041d5f4da3524eba
size 426
Loading…
Cancel
Save