Browse Source

Reduce allocations when encoding

Down to 2.8X Sys.Draw with 1/4 memory per allocation. No tsure where to
go from here. Vectors maybe?
pull/23/head
James Jackson-South 10 years ago
parent
commit
7b09a06b68
  1. 6
      src/ImageSharp/Formats/Jpg/JpegEncoder.cs
  2. 80
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  3. 26
      tests/ImageSharp.Tests/FileTestBase.cs

6
src/ImageSharp/Formats/Jpg/JpegEncoder.cs

@ -48,7 +48,11 @@ namespace ImageSharp.Formats
/// <value>The subsample ratio of the jpg image.</value> /// <value>The subsample ratio of the jpg image.</value>
public JpegSubsample Subsample public JpegSubsample Subsample
{ {
get { return this.subsample; } get
{
return this.subsample;
}
set set
{ {
this.subsample = value; this.subsample = value;

80
src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs

@ -11,7 +11,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Image encoder for writing an image to a stream as a jpeg. /// Image encoder for writing an image to a stream as a jpeg.
/// </summary> /// </summary>
internal class JpegEncoderCore internal unsafe class JpegEncoderCore
{ {
/// <summary> /// <summary>
/// The number of quantization tables. /// The number of quantization tables.
@ -102,7 +102,7 @@ namespace ImageSharp.Formats
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
}) })
}; };
@ -159,6 +159,16 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private readonly byte[] buffer = new byte[16];
/// <summary>
/// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough.
/// </summary>
private readonly byte[] emitBuffer = new byte[64];
/// <summary>
/// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + identifier.
/// </summary>
private readonly byte[] huffmanBuffer = new byte[179];
/// <summary> /// <summary>
/// The scaled quantization tables, in zig-zag order. /// The scaled quantization tables, in zig-zag order.
/// </summary> /// </summary>
@ -385,17 +395,6 @@ namespace ImageSharp.Formats
return -((-dividend + (divisor >> 1)) / divisor); return -((-dividend + (divisor >> 1)) / divisor);
} }
/// <summary>
/// Writes the given byte to the stream.
/// </summary>
/// <param name="b">The byte to write.</param>
private void WriteByte(byte b)
{
byte[] data = new byte[1];
data[0] = b;
this.outputStream.Write(data, 0, 1);
}
/// <summary> /// <summary>
/// Emits the least significant count of bits of bits to the bit-stream. /// Emits the least significant count of bits of bits to the bit-stream.
/// The precondition is bits <example>&lt; 1&lt;&lt;nBits &amp;&amp; nBits &lt;= 16</example>. /// The precondition is bits <example>&lt; 1&lt;&lt;nBits &amp;&amp; nBits &lt;= 16</example>.
@ -404,21 +403,32 @@ namespace ImageSharp.Formats
/// <param name="count">The number of bits</param> /// <param name="count">The number of bits</param>
private void Emit(uint bits, uint count) private void Emit(uint bits, uint count)
{ {
// TODO: This requires optimization. We have far too many writes to the underlying stream going on.
count += this.bitCount; count += this.bitCount;
bits <<= (int)(32 - count); bits <<= (int)(32 - count);
bits |= this.accumulatedBits; bits |= this.accumulatedBits;
while (count >= 8)
// Only write if more than 8 bits.
if (count >= 8)
{ {
byte b = (byte)(bits >> 24); // Track length
this.WriteByte(b); int len = 0;
if (b == 0xff) while (count >= 8)
{ {
this.WriteByte(0x00); byte b = (byte)(bits >> 24);
this.emitBuffer[len++] = b;
if (b == 0xff)
{
this.emitBuffer[len++] = 0x00;
}
bits <<= 8;
count -= 8;
} }
bits <<= 8; if (len > 0)
count -= 8; {
this.outputStream.Write(this.emitBuffer, 0, len);
}
} }
this.accumulatedBits = bits; this.accumulatedBits = bits;
@ -553,7 +563,7 @@ namespace ImageSharp.Formats
byte yy = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b)); byte yy = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b));
byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))); byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)));
byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))); byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)));
int index = (8 * j) + i; int index = (8 * j) + i;
yBlock[index] = yy; yBlock[index] = yy;
cbBlock[index] = cb; cbBlock[index] = cb;
@ -776,11 +786,29 @@ namespace ImageSharp.Formats
for (int i = 0; i < specs.Length; i++) for (int i = 0; i < specs.Length; i++)
{ {
HuffmanSpec spec = specs[i]; HuffmanSpec spec = specs[i];
int len = 0;
fixed (byte* huffman = this.huffmanBuffer)
{
fixed (byte* count = spec.Count)
{
fixed (byte* values = spec.Values)
{
huffman[len++] = headers[i];
for (int c = 0; c < spec.Count.Length; c++)
{
huffman[len++] = count[c];
}
for (int v = 0; v < spec.Values.Length; v++)
{
huffman[len++] = values[v];
}
}
}
}
// TODO: Investigate optimizing this. It might be better to create a single array. this.outputStream.Write(this.huffmanBuffer, 0, len);
this.WriteByte(headers[i]);
this.outputStream.Write(spec.Count, 0, spec.Count.Length);
this.outputStream.Write(spec.Values, 0, spec.Values.Length);
} }
} }

26
tests/ImageSharp.Tests/FileTestBase.cs

@ -5,8 +5,8 @@
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
/// <summary> /// <summary>
/// The test base class for reading and writing to files. /// The test base class for reading and writing to files.
@ -18,22 +18,22 @@ namespace ImageSharp.Tests
/// </summary> /// </summary>
protected static readonly List<TestFile> Files = new List<TestFile> protected static readonly List<TestFile> Files = new List<TestFile>
{ {
//new TestFile(TestImages.Png.P1), // new TestFile(TestImages.Png.P1), // Perf: Enable for local testing only
//new TestFile(TestImages.Png.Pd), // new TestFile(TestImages.Png.Pd), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only // new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Calliphora), new TestFile(TestImages.Jpeg.Calliphora),
new TestFile(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only // new TestFile(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only
new TestFile(TestImages.Jpeg.Turtle), new TestFile(TestImages.Jpeg.Turtle),
//new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only // new TestFile(TestImages.Jpeg.Fb), // Perf: Enable for local testing only
//new TestFile(TestImages.Jpeg.Progress), // Perf: Enable for local testing only // new TestFile(TestImages.Jpeg.Progress), // Perf: Enable for local testing only
//new TestFile(TestImages.Jpeg.Gamma_dalai_lama_gray). // Perf: Enable for local testing only // new TestFile(TestImages.Jpeg.GammaDalaiLamaGray), // Perf: Enable for local testing only
new TestFile(TestImages.Bmp.Car), new TestFile(TestImages.Bmp.Car),
//new TestFile(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only // new TestFile(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only
//new TestFile(TestImages.Png.Blur), // Perf: Enable for local testing only // 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.Indexed), // Perf: Enable for local testing only
new TestFile(TestImages.Png.Splash), new TestFile(TestImages.Png.Splash),
new TestFile(TestImages.Gif.Rings), new TestFile(TestImages.Gif.Rings),
//new TestFile(TestImages.Gif.Giphy) // Perf: Enable for local testing only // new TestFile(TestImages.Gif.Giphy) // Perf: Enable for local testing only
}; };
protected string CreateOutputDirectory(string path) protected string CreateOutputDirectory(string path)
@ -42,7 +42,7 @@ namespace ImageSharp.Tests
if (!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
} }
return path; return path;

Loading…
Cancel
Save