diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpg/JpegEncoder.cs index 7b3ffe5b35..0fc1f3d353 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoder.cs @@ -48,7 +48,11 @@ namespace ImageSharp.Formats /// The subsample ratio of the jpg image. public JpegSubsample Subsample { - get { return this.subsample; } + get + { + return this.subsample; + } + set { this.subsample = value; diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index e1eab40151..c9ffd855b5 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Formats /// /// Image encoder for writing an image to a stream as a jpeg. /// - internal class JpegEncoderCore + internal unsafe class JpegEncoderCore { /// /// The number of quantization tables. @@ -102,7 +102,7 @@ namespace ImageSharp.Formats 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 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 /// private readonly byte[] buffer = new byte[16]; + /// + /// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. + /// + private readonly byte[] emitBuffer = new byte[64]; + + /// + /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + identifier. + /// + private readonly byte[] huffmanBuffer = new byte[179]; + /// /// The scaled quantization tables, in zig-zag order. /// @@ -385,17 +395,6 @@ namespace ImageSharp.Formats return -((-dividend + (divisor >> 1)) / divisor); } - /// - /// Writes the given byte to the stream. - /// - /// The byte to write. - private void WriteByte(byte b) - { - byte[] data = new byte[1]; - data[0] = b; - this.outputStream.Write(data, 0, 1); - } - /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits < 1<<nBits && nBits <= 16. @@ -404,21 +403,32 @@ namespace ImageSharp.Formats /// The number of bits 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; bits <<= (int)(32 - count); bits |= this.accumulatedBits; - while (count >= 8) + + // Only write if more than 8 bits. + if (count >= 8) { - byte b = (byte)(bits >> 24); - this.WriteByte(b); - if (b == 0xff) + // Track length + int len = 0; + 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; - count -= 8; + if (len > 0) + { + this.outputStream.Write(this.emitBuffer, 0, len); + } } this.accumulatedBits = bits; @@ -553,7 +563,7 @@ namespace ImageSharp.Formats 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 cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))); - + int index = (8 * j) + i; yBlock[index] = yy; cbBlock[index] = cb; @@ -776,11 +786,29 @@ namespace ImageSharp.Formats for (int i = 0; i < specs.Length; 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.WriteByte(headers[i]); - this.outputStream.Write(spec.Count, 0, spec.Count.Length); - this.outputStream.Write(spec.Values, 0, spec.Values.Length); + this.outputStream.Write(this.huffmanBuffer, 0, len); } } diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 2b53d02538..b9a226fc8c 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -5,8 +5,8 @@ namespace ImageSharp.Tests { - using System.IO; using System.Collections.Generic; + using System.IO; /// /// The test base class for reading and writing to files. @@ -18,22 +18,22 @@ namespace ImageSharp.Tests /// protected static readonly List Files = new List { - //new TestFile(TestImages.Png.P1), - //new TestFile(TestImages.Png.Pd), - new TestFile(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only + // new TestFile(TestImages.Png.P1), // Perf: Enable for local testing only + // 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.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.Fb), // 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.Fb), // Perf: Enable for local testing only + // new TestFile(TestImages.Jpeg.Progress), // 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.Neg_height), // 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.Bmp.Neg_height), // 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.Splash), 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) @@ -42,7 +42,7 @@ namespace ImageSharp.Tests if (!Directory.Exists(path)) { - Directory.CreateDirectory(path); + Directory.CreateDirectory(path); } return path;