From 0d48f532e92c4de8ce6604b720f3d39baca45fe0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 22 Jan 2017 04:25:27 +0100 Subject: [PATCH 01/26] started implementing mixed error management in JpegDecoderCore --- .../Components/Decoder/Bits.cs | 15 ++- .../Components/Decoder/Bytes.cs | 10 +- .../Components/Decoder/DecoderErrorCode.cs | 29 +++++ .../Components/Decoder/DecoderThrowHelper.cs | 49 +++++++++ .../Components/Decoder/EOFException.cs | 21 ++++ .../Components/Decoder/JpegScanDecoder.cs | 19 ++-- .../Decoder/MissingFF00Exception.cs | 17 +++ .../JpegDecoderCore.cs | 102 +++++++++--------- .../Formats/Jpg/BadEofJpegTests.cs | 2 +- 9 files changed, 188 insertions(+), 76 deletions(-) create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/MissingFF00Exception.cs diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs index 88aa8a3fe8..8e46e18066 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs @@ -39,11 +39,11 @@ namespace ImageSharp.Formats.Jpg /// Jpeg decoder /// Error code [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal JpegDecoderCore.ErrorCodes EnsureNBits(int n, JpegDecoderCore decoder) + internal DecoderErrorCode EnsureNBits(int n, JpegDecoderCore decoder) { while (true) { - JpegDecoderCore.ErrorCodes errorCode; + DecoderErrorCode errorCode; // Grab the decode bytes, use them and then set them // back on the decoder. @@ -51,7 +51,7 @@ namespace ImageSharp.Formats.Jpg byte c = decoderBytes.ReadByteStuffedByte(decoder.InputStream, out errorCode); decoder.Bytes = decoderBytes; - if (errorCode != JpegDecoderCore.ErrorCodes.NoError) + if (errorCode != DecoderErrorCode.NoError) { return errorCode; } @@ -69,7 +69,7 @@ namespace ImageSharp.Formats.Jpg if (this.UnreadBits >= n) { - return JpegDecoderCore.ErrorCodes.NoError; + return DecoderErrorCode.NoError; } } } @@ -84,11 +84,8 @@ namespace ImageSharp.Formats.Jpg { if (this.UnreadBits < t) { - JpegDecoderCore.ErrorCodes errorCode = this.EnsureNBits(t, decoder); - if (errorCode != JpegDecoderCore.ErrorCodes.NoError) - { - throw new JpegDecoderCore.MissingFF00Exception(); - } + DecoderErrorCode errorCode = this.EnsureNBits(t, decoder); + errorCode.EnsureNoError(); } this.UnreadBits -= t; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs index b91420b42e..e33f852ff3 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs @@ -69,11 +69,11 @@ namespace ImageSharp.Formats.Jpg /// Input stream /// Error code /// The - internal byte ReadByteStuffedByte(Stream inputStream, out JpegDecoderCore.ErrorCodes errorCode) + internal byte ReadByteStuffedByte(Stream inputStream, out DecoderErrorCode errorCode) { byte x; - errorCode = JpegDecoderCore.ErrorCodes.NoError; + errorCode = DecoderErrorCode.NoError; // Take the fast path if bytes.buf contains at least two bytes. if (this.I + 2 <= this.J) @@ -88,7 +88,7 @@ namespace ImageSharp.Formats.Jpg if (this.Buffer[this.I] != 0x00) { - errorCode = JpegDecoderCore.ErrorCodes.MissingFF00; + errorCode = DecoderErrorCode.MissingFF00; return 0; // throw new MissingFF00Exception(); @@ -112,7 +112,7 @@ namespace ImageSharp.Formats.Jpg this.UnreadableBytes = 2; if (x != 0x00) { - errorCode = JpegDecoderCore.ErrorCodes.MissingFF00; + errorCode = DecoderErrorCode.MissingFF00; return 0; // throw new MissingFF00Exception(); @@ -167,7 +167,7 @@ namespace ImageSharp.Formats.Jpg int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J); if (n == 0) { - throw new JpegDecoderCore.EOFException(); + throw new EOFException(); } this.J += n; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs new file mode 100644 index 0000000000..4adc9fa7f0 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats +{ + /// + /// Represents "recoverable" decoder errors. + /// + internal enum DecoderErrorCode + { + /// + /// NoError + /// + NoError, + + /// + /// MissingFF00 + /// + // ReSharper disable once InconsistentNaming + MissingFF00, + + /// + /// End of stream reached unexpectedly + /// + UnexpectedEndOfFile + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs new file mode 100644 index 0000000000..33321bff91 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Encapsulates exception thrower methods for the Jpeg Encoder + /// + internal static class DecoderThrowHelper + { + /// + /// Throws an exception that belongs to the given + /// + /// The + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowExceptionForErrorCode(this DecoderErrorCode errorCode) + { + switch (errorCode) + { + case DecoderErrorCode.NoError: + throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode)); + case DecoderErrorCode.MissingFF00: + throw new MissingFF00Exception(); + case DecoderErrorCode.UnexpectedEndOfFile: + throw new EOFException(); + default: + throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null); + } + } + + /// + /// Throws an exception if the given defines an error. + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EnsureNoError(this DecoderErrorCode errorCode) + { + if (errorCode != DecoderErrorCode.NoError) + { + ThrowExceptionForErrorCode(errorCode); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs new file mode 100644 index 0000000000..81857b456c --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + + /// + /// The EOF (End of File exception). + /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker + /// + internal class EOFException : Exception + { + public EOFException() + : base("Reached end of stream before proceeding EOI marker!") + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 9fef5010df..8c2f079707 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -127,9 +127,9 @@ namespace ImageSharp.Formats.Jpg { for (int mx = 0; mx < decoder.MCUCountX; mx++) { - for (int i = 0; i < this.componentScanCount; i++) + for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) { - this.componentIndex = this.pointers.ComponentScan[i].ComponentIndex; + this.componentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; this.hi = decoder.ComponentArray[this.componentIndex].HorizontalFactor; int vi = decoder.ComponentArray[this.componentIndex].VerticalFactor; @@ -190,7 +190,7 @@ namespace ImageSharp.Formats.Jpg this.data.Block.Clear(); } - this.ProcessBlockImpl(decoder, i); + this.ProcessBlockImpl(decoder, scanIndex); } // for j @@ -204,6 +204,7 @@ namespace ImageSharp.Formats.Jpg // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, // but this one assumes well-formed input, and hence the restart marker follows immediately. decoder.ReadFull(decoder.Temp, 0, 2); + if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) { throw new ImageFormatException("Bad RST marker"); @@ -306,12 +307,12 @@ namespace ImageSharp.Formats.Jpg /// Process the current block at (, ) /// /// The decoder - /// The index of the scan - private void ProcessBlockImpl(JpegDecoderCore decoder, int i) + /// The index of the scan + private void ProcessBlockImpl(JpegDecoderCore decoder, int scanIndex) { var b = this.pointers.Block; - int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[i].AcTableSelector; + int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { this.Refine(decoder, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); @@ -319,6 +320,7 @@ namespace ImageSharp.Formats.Jpg else { int zig = this.zigStart; + DecoderErrorCode errorCode; if (zig == 0) { zig++; @@ -326,13 +328,15 @@ namespace ImageSharp.Formats.Jpg // Decode the DC coefficient, as specified in section F.2.2.1. byte value = decoder.DecodeHuffman( - ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[i].DcTableSelector]); + ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector]); if (value > 16) { throw new ImageFormatException("Excessive DC component"); } int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); + // errorCode.EnsureNoError(); + this.pointers.Dc[this.componentIndex] += deltaDC; // b[0] = dc[compIndex] << al; @@ -360,6 +364,7 @@ namespace ImageSharp.Formats.Jpg } int ac = decoder.Bits.ReceiveExtend(val1, decoder); + // errorCode.EnsureNoError(); // b[Unzig[zig]] = ac << al; Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al); diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/MissingFF00Exception.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/MissingFF00Exception.cs new file mode 100644 index 0000000000..f8c157237d --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/MissingFF00Exception.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + + /// + /// The missing ff00 exception. + /// + // ReSharper disable once InconsistentNaming + internal class MissingFF00Exception : Exception + { + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 707f9d3e4d..3bb774b69e 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -93,24 +93,6 @@ namespace ImageSharp.Formats this.Bytes = Bytes.Create(); } - /// - /// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent) - /// It's better tho have an error code for this! - /// - internal enum ErrorCodes - { - /// - /// NoError - /// - NoError, - - /// - /// MissingFF00 - /// - // ReSharper disable once InconsistentNaming - MissingFF00 - } - /// /// Gets the component array /// @@ -437,7 +419,8 @@ namespace ImageSharp.Formats [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte ReadByte() { - return this.Bytes.ReadByte(this.InputStream); + byte result = this.Bytes.ReadByte(this.InputStream); + return result; } /// @@ -448,11 +431,8 @@ namespace ImageSharp.Formats { if (this.Bits.UnreadBits == 0) { - ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } + DecoderErrorCode errorCode = this.Bits.EnsureNBits(1, this); + errorCode.EnsureNoError(); } bool ret = (this.Bits.Accumulator & this.Bits.Mask) != 0; @@ -467,7 +447,22 @@ namespace ImageSharp.Formats /// The data to write to. /// The offset in the source buffer /// The number of bytes to read + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadFull(byte[] data, int offset, int length) + { + DecoderErrorCode errorCode = this.ReadFullUnsafe(data, offset, length); + errorCode.EnsureNoError(); + } + + /// + /// Reads exactly length bytes into data. It does not care about byte stuffing. + /// Does not throw on errors, returns instead! + /// + /// The data to write to. + /// The offset in the source buffer + /// The number of bytes to read + /// The + public DecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length) { // Unread the overshot bytes, if any. if (this.Bytes.UnreadableBytes != 0) @@ -498,7 +493,9 @@ namespace ImageSharp.Formats this.Bytes.Fill(this.InputStream); } } - } + + return DecoderErrorCode.NoError; + } /// /// Decodes the given number of bits @@ -509,11 +506,8 @@ namespace ImageSharp.Formats { if (this.Bits.UnreadBits < count) { - ErrorCodes errorCode = this.Bits.EnsureNBits(count, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } + DecoderErrorCode errorCode = this.Bits.EnsureNBits(count, this); + errorCode.EnsureNoError(); } uint ret = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); @@ -538,9 +532,9 @@ namespace ImageSharp.Formats if (this.Bits.UnreadBits < 8) { - ErrorCodes errorCode = this.Bits.EnsureNBits(8, this); + DecoderErrorCode errorCode = this.Bits.EnsureNBits(8, this); - if (errorCode == ErrorCodes.NoError) + if (errorCode == DecoderErrorCode.NoError) { ushort v = huffmanTree.Lut[(this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSize)) & 0xFF]; @@ -552,6 +546,10 @@ namespace ImageSharp.Formats return (byte)(v >> 8); } } + else if (errorCode == DecoderErrorCode.UnexpectedEndOfFile) + { + errorCode.ThrowExceptionForErrorCode(); + } else { this.UnreadByteStuffedByte(); @@ -563,11 +561,8 @@ namespace ImageSharp.Formats { if (this.Bits.UnreadBits == 0) { - ErrorCodes errorCode = this.Bits.EnsureNBits(1, this); - if (errorCode != ErrorCodes.NoError) - { - throw new MissingFF00Exception(); - } + DecoderErrorCode errorCode = this.Bits.EnsureNBits(1, this); + errorCode.EnsureNoError(); } if ((this.Bits.Accumulator & this.Bits.Mask) != 0) @@ -1440,7 +1435,20 @@ namespace ImageSharp.Formats /// Skips the next n bytes. /// /// The number of bytes to ignore. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Skip(int count) + { + DecoderErrorCode errorCode = this.SkipUnsafe(count); + errorCode.EnsureNoError(); + } + + /// + /// Skips the next n bytes. + /// Does not throw, returns instead! + /// + /// The number of bytes to ignore. + /// The + private DecoderErrorCode SkipUnsafe(int count) { // Unread the overshot bytes, if any. if (this.Bytes.UnreadableBytes != 0) @@ -1470,6 +1478,8 @@ namespace ImageSharp.Formats this.Bytes.Fill(this.InputStream); } + + return DecoderErrorCode.NoError; } /// @@ -1490,21 +1500,5 @@ namespace ImageSharp.Formats this.Bits.Mask >>= 8; } } - - /// - /// The EOF (End of File exception). - /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker - /// - internal class EOFException : Exception - { - } - - /// - /// The missing ff00 exception. - /// - // ReSharper disable once InconsistentNaming - internal class MissingFF00Exception : Exception - { - } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs index 94ae9a2b16..628bc4ea95 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests provider.Utility.SaveTestOutputFile(image, "bmp"); } - // [Theory] // TODO: #18 + [Theory] // TODO: #18 [WithFile(TestImages.Jpeg.Progressive.Bad.BadEOF, PixelTypes.Color)] public void LoadProgressiveImage(TestImageProvider provider) where TColor : struct, IPackedPixel, IEquatable From 79067778a7de220b4fe60264f05b81f657a6d402 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 22 Jan 2017 14:02:11 +0100 Subject: [PATCH 02/26] unsafe version of EnsureNBits --- .../Components/Decoder/Bits.cs | 20 +++++++-- .../Components/Decoder/EOFException.cs | 3 ++ .../JpegScanDecoder.ComputationData.cs | 6 +-- .../Components/Decoder/JpegScanDecoder.cs | 44 ++++++++----------- .../Components/Decoder/JpegScanDecoder.md | 4 +- .../JpegDecoderCore.cs | 11 ++--- 6 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs index 8e46e18066..f80e75b990 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs @@ -37,9 +37,24 @@ namespace ImageSharp.Formats.Jpg /// /// The number of bits to ensure. /// Jpeg decoder + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EnsureNBits(int n, JpegDecoderCore decoder) + { + DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, decoder); + errorCode.EnsureNoError(); + } + + /// + /// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at + /// least n. For best performance (avoiding function calls inside hot loops), + /// the caller is the one responsible for first checking that bits.UnreadBits < n. + /// This method does not throw. Returns instead. + /// + /// The number of bits to ensure. + /// Jpeg decoder /// Error code [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal DecoderErrorCode EnsureNBits(int n, JpegDecoderCore decoder) + public DecoderErrorCode EnsureNBitsUnsafe(int n, JpegDecoderCore decoder) { while (true) { @@ -84,8 +99,7 @@ namespace ImageSharp.Formats.Jpg { if (this.UnreadBits < t) { - DecoderErrorCode errorCode = this.EnsureNBits(t, decoder); - errorCode.EnsureNoError(); + this.EnsureNBits(t, decoder); } this.UnreadBits -= t; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs index 81857b456c..b695e68123 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs @@ -13,6 +13,9 @@ namespace ImageSharp.Formats.Jpg /// internal class EOFException : Exception { + /// + /// Initializes a new instance of the class. + /// public EOFException() : base("Reached end of stream before proceeding EOI marker!") { diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs index ef04bf4188..06f170be5a 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Formats.Jpg internal unsafe partial struct JpegScanDecoder { /// - /// Holds the "large" data blocks needed for computations + /// Holds the "large" data blocks needed for computations. /// [StructLayout(LayoutKind.Sequential)] public struct ComputationData @@ -44,12 +44,12 @@ namespace ImageSharp.Formats.Jpg public UnzigData Unzig; /// - /// The no-idea-what's this data + /// The buffer storing the -s for each component /// public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents]; /// - /// The DC component values + /// The DC values for each component /// public fixed int Dc[JpegDecoderCore.MaxComponents]; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 8c2f079707..ef2ce18883 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -2,7 +2,6 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - // ReSharper disable InconsistentNaming namespace ImageSharp.Formats.Jpg { @@ -10,8 +9,21 @@ namespace ImageSharp.Formats.Jpg using System.Runtime.CompilerServices; /// - /// Encapsulates the impementation of Jpeg SOS decoder. - /// See JpegScanDecoder.md! + /// Encapsulates the impementation of Jpeg SOS decoder. See JpegScanDecoder.md! + /// and are the spectral selection bounds. + /// and are the successive approximation high and low values. + /// The spec calls these values Ss, Se, Ah and Al. + /// For progressive JPEGs, these are the two more-or-less independent + /// aspects of progression. Spectral selection progression is when not + /// all of a block's 64 DCT coefficients are transmitted in one pass. + /// For example, three passes could transmit coefficient 0 (the DC + /// component), coefficients 1-5, and coefficients 6-63, in zig-zag + /// order. Successive approximation is when not all of the bits of a + /// band of coefficients are transmitted in one pass. For example, + /// three passes could transmit the 6 most significant bits, followed + /// by the second-least significant bit, followed by the least + /// significant bit. + /// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. /// internal unsafe partial struct JpegScanDecoder { @@ -35,21 +47,6 @@ namespace ImageSharp.Formats.Jpg /// private int by; - // zigStart and zigEnd are the spectral selection bounds. - // ah and al are the successive approximation high and low values. - // The spec calls these values Ss, Se, Ah and Al. - // For progressive JPEGs, these are the two more-or-less independent - // aspects of progression. Spectral selection progression is when not - // all of a block's 64 DCT coefficients are transmitted in one pass. - // For example, three passes could transmit coefficient 0 (the DC - // component), coefficients 1-5, and coefficients 6-63, in zig-zag - // order. Successive approximation is when not all of the bits of a - // band of coefficients are transmitted in one pass. For example, - // three passes could transmit the 6 most significant bits, followed - // by the second-least significant bit, followed by the least - // significant bit. - // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. - /// /// Start index of the zig-zag selection bound /// @@ -91,14 +88,14 @@ namespace ImageSharp.Formats.Jpg private ushort eobRun; /// - /// The buffer + /// Pointers to elements of /// - private ComputationData data; + private DataPointers pointers; /// - /// Pointers to elements of + /// The buffer /// - private DataPointers pointers; + private ComputationData data; /// /// Initializes the default instance after creation. @@ -320,7 +317,6 @@ namespace ImageSharp.Formats.Jpg else { int zig = this.zigStart; - DecoderErrorCode errorCode; if (zig == 0) { zig++; @@ -335,7 +331,6 @@ namespace ImageSharp.Formats.Jpg } int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); - // errorCode.EnsureNoError(); this.pointers.Dc[this.componentIndex] += deltaDC; @@ -364,7 +359,6 @@ namespace ImageSharp.Formats.Jpg } int ac = decoder.Bits.ReceiveExtend(val1, decoder); - // errorCode.EnsureNoError(); // b[Unzig[zig]] = ac << al; Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al); diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md index 215f21807b..4ca4d1f642 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md @@ -4,7 +4,7 @@ The implementation is optimized to hold most of the necessary data in a single v #### Benefits: - Maximized locality of reference by keeping most of the operation data on the stack -- Reaching this without long parameter lists, most of the values describing the state of the decoder algorithm +- Achieving this without long parameter lists, most of the values describing the state of the decoder algorithm are members of the `JpegScanDecoder` struct - Most of the logic related to Scan decoding is refactored & simplified now to live in the methods of `JpegScanDecoder` - The first step is done towards separating the stream reading from block processing. They can be refactored later to be executed in two disctinct loops. @@ -16,8 +16,8 @@ are members of the `JpegScanDecoder` struct |JpegScanDecoder | |-------------------| |Variables | -|ComputationData | |DataPointers | +|ComputationData | - **ComputationData** holds the "large" data blocks needed for computations (Mostly `Block8x8F`-s) - **DataPointers** contains pointers to the memory regions of `ComponentData` so they can be easily passed around to pointer based utility methods of `Block8x8F` diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 3bb774b69e..aa5158395c 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -431,8 +431,7 @@ namespace ImageSharp.Formats { if (this.Bits.UnreadBits == 0) { - DecoderErrorCode errorCode = this.Bits.EnsureNBits(1, this); - errorCode.EnsureNoError(); + this.Bits.EnsureNBits(1, this); } bool ret = (this.Bits.Accumulator & this.Bits.Mask) != 0; @@ -506,8 +505,7 @@ namespace ImageSharp.Formats { if (this.Bits.UnreadBits < count) { - DecoderErrorCode errorCode = this.Bits.EnsureNBits(count, this); - errorCode.EnsureNoError(); + this.Bits.EnsureNBits(count, this); } uint ret = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); @@ -532,7 +530,7 @@ namespace ImageSharp.Formats if (this.Bits.UnreadBits < 8) { - DecoderErrorCode errorCode = this.Bits.EnsureNBits(8, this); + DecoderErrorCode errorCode = this.Bits.EnsureNBitsUnsafe(8, this); if (errorCode == DecoderErrorCode.NoError) { @@ -561,8 +559,7 @@ namespace ImageSharp.Formats { if (this.Bits.UnreadBits == 0) { - DecoderErrorCode errorCode = this.Bits.EnsureNBits(1, this); - errorCode.EnsureNoError(); + this.Bits.EnsureNBits(1, this); } if ((this.Bits.Accumulator & this.Bits.Mask) != 0) From f3471dd36fdccbada02eaf8fa5b57d74e128841b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 22 Jan 2017 14:57:39 +0100 Subject: [PATCH 03/26] pooling JpegDecoderCore.DecodedBlocks + made Sandbox46 executable --- .../Components/Block8x8F.cs | 1 + .../JpegDecoderCore.cs | 22 ++++++++++++++--- .../ImageSharp.Sandbox46.csproj | 9 ++++++- tests/ImageSharp.Sandbox46/Program.cs | 24 +++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 tests/ImageSharp.Sandbox46/Program.cs diff --git a/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs b/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs index 13475af09e..2b8c15ab3c 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats.Jpg { using System; + using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index aa5158395c..b9f1dc77b7 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Formats { using System; + using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -21,6 +22,11 @@ namespace ImageSharp.Formats /// public const int MaxComponents = 4; + /// + /// The maximum number of quantization tables + /// + public const int MaxTq = 3; + // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P #pragma warning disable SA1401 // FieldsMustBePrivate /// @@ -35,9 +41,10 @@ namespace ImageSharp.Formats #pragma warning restore SA401 /// - /// The maximum number of quantization tables + /// The used to pool data in . + /// Should always clean arrays when returning! /// - private const int MaxTq = 3; + private static readonly ArrayPool BlockPool = ArrayPool.Create(); /// /// The App14 marker color-space @@ -406,6 +413,15 @@ namespace ImageSharp.Formats this.HuffmanTrees[i].Dispose(); } + for (int i = 0; i < this.DecodedBlocks.Length; i++) + { + Block8x8F[] blockArray = this.DecodedBlocks[i]; + if (blockArray != null) + { + BlockPool.Return(blockArray, true); + } + } + this.ycbcrImage?.Dispose(); this.Bytes.Dispose(); this.grayImage.ReturnPooled(); @@ -1407,7 +1423,7 @@ namespace ImageSharp.Formats { int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = new Block8x8F[size]; + this.DecodedBlocks[i] = BlockPool.Rent(size); } } diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 5a27190dbf..7994456da4 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -6,7 +6,7 @@ Debug AnyCPU {96188137-5FA6-4924-AB6E-4EFF79C6E0BB} - Library + Exe Properties ImageSharp ImageSharp.Sandbox46 @@ -34,6 +34,9 @@ 4 true + + ImageSharp.Sandbox46.Program + @@ -199,6 +202,7 @@ Tests\TestUtilities\TestUtilityExtensions.cs + @@ -208,6 +212,9 @@ + + + diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs new file mode 100644 index 0000000000..db57cdb3d4 --- /dev/null +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -0,0 +1,24 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Sandbox46 +{ + using System; + + public class Program + { + /// + /// The main entry point. Useful for executing benchmarks and performance unit tests manually, + /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. + /// + /// + /// The arguments to pass to the program. + /// + public static void Main(string[] args) + { + Console.WriteLine("Hello."); + } + } +} From 658ac38a9356ab48117ec18f5cc09061860c67dc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 22 Jan 2017 20:25:41 +0100 Subject: [PATCH 04/26] new "...Unsafe" stream reading methods --- .../Components/Decoder/Bits.cs | 5 +- .../Components/Decoder/Bytes.cs | 95 +++++++++++++------ .../Components/Decoder/DecoderErrorCode.cs | 2 +- .../Components/Decoder/DecoderThrowHelper.cs | 17 +++- .../Components/Decoder/EOFException.cs | 1 + .../JpegDecoderCore.cs | 18 ++-- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 2 +- 7 files changed, 100 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs index f80e75b990..ab08b0d4cf 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs @@ -58,12 +58,11 @@ namespace ImageSharp.Formats.Jpg { while (true) { - DecoderErrorCode errorCode; - // Grab the decode bytes, use them and then set them // back on the decoder. Bytes decoderBytes = decoder.Bytes; - byte c = decoderBytes.ReadByteStuffedByte(decoder.InputStream, out errorCode); + byte c; + DecoderErrorCode errorCode = decoderBytes.ReadByteStuffedByteUnsafe(decoder.InputStream, out c); decoder.Bytes = decoderBytes; if (errorCode != DecoderErrorCode.NoError) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs index e33f852ff3..176e0cfbeb 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs @@ -67,14 +67,10 @@ namespace ImageSharp.Formats.Jpg /// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data. /// /// Input stream - /// Error code - /// The - internal byte ReadByteStuffedByte(Stream inputStream, out DecoderErrorCode errorCode) + /// The result + /// The + public DecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out byte x) { - byte x; - - errorCode = DecoderErrorCode.NoError; - // Take the fast path if bytes.buf contains at least two bytes. if (this.I + 2 <= this.J) { @@ -83,42 +79,46 @@ namespace ImageSharp.Formats.Jpg this.UnreadableBytes = 1; if (x != JpegConstants.Markers.XFF) { - return x; + return DecoderErrorCode.NoError; } if (this.Buffer[this.I] != 0x00) { - errorCode = DecoderErrorCode.MissingFF00; - return 0; - - // throw new MissingFF00Exception(); + return DecoderErrorCode.MissingFF00; } this.I++; this.UnreadableBytes = 2; - return JpegConstants.Markers.XFF; + x = JpegConstants.Markers.XFF; + return DecoderErrorCode.NoError; } this.UnreadableBytes = 0; - x = this.ReadByte(inputStream); + DecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out x); this.UnreadableBytes = 1; + if (errorCode != DecoderErrorCode.NoError) + { + return errorCode; + } if (x != JpegConstants.Markers.XFF) { - return x; + return DecoderErrorCode.NoError; } - x = this.ReadByte(inputStream); + errorCode = this.ReadByteUnsafe(inputStream, out x); this.UnreadableBytes = 2; + if (errorCode != DecoderErrorCode.NoError) + { + return errorCode; + } if (x != 0x00) { - errorCode = DecoderErrorCode.MissingFF00; - return 0; - - // throw new MissingFF00Exception(); + return DecoderErrorCode.MissingFF00; } - return JpegConstants.Markers.XFF; + x = JpegConstants.Markers.XFF; + return DecoderErrorCode.NoError; } /// @@ -127,30 +127,68 @@ namespace ImageSharp.Formats.Jpg /// Input stream /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal byte ReadByte(Stream inputStream) + public byte ReadByte(Stream inputStream) { + byte result; + DecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out result); + errorCode.EnsureNoError(); + return result; + } + + /// + /// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing. + /// This method does not throw on format error, it returns a instead. + /// + /// Input stream + /// The result as out parameter + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public DecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result) + { + DecoderErrorCode errorCode = DecoderErrorCode.NoError; while (this.I == this.J) { - this.Fill(inputStream); + errorCode = this.FillUnsafe(inputStream); + if (errorCode != DecoderErrorCode.NoError) + { + result = 0; + return errorCode; + } } - byte x = this.Buffer[this.I]; + result = this.Buffer[this.I]; this.I++; this.UnreadableBytes = 0; - return x; + return errorCode; + } + + /// + /// Fills up the bytes buffer from the underlying stream. + /// It should only be called when there are no unread bytes in bytes. + /// + /// Thrown when reached end of stream unexpectedly. + /// Input stream + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Fill(Stream inputStream) + { + DecoderErrorCode errorCode = this.FillUnsafe(inputStream); + errorCode.EnsureNoError(); } /// /// Fills up the bytes buffer from the underlying stream. /// It should only be called when there are no unread bytes in bytes. + /// This method does not throw , returns a instead! /// /// Input stream + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Fill(Stream inputStream) + public DecoderErrorCode FillUnsafe(Stream inputStream) { if (this.I != this.J) { - throw new ImageFormatException("Fill called when unread bytes exist."); + // Unrecoverable error in the input, throwing! + DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist(); } // Move the last 2 bytes to the start of the buffer, in case we need @@ -167,10 +205,11 @@ namespace ImageSharp.Formats.Jpg int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J); if (n == 0) { - throw new EOFException(); + return DecoderErrorCode.UnexpectedEndOfStream; } this.J += n; + return DecoderErrorCode.NoError; } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs index 4adc9fa7f0..8b82184faf 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs @@ -24,6 +24,6 @@ namespace ImageSharp.Formats /// /// End of stream reached unexpectedly /// - UnexpectedEndOfFile + UnexpectedEndOfStream } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs index 33321bff91..18ba02a9a0 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs @@ -26,7 +26,7 @@ namespace ImageSharp.Formats.Jpg throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode)); case DecoderErrorCode.MissingFF00: throw new MissingFF00Exception(); - case DecoderErrorCode.UnexpectedEndOfFile: + case DecoderErrorCode.UnexpectedEndOfStream: throw new EOFException(); default: throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null); @@ -45,5 +45,20 @@ namespace ImageSharp.Formats.Jpg ThrowExceptionForErrorCode(errorCode); } } + + /// + /// Encapsulates methods throwing different flavours of -s. + /// + public static class ThrowImageFormatException + { + /// + /// Throws "Fill called when unread bytes exist." + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static void FillCalledWhenUnreadBytesExist() + { + throw new ImageFormatException("Fill called when unread bytes exist."); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs index b695e68123..5ed25ef049 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs @@ -10,6 +10,7 @@ namespace ImageSharp.Formats.Jpg /// /// The EOF (End of File exception). /// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker + /// TODO: Rename to UnexpectedEndOfStreamException /// internal class EOFException : Exception { diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index b9f1dc77b7..5571ba7075 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -112,7 +112,7 @@ namespace ImageSharp.Formats /// /// Gets the saved state between progressive-mode scans. - /// TODO: Also store non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop) + /// TODO: Also save non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop) /// public Block8x8F[][] DecodedBlocks { get; } @@ -490,6 +490,7 @@ namespace ImageSharp.Formats this.Bytes.UnreadableBytes = 0; } + DecoderErrorCode errorCode = DecoderErrorCode.NoError; while (length > 0) { if (this.Bytes.J - this.Bytes.I >= length) @@ -505,12 +506,12 @@ namespace ImageSharp.Formats length -= this.Bytes.J - this.Bytes.I; this.Bytes.I += this.Bytes.J - this.Bytes.I; - this.Bytes.Fill(this.InputStream); + errorCode = this.Bytes.FillUnsafe(this.InputStream); } } - return DecoderErrorCode.NoError; - } + return errorCode; + } /// /// Decodes the given number of bits @@ -560,7 +561,7 @@ namespace ImageSharp.Formats return (byte)(v >> 8); } } - else if (errorCode == DecoderErrorCode.UnexpectedEndOfFile) + else if (errorCode == DecoderErrorCode.UnexpectedEndOfStream) { errorCode.ThrowExceptionForErrorCode(); } @@ -1419,6 +1420,7 @@ namespace ImageSharp.Formats this.MCUCountX = (this.ImageWidth + (8 * h0) - 1) / (8 * h0); this.MCUCountY = (this.ImageHeight + (8 * v0) - 1) / (8 * v0); + // As a preparation for parallelizing Scan decoder, we also allocate DecodedBlocks in the non-progressive case! for (int i = 0; i < this.ComponentCount; i++) { int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor @@ -1489,7 +1491,11 @@ namespace ImageSharp.Formats break; } - this.Bytes.Fill(this.InputStream); + DecoderErrorCode errorCode = this.Bytes.FillUnsafe(this.InputStream); + if (errorCode != DecoderErrorCode.NoError) + { + return errorCode; + } } return DecoderErrorCode.NoError; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index f2abce6e75..eb9747cce0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Tests { } - // [Theory] // Benchmark, enable manually + [Theory] // Benchmark, enable manually [InlineData(30, TestImages.Jpeg.Baseline.Cmyk)] [InlineData(30, TestImages.Jpeg.Baseline.Ycck)] [InlineData(30, TestImages.Jpeg.Baseline.Calliphora)] From 4096ee8d1246fba95c8c0bdc1727824686bf9115 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 22 Jan 2017 20:57:10 +0100 Subject: [PATCH 05/26] DecodeHuffmanUnsafe, DecodeBitUnsafe --- .../Components/Decoder/Bits.cs | 29 +++++++-- .../Components/Decoder/Bytes.cs | 2 + .../Components/Decoder/DecoderThrowHelper.cs | 26 +++++++- .../Components/Decoder/JpegScanDecoder.cs | 57 +++++++++++++---- .../JpegDecoderCore.cs | 62 ++++++++++++------- 5 files changed, 137 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs index ab08b0d4cf..36344b75a6 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs @@ -94,24 +94,45 @@ namespace ImageSharp.Formats.Jpg /// Byte /// Jpeg decoder /// Read bits value - internal int ReceiveExtend(byte t, JpegDecoderCore decoder) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReceiveExtend(byte t, JpegDecoderCore decoder) + { + int x; + DecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, decoder, out x); + errorCode.EnsureNoError(); + return x; + } + + /// + /// Receive extend + /// + /// Byte + /// Jpeg decoder + /// Read bits value + /// The + public DecoderErrorCode ReceiveExtendUnsafe(byte t, JpegDecoderCore decoder, out int x) { if (this.UnreadBits < t) { - this.EnsureNBits(t, decoder); + DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, decoder); + if (errorCode != DecoderErrorCode.NoError) + { + x = int.MaxValue; + return errorCode; + } } this.UnreadBits -= t; this.Mask >>= t; int s = 1 << t; - int x = (int)((this.Accumulator >> this.UnreadBits) & (s - 1)); + x = (int)((this.Accumulator >> this.UnreadBits) & (s - 1)); if (x < (s >> 1)) { x += ((-1) << t) + 1; } - return x; + return DecoderErrorCode.NoError; } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs index 176e0cfbeb..6ca017f4e8 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs @@ -101,6 +101,7 @@ namespace ImageSharp.Formats.Jpg { return errorCode; } + if (x != JpegConstants.Markers.XFF) { return DecoderErrorCode.NoError; @@ -112,6 +113,7 @@ namespace ImageSharp.Formats.Jpg { return errorCode; } + if (x != 0x00) { return DecoderErrorCode.MissingFF00; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs index 18ba02a9a0..84f7579c8a 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs @@ -46,18 +46,40 @@ namespace ImageSharp.Formats.Jpg } } + /// + /// Throws an exception if the given is . + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EnsureNoEOF(this DecoderErrorCode errorCode) + { + if (errorCode == DecoderErrorCode.UnexpectedEndOfStream) + { + errorCode.ThrowExceptionForErrorCode(); + } + } + /// /// Encapsulates methods throwing different flavours of -s. /// public static class ThrowImageFormatException { /// - /// Throws "Fill called when unread bytes exist." + /// Throws "Fill called when unread bytes exist". /// [MethodImpl(MethodImplOptions.NoInlining)] public static void FillCalledWhenUnreadBytesExist() { - throw new ImageFormatException("Fill called when unread bytes exist."); + throw new ImageFormatException("Fill called when unread bytes exist!"); + } + + /// + /// Throws "Bad Huffman code". + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static void BadHuffmanCode() + { + throw new ImageFormatException("Bad Huffman code!"); } } } diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index ef2ce18883..30cb6c851b 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -308,7 +308,7 @@ namespace ImageSharp.Formats.Jpg private void ProcessBlockImpl(JpegDecoderCore decoder, int scanIndex) { var b = this.pointers.Block; - + DecoderErrorCode errorCode; int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { @@ -322,9 +322,13 @@ namespace ImageSharp.Formats.Jpg zig++; // Decode the DC coefficient, as specified in section F.2.2.1. - byte value = - decoder.DecodeHuffman( - ref decoder.HuffmanTrees[(DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector]); + byte value; + int huffmanIndex = (DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; + errorCode = decoder.DecodeHuffmanUnsafe( + ref decoder.HuffmanTrees[huffmanIndex], + out value); + errorCode.EnsureNoEOF(); + if (value > 16) { throw new ImageFormatException("Excessive DC component"); @@ -347,7 +351,10 @@ namespace ImageSharp.Formats.Jpg // Decode the AC coefficients, as specified in section F.2.2.2. for (; zig <= this.zigEnd; zig++) { - byte value = decoder.DecodeHuffman(ref decoder.HuffmanTrees[huffmannIdx]); + byte value; + errorCode = decoder.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value); + errorCode.EnsureNoEOF(); + byte val0 = (byte)(value >> 4); byte val1 = (byte)(value & 0x0f); if (val1 != 0) @@ -370,7 +377,8 @@ namespace ImageSharp.Formats.Jpg this.eobRun = (ushort)(1 << val0); if (val0 != 0) { - this.eobRun |= (ushort)decoder.DecodeBits(val0); + errorCode = this.DecodeEobRun(val0, decoder); + errorCode.EnsureNoError(); } this.eobRun--; @@ -409,6 +417,19 @@ namespace ImageSharp.Formats.Jpg destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); } + private DecoderErrorCode DecodeEobRun(int count, JpegDecoderCore decoder) + { + uint bitsResult; + DecoderErrorCode errorCode = decoder.DecodeBitsUnsafe(count, out bitsResult); + if (errorCode != DecoderErrorCode.NoError) + { + return errorCode; + } + + this.eobRun |= (ushort)bitsResult; + return DecoderErrorCode.NoError; + } + /// /// Gets the block index used to retieve blocks from in /// @@ -496,7 +517,9 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("Invalid state for zig DC component"); } - bool bit = decoder.DecodeBit(); + bool bit; + DecoderErrorCode errorCode = decoder.DecodeBitUnsafe(out bit); + errorCode.EnsureNoError(); if (bit) { int stuff = (int)Block8x8F.GetScalarAt(b, 0); @@ -519,7 +542,11 @@ namespace ImageSharp.Formats.Jpg { bool done = false; int z = 0; - byte val = decoder.DecodeHuffman(ref h); + + byte val; + DecoderErrorCode errorCode = decoder.DecodeHuffmanUnsafe(ref h, out val); + errorCode.EnsureNoEOF(); + int val0 = val >> 4; int val1 = val & 0x0f; @@ -531,7 +558,8 @@ namespace ImageSharp.Formats.Jpg this.eobRun = (ushort)(1 << val0); if (val0 != 0) { - this.eobRun |= (ushort)decoder.DecodeBits(val0); + errorCode = this.DecodeEobRun(val0, decoder); + errorCode.EnsureNoError(); } done = true; @@ -540,7 +568,11 @@ namespace ImageSharp.Formats.Jpg break; case 1: z = delta; - bool bit = decoder.DecodeBit(); + + bool bit; + errorCode = decoder.DecodeBitUnsafe(out bit); + errorCode.EnsureNoError(); + if (!bit) { z = -z; @@ -606,7 +638,10 @@ namespace ImageSharp.Formats.Jpg continue; } - bool bit = decoder.DecodeBit(); + bool bit; + DecoderErrorCode errorCode = decoder.DecodeBitUnsafe(out bit); + errorCode.EnsureNoError(); + if (!bit) { continue; diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 5571ba7075..1e1210387a 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -435,25 +435,31 @@ namespace ImageSharp.Formats [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte ReadByte() { - byte result = this.Bytes.ReadByte(this.InputStream); - return result; + return this.Bytes.ReadByte(this.InputStream); } /// /// Decodes a single bit + /// TODO: This method (and also the usages) could be optimized by batching! /// - /// The - public bool DecodeBit() + /// The decoded bit as a + /// The + public DecoderErrorCode DecodeBitUnsafe(out bool result) { if (this.Bits.UnreadBits == 0) { - this.Bits.EnsureNBits(1, this); + DecoderErrorCode errorCode = this.Bits.EnsureNBitsUnsafe(1, this); + if (errorCode != DecoderErrorCode.NoError) + { + result = false; + return errorCode; + } } - bool ret = (this.Bits.Accumulator & this.Bits.Mask) != 0; + result = (this.Bits.Accumulator & this.Bits.Mask) != 0; this.Bits.UnreadBits--; this.Bits.Mask >>= 1; - return ret; + return DecoderErrorCode.NoError; } /// @@ -517,28 +523,32 @@ namespace ImageSharp.Formats /// Decodes the given number of bits /// /// The number of bits to decode. - /// The - public uint DecodeBits(int count) + /// The result + /// The + public DecoderErrorCode DecodeBitsUnsafe(int count, out uint result) { if (this.Bits.UnreadBits < count) { this.Bits.EnsureNBits(count, this); } - uint ret = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); - ret = (uint)(ret & ((1 << count) - 1)); + result = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); + result = (uint)(result & ((1 << count) - 1)); this.Bits.UnreadBits -= count; this.Bits.Mask >>= count; - return ret; + return DecoderErrorCode.NoError; } /// - /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value. + /// Extracts the next Huffman-coded value from the bit-stream into result, decoded according to the given value. /// /// The huffman value - /// The - public byte DecodeHuffman(ref HuffmanTree huffmanTree) + /// The decoded + /// The + public DecoderErrorCode DecodeHuffmanUnsafe(ref HuffmanTree huffmanTree, out byte result) { + result = 0; + // Copy stuff to the stack: if (huffmanTree.Length == 0) { @@ -558,16 +568,19 @@ namespace ImageSharp.Formats int n = (v & 0xFF) - 1; this.Bits.UnreadBits -= n; this.Bits.Mask >>= n; - return (byte)(v >> 8); + result = (byte)(v >> 8); + return errorCode; } } - else if (errorCode == DecoderErrorCode.UnexpectedEndOfStream) - { - errorCode.ThrowExceptionForErrorCode(); - } + + // else if (errorCode == DecoderErrorCode.UnexpectedEndOfStream) + // { + // errorCode.ThrowExceptionForErrorCode(); + // } else { this.UnreadByteStuffedByte(); + return errorCode; } } @@ -589,13 +602,18 @@ namespace ImageSharp.Formats if (code <= huffmanTree.MaxCodes[i]) { - return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]]; + result = huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]]; + return DecoderErrorCode.NoError; } code <<= 1; } - throw new ImageFormatException("Bad Huffman code"); + // Unrecoverable error, throwing: + DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode(); + + // DUMMY RETURN! C# doesn't know we have thrown an exception! + return DecoderErrorCode.NoError; } /// From 496914cdea197ef898c8de5d6a95695267f727c4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 22 Jan 2017 23:31:25 +0100 Subject: [PATCH 06/26] JpegScanDecoder: ReadBlock() <--> ProcessBlock() split --- .../Components/Decoder/JpegScanDecoder.cs | 104 ++++++++---------- 1 file changed, 46 insertions(+), 58 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 30cb6c851b..70c4402e86 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -112,6 +112,28 @@ namespace ImageSharp.Formats.Jpg /// /// Reads the blocks from the -s stream, and processes them into the corresponding instances. + /// The blocks are traversed one MCU at a time. For 4:2:0 chroma + /// subsampling, there are four Y 8x8 blocks in every 16x16 MCU. + /// For a baseline 32x16 pixel image, the Y blocks visiting order is: + /// 0 1 4 5 + /// 2 3 6 7 + /// For progressive images, the interleaved scans (those with component count > 1) + /// are traversed as above, but non-interleaved scans are traversed left + /// to right, top to bottom: + /// 0 1 2 3 + /// 4 5 6 7 + /// Only DC scans (zigStart == 0) can be interleave AC scans must have + /// only one component. + /// To further complicate matters, for non-interleaved scans, there is no + /// data for any blocks that are inside the image at the MCU level but + /// outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 + /// progressive image consists of two 16x16 MCUs. The interleaved scans + /// will process 8 Y blocks: + /// 0 1 4 5 + /// 2 3 6 7 + /// The non-interleaved scans will process only 6 Y blocks: + /// 0 1 2 + /// 3 4 5 /// /// The instance public void ProcessBlocks(JpegDecoderCore decoder) @@ -132,28 +154,6 @@ namespace ImageSharp.Formats.Jpg for (int j = 0; j < this.hi * vi; j++) { - // The blocks are traversed one MCU at a time. For 4:2:0 chroma - // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. - // For a baseline 32x16 pixel image, the Y blocks visiting order is: - // 0 1 4 5 - // 2 3 6 7 - // For progressive images, the interleaved scans (those with component count > 1) - // are traversed as above, but non-interleaved scans are traversed left - // to right, top to bottom: - // 0 1 2 3 - // 4 5 6 7 - // Only DC scans (zigStart == 0) can be interleave AC scans must have - // only one component. - // To further complicate matters, for non-interleaved scans, there is no - // data for any blocks that are inside the image at the MCU level but - // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 - // progressive image consists of two 16x16 MCUs. The interleaved scans - // will process 8 Y blocks: - // 0 1 4 5 - // 2 3 6 7 - // The non-interleaved scans will process only 6 Y blocks: - // 0 1 2 - // 3 4 5 if (this.componentScanCount != 1) { this.bx = (this.hi * mx) + (j % this.hi); @@ -171,23 +171,8 @@ namespace ImageSharp.Formats.Jpg } } - int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; - - // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async. - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - // Load the previous partially decoded coefficients, if applicable. - if (decoder.IsProgressive) - { - int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex]; - } - else - { - this.data.Block.Clear(); - } - - this.ProcessBlockImpl(decoder, scanIndex); + this.ReadBlock(decoder, scanIndex); + this.ProcessBlock(decoder); } // for j @@ -301,12 +286,15 @@ namespace ImageSharp.Formats.Jpg } /// - /// Process the current block at (, ) + /// Read the current the current block at (, ) from the decoders stream /// /// The decoder /// The index of the scan - private void ProcessBlockImpl(JpegDecoderCore decoder, int scanIndex) + private void ReadBlock(JpegDecoderCore decoder, int scanIndex) { + int blockIndex = this.GetBlockIndex(decoder); + this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex]; + var b = this.pointers.Block; DecoderErrorCode errorCode; int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; @@ -391,23 +379,23 @@ namespace ImageSharp.Formats.Jpg } } - if (decoder.IsProgressive) - { - if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0) - { - // We haven't completely decoded this 8x8 block. Save the coefficients. - decoder.DecodedBlocks[this.componentIndex][this.GetBlockIndex(decoder)] = *b; - - // At this point, we could execute the rest of the loop body to dequantize and - // perform the inverse DCT, to save early stages of a progressive image to the - // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, - // the jpeg.Decode function does not return until the entire image is decoded, - // so we "continue" here to avoid wasted computation. - return; - } - } + decoder.DecodedBlocks[this.componentIndex][blockIndex] = this.data.Block; + } + + private bool IsProgressiveBlockFinished(JpegDecoderCore decoder) + => decoder.IsProgressive && (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0); + + /// + /// Dequantize, perform the inverse DCT and store the block to the into the corresponding instances. + /// + /// The instance + private void ProcessBlock(JpegDecoderCore decoder) + { + int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + Block8x8F* b = this.pointers.Block; - // Dequantize, perform the inverse DCT and store the block to the image. Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); @@ -437,7 +425,7 @@ namespace ImageSharp.Formats.Jpg /// The index private int GetBlockIndex(JpegDecoderCore decoder) { - return ((this.@by * decoder.MCUCountX) * this.hi) + this.bx; + return ((this.by * decoder.MCUCountX) * this.hi) + this.bx; } private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref ComponentScan currentComponentScan, ref int totalHv) From 8df425c63012af99f9b4e453f5935a2fe1dede9f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 23 Jan 2017 00:33:07 +0100 Subject: [PATCH 07/26] introduced DecodedBlockMemento --- .../Components/Decoder/DecodedBlockMemento.cs | 31 +++++++++++ .../Components/Decoder/JpegScanDecoder.cs | 52 ++++++++++--------- .../JpegDecoderCore.cs | 19 +++---- .../Formats/Jpg/JpegDecoderTests.cs | 25 +++++++++ 4 files changed, 91 insertions(+), 36 deletions(-) create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs new file mode 100644 index 0000000000..4c3c7689f8 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System.Buffers; + + internal struct DecodedBlockMemento + { + /// + /// The used to pool data in . + /// Should always clean arrays when returning! + /// + public static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + public int Bx; + + public int By; + + public Block8x8F Block; + + public static void Store(DecodedBlockMemento[] blockArray, int index, int bx, int by, ref Block8x8F block) + { + blockArray[index].Bx = bx; + blockArray[index].By = by; + blockArray[index].Block = block; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 70c4402e86..a1fe6444b5 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -136,7 +136,7 @@ namespace ImageSharp.Formats.Jpg /// 3 4 5 /// /// The instance - public void ProcessBlocks(JpegDecoderCore decoder) + public void ReadBlocks(JpegDecoderCore decoder) { int blockCount = 0; int mcu = 0; @@ -213,6 +213,26 @@ namespace ImageSharp.Formats.Jpg } } + /// + /// Dequantize, perform the inverse DCT and store the block to the into the corresponding instances. + /// + /// The instance + public void ProcessBlock(JpegDecoderCore decoder) + { + int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; + this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + + Block8x8F* b = this.pointers.Block; + + Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + + DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); + + var destChannel = decoder.GetDestinationChannel(this.componentIndex); + var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); + destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); + } + private void ResetDc() { Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents); @@ -248,7 +268,7 @@ namespace ImageSharp.Formats.Jpg for (int i = 0; i < this.componentScanCount; i++) { - this.ProcessScanImpl(decoder, i, ref this.pointers.ComponentScan[i], ref totalHv); + this.InitComponentScan(decoder, i, ref this.pointers.ComponentScan[i], ref totalHv); } // Section B.2.3 states that if there is more than one component then the @@ -293,7 +313,7 @@ namespace ImageSharp.Formats.Jpg private void ReadBlock(JpegDecoderCore decoder, int scanIndex) { int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex]; + this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex].Block; var b = this.pointers.Block; DecoderErrorCode errorCode; @@ -379,31 +399,13 @@ namespace ImageSharp.Formats.Jpg } } - decoder.DecodedBlocks[this.componentIndex][blockIndex] = this.data.Block; + DecodedBlockMemento[] blocks = decoder.DecodedBlocks[this.componentIndex]; + DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref *b); } private bool IsProgressiveBlockFinished(JpegDecoderCore decoder) => decoder.IsProgressive && (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0); - /// - /// Dequantize, perform the inverse DCT and store the block to the into the corresponding instances. - /// - /// The instance - private void ProcessBlock(JpegDecoderCore decoder) - { - int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; - - Block8x8F* b = this.pointers.Block; - - Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig); - - DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); - - var destChannel = decoder.GetDestinationChannel(this.componentIndex); - var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); - destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); - } private DecoderErrorCode DecodeEobRun(int count, JpegDecoderCore decoder) { @@ -428,7 +430,7 @@ namespace ImageSharp.Formats.Jpg return ((this.by * decoder.MCUCountX) * this.hi) + this.bx; } - private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref ComponentScan currentComponentScan, ref int totalHv) + private void InitComponentScan(JpegDecoderCore decoder, int i, ref ComponentScan currentComponentScan, ref int totalHv) { // Component selector. int cs = decoder.Temp[1 + (2 * i)]; @@ -649,5 +651,7 @@ namespace ImageSharp.Formats.Jpg return zig; } + + } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 1e1210387a..30367e93ef 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -40,11 +40,7 @@ namespace ImageSharp.Formats public Bytes Bytes; #pragma warning restore SA401 - /// - /// The used to pool data in . - /// Should always clean arrays when returning! - /// - private static readonly ArrayPool BlockPool = ArrayPool.Create(); + /// /// The App14 marker color-space @@ -95,7 +91,7 @@ namespace ImageSharp.Formats this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.ComponentArray = new Component[MaxComponents]; - this.DecodedBlocks = new Block8x8F[MaxComponents][]; + this.DecodedBlocks = new DecodedBlockMemento[MaxComponents][]; this.Bits = default(Bits); this.Bytes = Bytes.Create(); } @@ -114,7 +110,7 @@ namespace ImageSharp.Formats /// Gets the saved state between progressive-mode scans. /// TODO: Also save non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop) /// - public Block8x8F[][] DecodedBlocks { get; } + public DecodedBlockMemento[][] DecodedBlocks { get; } /// /// Gets the quantization tables, in zigzag order. @@ -413,12 +409,11 @@ namespace ImageSharp.Formats this.HuffmanTrees[i].Dispose(); } - for (int i = 0; i < this.DecodedBlocks.Length; i++) + foreach (DecodedBlockMemento[] blockArray in this.DecodedBlocks) { - Block8x8F[] blockArray = this.DecodedBlocks[i]; if (blockArray != null) { - BlockPool.Return(blockArray, true); + DecodedBlockMemento.ArrayPool.Return(blockArray, true); } } @@ -1443,7 +1438,7 @@ namespace ImageSharp.Formats { int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = BlockPool.Rent(size); + this.DecodedBlocks[i] = DecodedBlockMemento.ArrayPool.Rent(size); } } @@ -1461,7 +1456,7 @@ namespace ImageSharp.Formats JpegScanDecoder.Init(&scan, this, remaining); this.Bits = default(Bits); this.MakeImage(); - scan.ProcessBlocks(this); + scan.ReadBlocks(this); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index b02f99be1a..37aff83383 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -41,5 +41,30 @@ namespace ImageSharp.Tests provider.Utility.SaveTestOutputFile(image, "bmp"); } + unsafe struct Buzisag + { + public int Value; + + public delegate void BlockAction(Buzisag* b); + + public static void Foo(Buzisag* buzisag) + { + Bar(buzisag, b => b->Value++); + } + + public static void Bar(Buzisag* buzisag, BlockAction action) + { + action(buzisag); + } + } + + [Fact] + public unsafe void Kabbe() + { + Buzisag b = default(Buzisag); + Buzisag.Foo(&b); + + Assert.Equal(1, b.Value); + } } } \ No newline at end of file From 4ebc23b7e27e47255909c299eb26d563b5a46df8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 23 Jan 2017 01:00:07 +0100 Subject: [PATCH 08/26] loading and processing blocks successfully separated!! --- .../Components/Decoder/JpegScanDecoder.cs | 50 +++--- .../JpegDecoderCore.cs | 146 +++++++++++------- 2 files changed, 120 insertions(+), 76 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index a1fe6444b5..ab376ee3cb 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -37,6 +37,11 @@ namespace ImageSharp.Formats.Jpg /// private const int DcTableIndex = 0; + /// + /// The current component index + /// + public int ComponentIndex; + /// /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) /// @@ -72,11 +77,6 @@ namespace ImageSharp.Formats.Jpg /// private int componentScanCount; - /// - /// The current component index - /// - private int componentIndex; - /// /// Horizontal sampling factor at the current component index /// @@ -103,11 +103,23 @@ namespace ImageSharp.Formats.Jpg /// Pointer to on the stack /// The instance /// The remaining bytes in the segment block. - public static void Init(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) + public static void InitStreamReading(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining) + { + Init(p); + p->InitStreamReadingImpl(decoder, remaining); + } + + public static void Init(JpegScanDecoder* p) { p->data = ComputationData.Create(); p->pointers = new DataPointers(&p->data); - p->InitImpl(decoder, remaining); + } + + public void LoadMemento(ref DecodedBlockMemento memento) + { + this.bx = memento.Bx; + this.by = memento.By; + this.data.Block = memento.Block; } /// @@ -148,9 +160,9 @@ namespace ImageSharp.Formats.Jpg { for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) { - this.componentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; - this.hi = decoder.ComponentArray[this.componentIndex].HorizontalFactor; - int vi = decoder.ComponentArray[this.componentIndex].VerticalFactor; + this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; + this.hi = decoder.ComponentArray[this.ComponentIndex].HorizontalFactor; + int vi = decoder.ComponentArray[this.ComponentIndex].VerticalFactor; for (int j = 0; j < this.hi * vi; j++) { @@ -172,7 +184,7 @@ namespace ImageSharp.Formats.Jpg } this.ReadBlock(decoder, scanIndex); - this.ProcessBlock(decoder); + //this.ProcessBlock(decoder); } // for j @@ -219,7 +231,7 @@ namespace ImageSharp.Formats.Jpg /// The instance public void ProcessBlock(JpegDecoderCore decoder) { - int qtIndex = decoder.ComponentArray[this.componentIndex].Selector; + int qtIndex = decoder.ComponentArray[this.ComponentIndex].Selector; this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; Block8x8F* b = this.pointers.Block; @@ -228,7 +240,7 @@ namespace ImageSharp.Formats.Jpg DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); - var destChannel = decoder.GetDestinationChannel(this.componentIndex); + var destChannel = decoder.GetDestinationChannel(this.ComponentIndex); var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by); destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); } @@ -239,11 +251,11 @@ namespace ImageSharp.Formats.Jpg } /// - /// The implementation part of as an instance method. + /// The implementation part of as an instance method. /// /// The /// The remaining bytes - private void InitImpl(JpegDecoderCore decoder, int remaining) + private void InitStreamReadingImpl(JpegDecoderCore decoder, int remaining) { if (decoder.ComponentCount == 0) { @@ -313,7 +325,7 @@ namespace ImageSharp.Formats.Jpg private void ReadBlock(JpegDecoderCore decoder, int scanIndex) { int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex].Block; + this.data.Block = decoder.DecodedBlocks[this.ComponentIndex][blockIndex].Block; var b = this.pointers.Block; DecoderErrorCode errorCode; @@ -344,10 +356,10 @@ namespace ImageSharp.Formats.Jpg int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); - this.pointers.Dc[this.componentIndex] += deltaDC; + this.pointers.Dc[this.ComponentIndex] += deltaDC; // b[0] = dc[compIndex] << al; - Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[this.componentIndex] << this.al); + Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[this.ComponentIndex] << this.al); } if (zig <= this.zigEnd && this.eobRun > 0) @@ -399,7 +411,7 @@ namespace ImageSharp.Formats.Jpg } } - DecodedBlockMemento[] blocks = decoder.DecodedBlocks[this.componentIndex]; + DecodedBlockMemento[] blocks = decoder.DecodedBlocks[this.ComponentIndex]; DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref *b); } diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 30367e93ef..f8adedd267 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -175,8 +175,89 @@ namespace ImageSharp.Formats /// The pixel format. /// The image, where the data should be set to. /// The stream, where the image should be. - /// Whether to decode metadata only. - public void Decode(Image image, Stream stream, bool configOnly) + /// Whether to decode metadata only. + public void Decode(Image image, Stream stream, bool metadataOnly) + where TColor : struct, IPackedPixel, IEquatable + { + this.ProcessStream(image, stream, metadataOnly); + if (metadataOnly) return; + this.ConvertBlocksToImagePixels(image); + } + + private void ConvertBlocksToImagePixels(Image image) + where TColor : struct, IPackedPixel, IEquatable + { + this.ProcessBlocks(); + + if (this.grayImage.IsInitialized) + { + this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image); + } + else if (this.ycbcrImage != null) + { + if (this.ComponentCount == 4) + { + if (!this.adobeTransformValid) + { + throw new ImageFormatException( + "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); + } + + // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html + // TODO: YCbCrA? + if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck) + { + this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image); + } + else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) + { + // Assume CMYK + this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image); + } + + return; + } + + if (this.ComponentCount == 3) + { + if (this.IsRGB()) + { + this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image); + return; + } + + this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image); + return; + } + + throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); + } + else + { + throw new ImageFormatException("Missing SOS marker."); + } + } + + private void ProcessBlocks() + where TColor : struct, IPackedPixel, IEquatable + { + JpegScanDecoder scanDecoder = default(JpegScanDecoder); + JpegScanDecoder.Init(&scanDecoder); + + for(int componentIndex = 0; componentIndex < this.ComponentCount; componentIndex++) + { + scanDecoder.ComponentIndex = componentIndex; + DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex]; + for (int i = 0; i < blockArray.Length; i++) + { + scanDecoder.LoadMemento(ref blockArray[i]); + scanDecoder.ProcessBlock(this); + } + } + } + + private void ProcessStream(Image image, Stream stream, bool metadataOnly) where TColor : struct, IPackedPixel, IEquatable { this.InputStream = stream; @@ -265,14 +346,14 @@ namespace ImageSharp.Formats case JpegConstants.Markers.SOF2: this.IsProgressive = marker == JpegConstants.Markers.SOF2; this.ProcessStartOfFrameMarker(remaining); - if (configOnly && this.isJfif) + if (metadataOnly && this.isJfif) { return; } break; case JpegConstants.Markers.DHT: - if (configOnly) + if (metadataOnly) { this.Skip(remaining); } @@ -283,7 +364,7 @@ namespace ImageSharp.Formats break; case JpegConstants.Markers.DQT: - if (configOnly) + if (metadataOnly) { this.Skip(remaining); } @@ -294,7 +375,7 @@ namespace ImageSharp.Formats break; case JpegConstants.Markers.SOS: - if (configOnly) + if (metadataOnly) { return; } @@ -310,7 +391,7 @@ namespace ImageSharp.Formats break; case JpegConstants.Markers.DRI: - if (configOnly) + if (metadataOnly) { this.Skip(remaining); } @@ -348,55 +429,6 @@ namespace ImageSharp.Formats break; } } - - if (this.grayImage.IsInitialized) - { - this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image); - } - else if (this.ycbcrImage != null) - { - if (this.ComponentCount == 4) - { - if (!this.adobeTransformValid) - { - throw new ImageFormatException( - "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); - } - - // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html - // TODO: YCbCrA? - if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck) - { - this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image); - } - else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) - { - // Assume CMYK - this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image); - } - - return; - } - - if (this.ComponentCount == 3) - { - if (this.IsRGB()) - { - this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image); - return; - } - - this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image); - return; - } - - throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); - } - else - { - throw new ImageFormatException("Missing SOS marker."); - } } /// @@ -1453,7 +1485,7 @@ namespace ImageSharp.Formats private void ProcessStartOfScan(int remaining) { JpegScanDecoder scan = default(JpegScanDecoder); - JpegScanDecoder.Init(&scan, this, remaining); + JpegScanDecoder.InitStreamReading(&scan, this, remaining); this.Bits = default(Bits); this.MakeImage(); scan.ReadBlocks(this); From a8657d9bd1a284cd1733a75bb1fc6241a726cbcd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 23 Jan 2017 01:29:32 +0100 Subject: [PATCH 09/26] cleanup --- .../Components/Decoder/DecodedBlockMemento.cs | 51 +- .../Components/Decoder/JpegScanDecoder.cs | 21 +- .../JpegDecoderCore.cs | 517 +++++++++--------- 3 files changed, 325 insertions(+), 264 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs index 4c3c7689f8..49d9b591f9 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs @@ -7,22 +7,67 @@ namespace ImageSharp.Formats.Jpg { using System.Buffers; + /// + /// A structure to store unprocessed instances and their coordinates while scanning the image. + /// internal struct DecodedBlockMemento { /// - /// The used to pool data in . - /// Should always clean arrays when returning! + /// A value indicating whether the instance is initialized. /// - public static readonly ArrayPool ArrayPool = ArrayPool.Create(); + public bool Initialized; + /// + /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// public int Bx; + /// + /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0)) + /// public int By; + /// + /// The + /// public Block8x8F Block; + /// + /// The used to pool data in . + /// Should always clean arrays when returning! + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + /// + /// Rent an array of -s from the pool. + /// + /// The requested array size + /// An array of -s + public static DecodedBlockMemento[] RentArray(int size) + { + return ArrayPool.Rent(size); + } + + /// + /// Returns the array to the pool. + /// + /// The array + public static void ReturnArray(DecodedBlockMemento[] blockArray) + { + ArrayPool.Return(blockArray, true); + } + + /// + /// Store the block data into a at the given index. + /// + /// The array of + /// The index in the array + /// X coordinate of the block + /// Y coordinate of the block + /// The public static void Store(DecodedBlockMemento[] blockArray, int index, int bx, int by, ref Block8x8F block) { + blockArray[index].Initialized = true; blockArray[index].Bx = bx; blockArray[index].By = by; blockArray[index].Block = block; diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index ab376ee3cb..4dcf6def8f 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -30,12 +30,12 @@ namespace ImageSharp.Formats.Jpg /// /// The AC table index /// - private const int AcTableIndex = 1; + public const int AcTableIndex = 1; /// /// The DC table index /// - private const int DcTableIndex = 0; + public const int DcTableIndex = 0; /// /// The current component index @@ -98,7 +98,7 @@ namespace ImageSharp.Formats.Jpg private ComputationData data; /// - /// Initializes the default instance after creation. + /// Initializes a default-constructed instance for reading data from -s stream. /// /// Pointer to on the stack /// The instance @@ -109,12 +109,20 @@ namespace ImageSharp.Formats.Jpg p->InitStreamReadingImpl(decoder, remaining); } + /// + /// Initializes a default-constructed instance, filling the data and setting the pointers. + /// + /// Pointer to on the stack public static void Init(JpegScanDecoder* p) { p->data = ComputationData.Create(); p->pointers = new DataPointers(&p->data); } + /// + /// Loads the data from the given into the block. + /// + /// The public void LoadMemento(ref DecodedBlockMemento memento) { this.bx = memento.Bx; @@ -184,7 +192,6 @@ namespace ImageSharp.Formats.Jpg } this.ReadBlock(decoder, scanIndex); - //this.ProcessBlock(decoder); } // for j @@ -415,10 +422,6 @@ namespace ImageSharp.Formats.Jpg DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref *b); } - private bool IsProgressiveBlockFinished(JpegDecoderCore decoder) - => decoder.IsProgressive && (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0); - - private DecoderErrorCode DecodeEobRun(int count, JpegDecoderCore decoder) { uint bitsResult; @@ -663,7 +666,5 @@ namespace ImageSharp.Formats.Jpg return zig; } - - } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index f8adedd267..2184e9e1f0 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -40,8 +40,6 @@ namespace ImageSharp.Formats public Bytes Bytes; #pragma warning restore SA401 - - /// /// The App14 marker color-space /// @@ -180,254 +178,10 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel, IEquatable { this.ProcessStream(image, stream, metadataOnly); - if (metadataOnly) return; - this.ConvertBlocksToImagePixels(image); - } - - private void ConvertBlocksToImagePixels(Image image) - where TColor : struct, IPackedPixel, IEquatable - { - this.ProcessBlocks(); - - if (this.grayImage.IsInitialized) - { - this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image); - } - else if (this.ycbcrImage != null) - { - if (this.ComponentCount == 4) - { - if (!this.adobeTransformValid) - { - throw new ImageFormatException( - "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); - } - - // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe - // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html - // TODO: YCbCrA? - if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck) - { - this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image); - } - else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) - { - // Assume CMYK - this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image); - } - - return; - } - - if (this.ComponentCount == 3) - { - if (this.IsRGB()) - { - this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image); - return; - } - - this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image); - return; - } - - throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); - } - else - { - throw new ImageFormatException("Missing SOS marker."); - } - } - - private void ProcessBlocks() - where TColor : struct, IPackedPixel, IEquatable - { - JpegScanDecoder scanDecoder = default(JpegScanDecoder); - JpegScanDecoder.Init(&scanDecoder); - - for(int componentIndex = 0; componentIndex < this.ComponentCount; componentIndex++) + if (!metadataOnly) { - scanDecoder.ComponentIndex = componentIndex; - DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex]; - for (int i = 0; i < blockArray.Length; i++) - { - scanDecoder.LoadMemento(ref blockArray[i]); - scanDecoder.ProcessBlock(this); - } - } - } - - private void ProcessStream(Image image, Stream stream, bool metadataOnly) - where TColor : struct, IPackedPixel, IEquatable - { - this.InputStream = stream; - - // Check for the Start Of Image marker. - this.ReadFull(this.Temp, 0, 2); - if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI) - { - throw new ImageFormatException("Missing SOI marker."); - } - - // Process the remaining segments until the End Of Image marker. - bool processBytes = true; - - // we can't currently short circute progressive images so don't try. - while (processBytes) - { - this.ReadFull(this.Temp, 0, 2); - while (this.Temp[0] != 0xff) - { - // Strictly speaking, this is a format error. However, libjpeg is - // liberal in what it accepts. As of version 9, next_marker in - // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and - // continues to decode the stream. Even before next_marker sees - // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many - // bytes as it can, possibly past the end of a scan's data. It - // effectively puts back any markers that it overscanned (e.g. an - // "\xff\xd9" EOI marker), but it does not put back non-marker data, - // and thus it can silently ignore a small number of extraneous - // non-marker bytes before next_marker has a chance to see them (and - // print a warning). - // We are therefore also liberal in what we accept. Extraneous data - // is silently ignore - // This is similar to, but not exactly the same as, the restart - // mechanism within a scan (the RST[0-7] markers). - // Note that extraneous 0xff bytes in e.g. SOS data are escaped as - // "\xff\x00", and so are detected a little further down below. - this.Temp[0] = this.Temp[1]; - this.Temp[1] = this.ReadByte(); - } - - byte marker = this.Temp[1]; - if (marker == 0) - { - // Treat "\xff\x00" as extraneous data. - continue; - } - - while (marker == 0xff) - { - // Section B.1.1.2 says, "Any marker may optionally be preceded by any - // number of fill bytes, which are bytes assigned code X'FF'". - marker = this.ReadByte(); - } - - // End Of Image. - if (marker == JpegConstants.Markers.EOI) - { - break; - } - - if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) - { - // Figures B.2 and B.16 of the specification suggest that restart markers should - // only occur between Entropy Coded Segments and not after the final ECS. - // However, some encoders may generate incorrect JPEGs with a final restart - // marker. That restart marker will be seen here instead of inside the ProcessSOS - // method, and is ignored as a harmless error. Restart markers have no extra data, - // so we check for this before we read the 16-bit length of the segment. - continue; - } - - // Read the 16-bit length of the segment. The value includes the 2 bytes for the - // length itself, so we subtract 2 to get the number of remaining bytes. - this.ReadFull(this.Temp, 0, 2); - int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; - if (remaining < 0) - { - throw new ImageFormatException("Short segment length."); - } - - switch (marker) - { - case JpegConstants.Markers.SOF0: - case JpegConstants.Markers.SOF1: - case JpegConstants.Markers.SOF2: - this.IsProgressive = marker == JpegConstants.Markers.SOF2; - this.ProcessStartOfFrameMarker(remaining); - if (metadataOnly && this.isJfif) - { - return; - } - - break; - case JpegConstants.Markers.DHT: - if (metadataOnly) - { - this.Skip(remaining); - } - else - { - this.ProcessDefineHuffmanTablesMarker(remaining); - } - - break; - case JpegConstants.Markers.DQT: - if (metadataOnly) - { - this.Skip(remaining); - } - else - { - this.ProcessDqt(remaining); - } - - break; - case JpegConstants.Markers.SOS: - if (metadataOnly) - { - return; - } - - // when this is a progressive image this gets called a number of times - // need to know how many times this should be called in total. - this.ProcessStartOfScan(remaining); - if (!this.IsProgressive) - { - // if this is not a progressive image we can stop processing bytes as we now have the image data. - processBytes = false; - } - - break; - case JpegConstants.Markers.DRI: - if (metadataOnly) - { - this.Skip(remaining); - } - else - { - this.ProcessDefineRestartIntervalMarker(remaining); - } - - break; - case JpegConstants.Markers.APP0: - this.ProcessApplicationHeader(remaining); - break; - case JpegConstants.Markers.APP1: - this.ProcessApp1Marker(remaining, image); - break; - case JpegConstants.Markers.APP14: - this.ProcessApp14Marker(remaining); - break; - default: - if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15) - || marker == JpegConstants.Markers.COM) - { - this.Skip(remaining); - } - else if (marker < JpegConstants.Markers.SOF0) - { - // See Table B.1 "Marker code assignments". - throw new ImageFormatException("Unknown marker"); - } - else - { - throw new ImageFormatException("Unknown marker"); - } - - break; - } + this.DecodeBlocksIntoJpegImageChannels(); + this.ConvertJpegPixelsToImagePixels(image); } } @@ -445,7 +199,7 @@ namespace ImageSharp.Formats { if (blockArray != null) { - DecodedBlockMemento.ArrayPool.Return(blockArray, true); + DecodedBlockMemento.ReturnArray(blockArray); } } @@ -701,6 +455,267 @@ namespace ImageSharp.Formats packed.PackFromBytes(r, g, b, 255); } + /// + /// Read metadata from stream and read the blocks in the scans into . + /// + /// The pixel type + /// The + /// The stream + /// Whether to decode metadata only. + private void ProcessStream(Image image, Stream stream, bool metadataOnly) + where TColor : struct, IPackedPixel, IEquatable + { + this.InputStream = stream; + + // Check for the Start Of Image marker. + this.ReadFull(this.Temp, 0, 2); + if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI) + { + throw new ImageFormatException("Missing SOI marker."); + } + + // Process the remaining segments until the End Of Image marker. + bool processBytes = true; + + // we can't currently short circute progressive images so don't try. + while (processBytes) + { + this.ReadFull(this.Temp, 0, 2); + while (this.Temp[0] != 0xff) + { + // Strictly speaking, this is a format error. However, libjpeg is + // liberal in what it accepts. As of version 9, next_marker in + // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and + // continues to decode the stream. Even before next_marker sees + // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many + // bytes as it can, possibly past the end of a scan's data. It + // effectively puts back any markers that it overscanned (e.g. an + // "\xff\xd9" EOI marker), but it does not put back non-marker data, + // and thus it can silently ignore a small number of extraneous + // non-marker bytes before next_marker has a chance to see them (and + // print a warning). + // We are therefore also liberal in what we accept. Extraneous data + // is silently ignore + // This is similar to, but not exactly the same as, the restart + // mechanism within a scan (the RST[0-7] markers). + // Note that extraneous 0xff bytes in e.g. SOS data are escaped as + // "\xff\x00", and so are detected a little further down below. + this.Temp[0] = this.Temp[1]; + this.Temp[1] = this.ReadByte(); + } + + byte marker = this.Temp[1]; + if (marker == 0) + { + // Treat "\xff\x00" as extraneous data. + continue; + } + + while (marker == 0xff) + { + // Section B.1.1.2 says, "Any marker may optionally be preceded by any + // number of fill bytes, which are bytes assigned code X'FF'". + marker = this.ReadByte(); + } + + // End Of Image. + if (marker == JpegConstants.Markers.EOI) + { + break; + } + + if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) + { + // Figures B.2 and B.16 of the specification suggest that restart markers should + // only occur between Entropy Coded Segments and not after the final ECS. + // However, some encoders may generate incorrect JPEGs with a final restart + // marker. That restart marker will be seen here instead of inside the ProcessSOS + // method, and is ignored as a harmless error. Restart markers have no extra data, + // so we check for this before we read the 16-bit length of the segment. + continue; + } + + // Read the 16-bit length of the segment. The value includes the 2 bytes for the + // length itself, so we subtract 2 to get the number of remaining bytes. + this.ReadFull(this.Temp, 0, 2); + int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; + if (remaining < 0) + { + throw new ImageFormatException("Short segment length."); + } + + switch (marker) + { + case JpegConstants.Markers.SOF0: + case JpegConstants.Markers.SOF1: + case JpegConstants.Markers.SOF2: + this.IsProgressive = marker == JpegConstants.Markers.SOF2; + this.ProcessStartOfFrameMarker(remaining); + if (metadataOnly && this.isJfif) + { + return; + } + + break; + case JpegConstants.Markers.DHT: + if (metadataOnly) + { + this.Skip(remaining); + } + else + { + this.ProcessDefineHuffmanTablesMarker(remaining); + } + + break; + case JpegConstants.Markers.DQT: + if (metadataOnly) + { + this.Skip(remaining); + } + else + { + this.ProcessDqt(remaining); + } + + break; + case JpegConstants.Markers.SOS: + if (metadataOnly) + { + return; + } + + // when this is a progressive image this gets called a number of times + // need to know how many times this should be called in total. + this.ProcessStartOfScan(remaining); + if (!this.IsProgressive) + { + // if this is not a progressive image we can stop processing bytes as we now have the image data. + processBytes = false; + } + + break; + case JpegConstants.Markers.DRI: + if (metadataOnly) + { + this.Skip(remaining); + } + else + { + this.ProcessDefineRestartIntervalMarker(remaining); + } + + break; + case JpegConstants.Markers.APP0: + this.ProcessApplicationHeader(remaining); + break; + case JpegConstants.Markers.APP1: + this.ProcessApp1Marker(remaining, image); + break; + case JpegConstants.Markers.APP14: + this.ProcessApp14Marker(remaining); + break; + default: + if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15) + || marker == JpegConstants.Markers.COM) + { + this.Skip(remaining); + } + else if (marker < JpegConstants.Markers.SOF0) + { + // See Table B.1 "Marker code assignments". + throw new ImageFormatException("Unknown marker"); + } + else + { + throw new ImageFormatException("Unknown marker"); + } + + break; + } + } + } + + /// + /// Process the blocks in into Jpeg image channels ( and ) + /// + /// The pixel type + private void DecodeBlocksIntoJpegImageChannels() + where TColor : struct, IPackedPixel, IEquatable + { + JpegScanDecoder scanDecoder = default(JpegScanDecoder); + JpegScanDecoder.Init(&scanDecoder); + + for (int componentIndex = 0; componentIndex < this.ComponentCount; componentIndex++) + { + scanDecoder.ComponentIndex = componentIndex; + DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex]; + for (int i = 0; i < blockArray.Length; i++) + { + scanDecoder.LoadMemento(ref blockArray[i]); + scanDecoder.ProcessBlock(this); + } + } + } + + /// + /// Convert the pixel data in and/or into pixels of + /// + /// The pixel type + /// The destination image + private void ConvertJpegPixelsToImagePixels(Image image) + where TColor : struct, IPackedPixel, IEquatable + { + if (this.grayImage.IsInitialized) + { + this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image); + } + else if (this.ycbcrImage != null) + { + if (this.ComponentCount == 4) + { + if (!this.adobeTransformValid) + { + throw new ImageFormatException( + "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata"); + } + + // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe + // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html + // TODO: YCbCrA? + if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck) + { + this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image); + } + else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) + { + // Assume CMYK + this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image); + } + + return; + } + + if (this.ComponentCount == 3) + { + if (this.IsRGB()) + { + this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image); + return; + } + + this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image); + return; + } + + throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); + } + else + { + throw new ImageFormatException("Missing SOS marker."); + } + } + /// /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header. /// @@ -1470,7 +1485,7 @@ namespace ImageSharp.Formats { int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = DecodedBlockMemento.ArrayPool.Rent(size); + this.DecodedBlocks[i] = DecodedBlockMemento.RentArray(size); } } From b51dbf33fd063129282e2015a071044f672d40fc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 23 Jan 2017 01:53:34 +0100 Subject: [PATCH 10/26] verifying Vector.IsHardwareAccelerated in tests and benchmarks --- .../Image/MultiImageBenchmarkBase.cs | 4 ++++ tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj | 4 ++++ tests/ImageSharp.Sandbox46/packages.config | 1 + .../Formats/Jpg/JpegProfilingBenchmarks.cs | 8 +++++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs index 9007ef75b6..26aad07b8d 100644 --- a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs @@ -86,6 +86,10 @@ namespace ImageSharp.Benchmarks.Image [Setup] public void ReadImages() { + if (!Vector.IsHardwareAccelerated) + { + throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); + } // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); this.ReadFilesImpl(); } diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 7994456da4..ab46034d16 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -41,6 +41,10 @@ + + ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll + True + diff --git a/tests/ImageSharp.Sandbox46/packages.config b/tests/ImageSharp.Sandbox46/packages.config index 8b4f34078a..8163e6e2e8 100644 --- a/tests/ImageSharp.Sandbox46/packages.config +++ b/tests/ImageSharp.Sandbox46/packages.config @@ -1,5 +1,6 @@  + diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index eb9747cce0..64528d4aca 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Tests using System; using System.IO; using System.Linq; + using System.Numerics; using ImageSharp.Formats; @@ -30,11 +31,16 @@ namespace ImageSharp.Tests [InlineData(30, TestImages.Jpeg.Baseline.Jpeg444)] public void DecodeJpeg(int executionCount, string fileName) { + if (!Vector.IsHardwareAccelerated) + { + throw new Exception("Vector.IsHardwareAccelerated == false! (Wrong build?)"); + } + string path = TestFile.GetPath(fileName); byte[] bytes = File.ReadAllBytes(path); this.Measure( - 100, + executionCount, () => { Image img = new Image(bytes); From 09f80391409e0a47a1477057cd5d6a6ab23e84ba Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 23 Jan 2017 02:31:38 +0100 Subject: [PATCH 11/26] console app benchmarking in Sandbox46 --- .../JpegDecoderCore.cs | 5 - .../ImageSharp.Sandbox46.csproj | 116 ++++++++++++++++++ tests/ImageSharp.Sandbox46/Program.cs | 31 ++++- tests/ImageSharp.Sandbox46/app.config | 15 +++ tests/ImageSharp.Sandbox46/packages.config | 49 ++++++++ .../Formats/Jpg/JpegProfilingBenchmarks.cs | 25 ++-- 6 files changed, 226 insertions(+), 15 deletions(-) create mode 100644 tests/ImageSharp.Sandbox46/app.config diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 2184e9e1f0..7d6cfba52b 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -353,11 +353,6 @@ namespace ImageSharp.Formats return errorCode; } } - - // else if (errorCode == DecoderErrorCode.UnexpectedEndOfStream) - // { - // errorCode.ThrowExceptionForErrorCode(); - // } else { this.UnreadByteStuffedByte(); diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index ab46034d16..c4c7fb042e 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -24,6 +24,7 @@ prompt 4 true + false pdbonly @@ -33,24 +34,127 @@ prompt 4 true + false ImageSharp.Sandbox46.Program + + ..\..\packages\BenchmarkDotNet.0.10.2\lib\net45\BenchmarkDotNet.dll + True + + + ..\..\packages\BenchmarkDotNet.Core.0.10.2\lib\net45\BenchmarkDotNet.Core.dll + True + + + ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.2\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll + True + + + ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.2\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll + True + + + ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + True + + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + True + + + ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll + True + + + ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll + True + + + ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + True + + + + ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll + True + + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + True + + + ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll + True + + + ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll + True + + + ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll + True + + ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll True + + ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + True + + + ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll + True + + + ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + + + ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll + True + + + ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + True + + + ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll + True + + + ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll + True + + + ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll + True + + + ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll + True + ..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll True @@ -210,8 +314,10 @@ + + @@ -219,13 +325,23 @@ + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + InputProcessor --- .../Components/Decoder/Bits.cs | 40 ++++++------- .../Components/Decoder/HuffmanTree.cs | 6 +- .../{BufferProcessor.cs => InputProcessor.cs} | 8 +-- .../Components/Decoder/JpegScanDecoder.cs | 44 +++++++------- .../JpegDecoderCore.cs | 58 +++++++++---------- 5 files changed, 78 insertions(+), 78 deletions(-) rename src/ImageSharp.Formats.Jpeg/Components/Decoder/{BufferProcessor.cs => InputProcessor.cs} (97%) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs index 21d4f8c430..02f585be02 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs @@ -36,11 +36,11 @@ namespace ImageSharp.Formats.Jpg /// the caller is the one responsible for first checking that bits.UnreadBits < n. /// /// The number of bits to ensure. - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EnsureNBits(int n, ref BufferProcessor bufferProcessor) + public void EnsureNBits(int n, ref InputProcessor inputProcessor) { - DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref bufferProcessor); + DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor); errorCode.EnsureNoError(); } @@ -51,13 +51,13 @@ namespace ImageSharp.Formats.Jpg /// This method does not throw. Returns instead. /// /// The number of bits to ensure. - /// The + /// The /// Error code - public DecoderErrorCode EnsureNBitsUnsafe(int n, ref BufferProcessor bufferProcessor) + public DecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor) { while (true) { - DecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref bufferProcessor); + DecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor); if (errorCode != DecoderErrorCode.NoError || this.UnreadBits >= n) { return errorCode; @@ -68,34 +68,34 @@ namespace ImageSharp.Formats.Jpg /// /// Unrolled version of for n==8 /// - /// The + /// The /// A - public DecoderErrorCode Ensure8BitsUnsafe(ref BufferProcessor bufferProcessor) + public DecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor) { - return this.EnsureBitsStepImpl(ref bufferProcessor); + return this.EnsureBitsStepImpl(ref inputProcessor); } /// /// Unrolled version of for n==1 /// - /// The + /// The /// A - public DecoderErrorCode Ensure1BitUnsafe(ref BufferProcessor bufferProcessor) + public DecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor) { - return this.EnsureBitsStepImpl(ref bufferProcessor); + return this.EnsureBitsStepImpl(ref inputProcessor); } /// /// Receive extend /// /// Byte - /// The + /// The /// Read bits value [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReceiveExtend(int t, ref BufferProcessor bufferProcessor) + public int ReceiveExtend(int t, ref InputProcessor inputProcessor) { int x; - DecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref bufferProcessor, out x); + DecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out x); errorCode.EnsureNoError(); return x; } @@ -104,14 +104,14 @@ namespace ImageSharp.Formats.Jpg /// Receive extend /// /// Byte - /// The + /// The /// Read bits value /// The - public DecoderErrorCode ReceiveExtendUnsafe(int t, ref BufferProcessor bufferProcessor, out int x) + public DecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x) { if (this.UnreadBits < t) { - DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref bufferProcessor); + DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor); if (errorCode != DecoderErrorCode.NoError) { x = int.MaxValue; @@ -132,10 +132,10 @@ namespace ImageSharp.Formats.Jpg return DecoderErrorCode.NoError; } - private DecoderErrorCode EnsureBitsStepImpl(ref BufferProcessor bufferProcessor) + private DecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor) { int c; - DecoderErrorCode errorCode = bufferProcessor.Bytes.ReadByteStuffedByteUnsafe(bufferProcessor.InputStream, out c); + DecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out c); if (errorCode != DecoderErrorCode.NoError) { diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs index cd8781a5fb..abe7b1106f 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs @@ -127,11 +127,11 @@ namespace ImageSharp.Formats.Jpg /// /// Internal part of the DHT processor, whatever does it mean /// - /// The decoder instance + /// The decoder instance /// The temporal buffer that holds the data that has been read from the Jpeg stream /// Remaining bits public void ProcessDefineHuffmanTablesMarkerLoop( - ref BufferProcessor bufferProcessor, + ref InputProcessor inputProcessor, byte[] defineHuffmanTablesData, ref int remaining) { @@ -163,7 +163,7 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("DHT has wrong length"); } - bufferProcessor.ReadFull(this.Values, 0, this.Length); + inputProcessor.ReadFull(this.Values, 0, this.Length); for (int i = 0; i < this.Values.Length; i++) { diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/BufferProcessor.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs similarity index 97% rename from src/ImageSharp.Formats.Jpeg/Components/Decoder/BufferProcessor.cs rename to src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs index 6dc3c012cd..60042d36f8 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/BufferProcessor.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -13,7 +13,7 @@ namespace ImageSharp.Formats.Jpg /// Encapsulates stream reading and processing data and operations for . /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s /// - internal struct BufferProcessor : IDisposable + internal struct InputProcessor : IDisposable { /// /// Holds the unprocessed bits that have been taken from the byte-stream. @@ -26,11 +26,11 @@ namespace ImageSharp.Formats.Jpg public Bytes Bytes; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The input /// Temporal buffer, same as - public BufferProcessor(Stream inputStream, byte[] temp) + public InputProcessor(Stream inputStream, byte[] temp) { this.Bits = default(Bits); this.Bytes = Bytes.Create(); diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 472720f8fc..2d292ddf93 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -198,7 +198,7 @@ namespace ImageSharp.Formats.Jpg int blockIndex = this.GetBlockIndex(decoder); this.data.Block = decoder.DecodedBlocks[this.ComponentIndex][blockIndex].Block; - if (!decoder.BufferProcessor.UnexpectedEndOfStreamReached) + if (!decoder.InputProcessor.UnexpectedEndOfStreamReached) { this.DecodeBlock(decoder, scanIndex); } @@ -218,10 +218,10 @@ namespace ImageSharp.Formats.Jpg { // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, // but this one assumes well-formed input, and hence the restart marker follows immediately. - if (!decoder.BufferProcessor.UnexpectedEndOfStreamReached) + if (!decoder.InputProcessor.UnexpectedEndOfStreamReached) { - DecoderErrorCode errorCode = decoder.BufferProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); - if (decoder.BufferProcessor.CheckEOFEnsureNoError(errorCode)) + DecoderErrorCode errorCode = decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); + if (decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) { if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) { @@ -237,7 +237,7 @@ namespace ImageSharp.Formats.Jpg } // Reset the Huffman decoder. - decoder.BufferProcessor.Bits = default(Bits); + decoder.InputProcessor.Bits = default(Bits); // Reset the DC components, as per section F.2.1.3.1. this.ResetDc(); @@ -293,7 +293,7 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("SOS has wrong length"); } - decoder.BufferProcessor.ReadFull(decoder.Temp, 0, remaining); + decoder.InputProcessor.ReadFull(decoder.Temp, 0, remaining); this.componentScanCount = decoder.Temp[0]; int scanComponentCountX2 = 2 * this.componentScanCount; @@ -355,7 +355,7 @@ namespace ImageSharp.Formats.Jpg int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { - this.Refine(ref decoder.BufferProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); + this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); } else { @@ -367,10 +367,10 @@ namespace ImageSharp.Formats.Jpg // Decode the DC coefficient, as specified in section F.2.2.1. int value; int huffmanIndex = (DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; - errorCode = decoder.BufferProcessor.DecodeHuffmanUnsafe( + errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe( ref decoder.HuffmanTrees[huffmanIndex], out value); - if (!decoder.BufferProcessor.CheckEOF(errorCode)) + if (!decoder.InputProcessor.CheckEOF(errorCode)) { return; } @@ -381,8 +381,8 @@ namespace ImageSharp.Formats.Jpg } int deltaDC; - errorCode = decoder.BufferProcessor.ReceiveExtendUnsafe(value, out deltaDC); - if (!decoder.BufferProcessor.CheckEOFEnsureNoError(errorCode)) + errorCode = decoder.InputProcessor.ReceiveExtendUnsafe(value, out deltaDC); + if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) { return; } @@ -403,8 +403,8 @@ namespace ImageSharp.Formats.Jpg for (; zig <= this.zigEnd; zig++) { int value; - errorCode = decoder.BufferProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value); - if (!decoder.BufferProcessor.CheckEOF(errorCode)) + errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value); + if (!decoder.InputProcessor.CheckEOF(errorCode)) { return; } @@ -420,8 +420,8 @@ namespace ImageSharp.Formats.Jpg } int ac; - errorCode = decoder.BufferProcessor.ReceiveExtendUnsafe(val1, out ac); - if (!decoder.BufferProcessor.CheckEOFEnsureNoError(errorCode)) + errorCode = decoder.InputProcessor.ReceiveExtendUnsafe(val1, out ac); + if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) { return; } @@ -436,8 +436,8 @@ namespace ImageSharp.Formats.Jpg this.eobRun = (ushort)(1 << val0); if (val0 != 0) { - errorCode = this.DecodeEobRun(val0, ref decoder.BufferProcessor); - if (!decoder.BufferProcessor.CheckEOFEnsureNoError(errorCode)) + errorCode = this.DecodeEobRun(val0, ref decoder.InputProcessor); + if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode)) { return; } @@ -454,7 +454,7 @@ namespace ImageSharp.Formats.Jpg } } - private DecoderErrorCode DecodeEobRun(int count, ref BufferProcessor decoder) + private DecoderErrorCode DecodeEobRun(int count, ref InputProcessor decoder) { int bitsResult; DecoderErrorCode errorCode = decoder.DecodeBitsUnsafe(count, out bitsResult); @@ -539,10 +539,10 @@ namespace ImageSharp.Formats.Jpg /// /// Decodes a successive approximation refinement block, as specified in section G.1.2. /// - /// The instance + /// The instance /// The Huffman tree /// The low transform offset - private void Refine(ref BufferProcessor bp, ref HuffmanTree h, int delta) + private void Refine(ref InputProcessor bp, ref HuffmanTree h, int delta) { Block8x8F* b = this.pointers.Block; @@ -668,12 +668,12 @@ namespace ImageSharp.Formats.Jpg /// Refines non-zero entries of b in zig-zag order. /// If >= 0, the first zero entries are skipped over. /// - /// The + /// The /// The zig-zag start index /// The non-zero entry /// The low transform offset /// The - private int RefineNonZeroes(ref BufferProcessor bp, int zig, int nz, int delta) + private int RefineNonZeroes(ref InputProcessor bp, int zig, int nz, int delta) { var b = this.pointers.Block; for (; zig <= this.zigEnd; zig++) diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 609eb5f5fb..127b399ab4 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -35,7 +35,7 @@ namespace ImageSharp.Formats /// Encapsulates stream reading and processing data and operations for . /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s /// - public BufferProcessor BufferProcessor; + public InputProcessor InputProcessor; #pragma warning restore SA401 /// @@ -200,7 +200,7 @@ namespace ImageSharp.Formats } this.ycbcrImage?.Dispose(); - this.BufferProcessor.Dispose(); + this.InputProcessor.Dispose(); this.grayImage.ReturnPooled(); this.blackImage.ReturnPooled(); } @@ -274,10 +274,10 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel, IEquatable { this.InputStream = stream; - this.BufferProcessor = new BufferProcessor(stream, this.Temp); + this.InputProcessor = new InputProcessor(stream, this.Temp); // Check for the Start Of Image marker. - this.BufferProcessor.ReadFull(this.Temp, 0, 2); + this.InputProcessor.ReadFull(this.Temp, 0, 2); if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI) { throw new ImageFormatException("Missing SOI marker."); @@ -289,7 +289,7 @@ namespace ImageSharp.Formats // we can't currently short circute progressive images so don't try. while (processBytes) { - this.BufferProcessor.ReadFull(this.Temp, 0, 2); + this.InputProcessor.ReadFull(this.Temp, 0, 2); while (this.Temp[0] != 0xff) { // Strictly speaking, this is a format error. However, libjpeg is @@ -310,7 +310,7 @@ namespace ImageSharp.Formats // Note that extraneous 0xff bytes in e.g. SOS data are escaped as // "\xff\x00", and so are detected a little further down below. this.Temp[0] = this.Temp[1]; - this.Temp[1] = this.BufferProcessor.ReadByte(); + this.Temp[1] = this.InputProcessor.ReadByte(); } byte marker = this.Temp[1]; @@ -324,7 +324,7 @@ namespace ImageSharp.Formats { // Section B.1.1.2 says, "Any marker may optionally be preceded by any // number of fill bytes, which are bytes assigned code X'FF'". - marker = this.BufferProcessor.ReadByte(); + marker = this.InputProcessor.ReadByte(); } // End Of Image. @@ -346,7 +346,7 @@ namespace ImageSharp.Formats // Read the 16-bit length of the segment. The value includes the 2 bytes for the // length itself, so we subtract 2 to get the number of remaining bytes. - this.BufferProcessor.ReadFull(this.Temp, 0, 2); + this.InputProcessor.ReadFull(this.Temp, 0, 2); int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; if (remaining < 0) { @@ -369,7 +369,7 @@ namespace ImageSharp.Formats case JpegConstants.Markers.DHT: if (metadataOnly) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); } else { @@ -380,7 +380,7 @@ namespace ImageSharp.Formats case JpegConstants.Markers.DQT: if (metadataOnly) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); } else { @@ -397,7 +397,7 @@ namespace ImageSharp.Formats // when this is a progressive image this gets called a number of times // need to know how many times this should be called in total. this.ProcessStartOfScan(remaining); - if (this.BufferProcessor.UnexpectedEndOfStreamReached || !this.IsProgressive) + if (this.InputProcessor.UnexpectedEndOfStreamReached || !this.IsProgressive) { // if unexpeced EOF reached or this is not a progressive image we can stop processing bytes as we now have the image data. processBytes = false; @@ -407,7 +407,7 @@ namespace ImageSharp.Formats case JpegConstants.Markers.DRI: if (metadataOnly) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); } else { @@ -428,7 +428,7 @@ namespace ImageSharp.Formats if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15) || marker == JpegConstants.Markers.COM) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); } else if (marker < JpegConstants.Markers.SOF0) { @@ -457,7 +457,7 @@ namespace ImageSharp.Formats { JpegScanDecoder scan = default(JpegScanDecoder); JpegScanDecoder.InitStreamReading(&scan, this, remaining); - this.BufferProcessor.Bits = default(Bits); + this.InputProcessor.Bits = default(Bits); this.MakeImage(); scan.DecodeBlocks(this); } @@ -918,11 +918,11 @@ namespace ImageSharp.Formats { if (remaining < 12) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); return; } - this.BufferProcessor.ReadFull(this.Temp, 0, 12); + this.InputProcessor.ReadFull(this.Temp, 0, 12); remaining -= 12; if (this.Temp[0] == 'A' && this.Temp[1] == 'd' && this.Temp[2] == 'o' && this.Temp[3] == 'b' @@ -934,7 +934,7 @@ namespace ImageSharp.Formats if (remaining > 0) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); } } @@ -949,12 +949,12 @@ namespace ImageSharp.Formats { if (remaining < 6) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); return; } byte[] profile = new byte[remaining]; - this.BufferProcessor.ReadFull(profile, 0, remaining); + this.InputProcessor.ReadFull(profile, 0, remaining); if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' && profile[5] == '\0') @@ -971,11 +971,11 @@ namespace ImageSharp.Formats { if (remaining < 5) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); return; } - this.BufferProcessor.ReadFull(this.Temp, 0, 13); + this.InputProcessor.ReadFull(this.Temp, 0, 13); remaining -= 13; // TODO: We should be using constants for this. @@ -990,7 +990,7 @@ namespace ImageSharp.Formats if (remaining > 0) { - this.BufferProcessor.Skip(remaining); + this.InputProcessor.Skip(remaining); } } @@ -1008,7 +1008,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("DHT has wrong length"); } - this.BufferProcessor.ReadFull(this.Temp, 0, 17); + this.InputProcessor.ReadFull(this.Temp, 0, 17); int tc = this.Temp[0] >> 4; if (tc > HuffmanTree.MaxTc) @@ -1024,7 +1024,7 @@ namespace ImageSharp.Formats int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th; this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop( - ref this.BufferProcessor, + ref this.InputProcessor, this.Temp, ref remaining); } @@ -1042,7 +1042,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("DRI has wrong length"); } - this.BufferProcessor.ReadFull(this.Temp, 0, remaining); + this.InputProcessor.ReadFull(this.Temp, 0, remaining); this.RestartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1]; } @@ -1060,7 +1060,7 @@ namespace ImageSharp.Formats bool done = false; remaining--; - byte x = this.BufferProcessor.ReadByte(); + byte x = this.InputProcessor.ReadByte(); int tq = x & 0x0F; if (tq > MaxTq) { @@ -1077,7 +1077,7 @@ namespace ImageSharp.Formats } remaining -= Block8x8F.ScalarCount; - this.BufferProcessor.ReadFull(this.Temp, 0, Block8x8F.ScalarCount); + this.InputProcessor.ReadFull(this.Temp, 0, Block8x8F.ScalarCount); for (int i = 0; i < Block8x8F.ScalarCount; i++) { @@ -1093,7 +1093,7 @@ namespace ImageSharp.Formats } remaining -= 2 * Block8x8F.ScalarCount; - this.BufferProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount); + this.InputProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount); for (int i = 0; i < Block8x8F.ScalarCount; i++) { @@ -1143,7 +1143,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Incorrect number of components"); } - this.BufferProcessor.ReadFull(this.Temp, 0, remaining); + this.InputProcessor.ReadFull(this.Temp, 0, remaining); // We only support 8-bit precision. if (this.Temp[0] != 8) From 1b4fc8354cb1e2465d97d210589595ef0a55dd48 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 24 Jan 2017 03:09:52 +0100 Subject: [PATCH 22/26] HuffmanTree cleanup --- .../Components/Decoder/HuffmanTree.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs index abe7b1106f..236cffbdf2 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs @@ -63,11 +63,6 @@ namespace ImageSharp.Formats.Jpg /// /// Gets the the decoded values, sorted by their encoding. /// - public byte[] Values; - - /// - /// Same as , converted to int-s - /// public int[] ValuesAsInt; /// @@ -87,11 +82,11 @@ namespace ImageSharp.Formats.Jpg /// public int[] Indices; - private static readonly ArrayPool IntBuffer256 = ArrayPool.Create(MaxNCodes, 50); + private static readonly ArrayPool IntPool256 = ArrayPool.Create(MaxNCodes, 50); - private static readonly ArrayPool ByteBuffer256 = ArrayPool.Create(MaxNCodes, 50); + private static readonly ArrayPool BytePool256 = ArrayPool.Create(MaxNCodes, 50); - private static readonly ArrayPool CodesBuffer16 = ArrayPool.Create(MaxCodeLength, 50); + private static readonly ArrayPool CodesPool16 = ArrayPool.Create(MaxCodeLength, 50); /// /// Creates and initializes an array of instances of size @@ -116,12 +111,11 @@ namespace ImageSharp.Formats.Jpg /// public void Dispose() { - IntBuffer256.Return(this.Lut, true); - IntBuffer256.Return(this.ValuesAsInt, true); - ByteBuffer256.Return(this.Values, true); - CodesBuffer16.Return(this.MinCodes, true); - CodesBuffer16.Return(this.MaxCodes, true); - CodesBuffer16.Return(this.Indices, true); + IntPool256.Return(this.Lut, true); + IntPool256.Return(this.ValuesAsInt, true); + CodesPool16.Return(this.MinCodes, true); + CodesPool16.Return(this.MaxCodes, true); + CodesPool16.Return(this.Indices, true); } /// @@ -163,11 +157,20 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("DHT has wrong length"); } - inputProcessor.ReadFull(this.Values, 0, this.Length); + byte[] values = null; + try + { + values = BytePool256.Rent(MaxNCodes); + inputProcessor.ReadFull(values, 0, this.Length); - for (int i = 0; i < this.Values.Length; i++) + for (int i = 0; i < values.Length; i++) + { + this.ValuesAsInt[i] = values[i]; + } + } + finally { - this.ValuesAsInt[i] = this.Values[i]; + BytePool256.Return(values, true); } // Derive the look-up table. @@ -230,11 +233,11 @@ namespace ImageSharp.Formats.Jpg /// Gets the value for the given code and index. /// /// The code - /// The index + /// The code length /// The value - public int GetValue(int code, int i) + public int GetValue(int code, int codeLength) { - return this.ValuesAsInt[this.Indices[i] + code - this.MinCodes[i]]; + return this.ValuesAsInt[this.Indices[codeLength] + code - this.MinCodes[codeLength]]; } /// @@ -242,12 +245,11 @@ namespace ImageSharp.Formats.Jpg /// private void Init() { - this.Lut = IntBuffer256.Rent(MaxNCodes); - this.Values = ByteBuffer256.Rent(MaxNCodes); - this.ValuesAsInt = IntBuffer256.Rent(MaxNCodes); - this.MinCodes = CodesBuffer16.Rent(MaxCodeLength); - this.MaxCodes = CodesBuffer16.Rent(MaxCodeLength); - this.Indices = CodesBuffer16.Rent(MaxCodeLength); + this.Lut = IntPool256.Rent(MaxNCodes); + this.ValuesAsInt = IntPool256.Rent(MaxNCodes); + this.MinCodes = CodesPool16.Rent(MaxCodeLength); + this.MaxCodes = CodesPool16.Rent(MaxCodeLength); + this.Indices = CodesPool16.Rent(MaxCodeLength); } } } \ No newline at end of file From 749b01a31a73ad942097d9f733a97476db373e86 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 24 Jan 2017 03:11:41 +0100 Subject: [PATCH 23/26] ValuesAsInt --> Values --- .../Components/Decoder/HuffmanTree.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs index 236cffbdf2..03013219c3 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs @@ -63,7 +63,7 @@ namespace ImageSharp.Formats.Jpg /// /// Gets the the decoded values, sorted by their encoding. /// - public int[] ValuesAsInt; + public int[] Values; /// /// Gets the array of minimum codes. @@ -112,7 +112,7 @@ namespace ImageSharp.Formats.Jpg public void Dispose() { IntPool256.Return(this.Lut, true); - IntPool256.Return(this.ValuesAsInt, true); + IntPool256.Return(this.Values, true); CodesPool16.Return(this.MinCodes, true); CodesPool16.Return(this.MaxCodes, true); CodesPool16.Return(this.Indices, true); @@ -165,7 +165,7 @@ namespace ImageSharp.Formats.Jpg for (int i = 0; i < values.Length; i++) { - this.ValuesAsInt[i] = values[i]; + this.Values[i] = values[i]; } } finally @@ -193,7 +193,7 @@ namespace ImageSharp.Formats.Jpg // The high 8 bits of lutValue are the encoded value. // The low 8 bits are 1 plus the codeLength. int base2 = code << (7 - i); - int lutValue = (this.ValuesAsInt[x] << 8) | (2 + i); + int lutValue = (this.Values[x] << 8) | (2 + i); for (int k = 0; k < 1 << (7 - i); k++) { @@ -237,7 +237,7 @@ namespace ImageSharp.Formats.Jpg /// The value public int GetValue(int code, int codeLength) { - return this.ValuesAsInt[this.Indices[codeLength] + code - this.MinCodes[codeLength]]; + return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]]; } /// @@ -246,7 +246,7 @@ namespace ImageSharp.Formats.Jpg private void Init() { this.Lut = IntPool256.Rent(MaxNCodes); - this.ValuesAsInt = IntPool256.Rent(MaxNCodes); + this.Values = IntPool256.Rent(MaxNCodes); this.MinCodes = CodesPool16.Rent(MaxCodeLength); this.MaxCodes = CodesPool16.Rent(MaxCodeLength); this.Indices = CodesPool16.Rent(MaxCodeLength); From cc5a6f9cfb209b367367500b3a5185dd87557b55 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 28 Jan 2017 02:53:40 +0100 Subject: [PATCH 24/26] eliminated the gray top left Block artifact --- .../Components/Decoder/DecodedBlockMemento.cs | 85 ++++++++++++------- .../Components/Decoder/JpegScanDecoder.cs | 6 +- .../JpegDecoderCore.cs | 27 +++--- .../Formats/Jpg/JpegDecoderTests.cs | 34 +++++++- 4 files changed, 104 insertions(+), 48 deletions(-) diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs index 49d9b591f9..04ece04ee8 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Formats.Jpg { + using System; using System.Buffers; /// @@ -33,44 +34,68 @@ namespace ImageSharp.Formats.Jpg public Block8x8F Block; /// - /// The used to pool data in . - /// Should always clean arrays when returning! + /// Store the block data into a at the given index of an . /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(); - - /// - /// Rent an array of -s from the pool. - /// - /// The requested array size - /// An array of -s - public static DecodedBlockMemento[] RentArray(int size) + /// The array + /// The index in the array + /// X coordinate of the block + /// Y coordinate of the block + /// The + public static void Store(ref DecodedBlockMemento.Array blockArray, int index, int bx, int by, ref Block8x8F block) { - return ArrayPool.Rent(size); - } + if (index >= blockArray.Count) + { + throw new IndexOutOfRangeException("Block index is out of range in DecodedBlockMemento.Store()!"); + } - /// - /// Returns the array to the pool. - /// - /// The array - public static void ReturnArray(DecodedBlockMemento[] blockArray) - { - ArrayPool.Return(blockArray, true); + blockArray.Buffer[index].Initialized = true; + blockArray.Buffer[index].Bx = bx; + blockArray.Buffer[index].By = by; + blockArray.Buffer[index].Block = block; } /// - /// Store the block data into a at the given index. + /// Because has no information for rented arrays, we need to store the count and the buffer separately. /// - /// The array of - /// The index in the array - /// X coordinate of the block - /// Y coordinate of the block - /// The - public static void Store(DecodedBlockMemento[] blockArray, int index, int bx, int by, ref Block8x8F block) + public struct Array : IDisposable { - blockArray[index].Initialized = true; - blockArray[index].Bx = bx; - blockArray[index].By = by; - blockArray[index].Block = block; + /// + /// The used to pool data in . + /// Should always clean arrays when returning! + /// + private static readonly ArrayPool ArrayPool = ArrayPool.Create(); + + /// + /// Initializes a new instance of the struct. Rents a buffer. + /// + /// The number of valid -s + public Array(int count) + { + this.Count = count; + this.Buffer = ArrayPool.Rent(count); + } + + /// + /// Gets the number of actual -s inside + /// + public int Count { get; } + + /// + /// Gets the rented buffer. + /// + public DecodedBlockMemento[] Buffer { get; private set; } + + /// + /// Returns the rented buffer to the pool. + /// + public void Dispose() + { + if (this.Buffer != null) + { + ArrayPool.Return(this.Buffer, true); + this.Buffer = null; + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 2d292ddf93..0e389771c0 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -196,7 +196,7 @@ namespace ImageSharp.Formats.Jpg // Take an existing block (required when progressive): int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.ComponentIndex][blockIndex].Block; + this.data.Block = decoder.DecodedBlocks[this.ComponentIndex].Buffer[blockIndex].Block; if (!decoder.InputProcessor.UnexpectedEndOfStreamReached) { @@ -204,8 +204,8 @@ namespace ImageSharp.Formats.Jpg } // Store the decoded block - DecodedBlockMemento[] blocks = decoder.DecodedBlocks[this.ComponentIndex]; - DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref this.data.Block); + DecodedBlockMemento.Array blocks = decoder.DecodedBlocks[this.ComponentIndex]; + DecodedBlockMemento.Store(ref blocks, blockIndex, this.bx, this.by, ref this.data.Block); } // for j diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 127b399ab4..91fca3eb68 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -87,7 +87,7 @@ namespace ImageSharp.Formats this.QuantizationTables = new Block8x8F[MaxTq + 1]; this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.ComponentArray = new Component[MaxComponents]; - this.DecodedBlocks = new DecodedBlockMemento[MaxComponents][]; + this.DecodedBlocks = new DecodedBlockMemento.Array[MaxComponents]; } /// @@ -104,7 +104,7 @@ namespace ImageSharp.Formats /// Gets the saved state between progressive-mode scans. /// TODO: Also save non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop) /// - public DecodedBlockMemento[][] DecodedBlocks { get; } + public DecodedBlockMemento.Array[] DecodedBlocks { get; } /// /// Gets the quantization tables, in zigzag order. @@ -176,7 +176,7 @@ namespace ImageSharp.Formats this.ProcessStream(image, stream, metadataOnly); if (!metadataOnly) { - this.ProcessBlockColorsIntoJpegImageChannels(); + this.ProcessBlocksIntoJpegImageChannels(); this.ConvertJpegPixelsToImagePixels(image); } } @@ -191,12 +191,9 @@ namespace ImageSharp.Formats this.HuffmanTrees[i].Dispose(); } - foreach (DecodedBlockMemento[] blockArray in this.DecodedBlocks) + foreach (DecodedBlockMemento.Array blockArray in this.DecodedBlocks) { - if (blockArray != null) - { - DecodedBlockMemento.ReturnArray(blockArray); - } + blockArray.Dispose(); } this.ycbcrImage?.Dispose(); @@ -464,9 +461,11 @@ namespace ImageSharp.Formats /// /// Process the blocks in into Jpeg image channels ( and ) + /// The blocks are expected in a "raw" frequency-domain decoded format. We need to apply IDCT and unzigging to transform them into color-space blocks. + /// We can copy these blocks into -s afterwards. /// /// The pixel type - private void ProcessBlockColorsIntoJpegImageChannels() + private void ProcessBlocksIntoJpegImageChannels() where TColor : struct, IPackedPixel, IEquatable { Parallel.For( @@ -478,10 +477,10 @@ namespace ImageSharp.Formats JpegScanDecoder.Init(&scanDecoder); scanDecoder.ComponentIndex = componentIndex; - DecodedBlockMemento[] blockArray = this.DecodedBlocks[componentIndex]; - for (int i = 0; i < blockArray.Length; i++) + DecodedBlockMemento.Array blockArray = this.DecodedBlocks[componentIndex]; + for (int i = 0; i < blockArray.Count; i++) { - scanDecoder.LoadMemento(ref blockArray[i]); + scanDecoder.LoadMemento(ref blockArray.Buffer[i]); scanDecoder.ProcessBlockColors(this); } }); @@ -1315,9 +1314,9 @@ namespace ImageSharp.Formats // As a preparation for parallelizing Scan decoder, we also allocate DecodedBlocks in the non-progressive case! for (int i = 0; i < this.ComponentCount; i++) { - int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor + int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor * this.ComponentArray[i].VerticalFactor; - this.DecodedBlocks[i] = DecodedBlockMemento.RentArray(size); + this.DecodedBlocks[i] = new DecodedBlockMemento.Array(count); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 5776bf2e45..12ebee3e5c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -7,6 +7,9 @@ namespace ImageSharp.Tests { using System; + using System.IO; + + using ImageSharp.Formats; using Xunit; @@ -30,7 +33,7 @@ namespace ImageSharp.Tests provider.Utility.SaveTestOutputFile(image, "bmp"); } - + [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)] public void OpenProgressiveJpeg_SaveBmp(TestImageProvider provider) @@ -40,5 +43,34 @@ namespace ImageSharp.Tests provider.Utility.SaveTestOutputFile(image, "bmp"); } + + [Theory] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 75)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 100)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 75)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] + [WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] + public void DecodeGenerated_SaveBmp(TestImageProvider provider, JpegSubsample subsample, int qulaity) + where TColor : struct, IPackedPixel, IEquatable + { + Image image = provider.GetImage(); + + JpegEncoder encoder = new JpegEncoder() + { + Subsample = subsample, + Quality = qulaity + }; + + byte[] data = new byte[65536]; + using (MemoryStream ms = new MemoryStream(data)) + { + image.Save(ms, encoder); + } + + // TODO: Automatic image comparers could help here a lot :P + Image mirror = provider.Factory.CreateImage(data); + provider.Utility.TestName += $"_{subsample}_Q{qulaity}"; + provider.Utility.SaveTestOutputFile(mirror, "bmp"); + } } } \ No newline at end of file From de9194c5acf3e26303c48e5554fe4933e2021b0c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 28 Jan 2017 03:22:13 +0100 Subject: [PATCH 25/26] "covered" the metadataOnly == true case --- .../Formats/Jpg/JpegDecoderTests.cs | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 12ebee3e5c..01ffa2eb0c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -43,23 +43,22 @@ namespace ImageSharp.Tests provider.Utility.SaveTestOutputFile(image, "bmp"); } - + [Theory] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 75)] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 100)] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 75)] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] [WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] - public void DecodeGenerated_SaveBmp(TestImageProvider provider, JpegSubsample subsample, int qulaity) + public void DecodeGenerated_SaveBmp( + TestImageProvider provider, + JpegSubsample subsample, + int quality) where TColor : struct, IPackedPixel, IEquatable { Image image = provider.GetImage(); - JpegEncoder encoder = new JpegEncoder() - { - Subsample = subsample, - Quality = qulaity - }; + JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality }; byte[] data = new byte[65536]; using (MemoryStream ms = new MemoryStream(data)) @@ -69,8 +68,32 @@ namespace ImageSharp.Tests // TODO: Automatic image comparers could help here a lot :P Image mirror = provider.Factory.CreateImage(data); - provider.Utility.TestName += $"_{subsample}_Q{qulaity}"; + provider.Utility.TestName += $"_{subsample}_Q{quality}"; provider.Utility.SaveTestOutputFile(mirror, "bmp"); } + + [Theory] + [WithSolidFilledImages(42, 88, 255, 0, 0, PixelTypes.StandardImageClass)] + public void DecodeGenerated_MetadataOnly( + TestImageProvider provider) + where TColor : struct, IPackedPixel, IEquatable + { + Image image = provider.GetImage(); + + using (MemoryStream ms = new MemoryStream()) + { + image.Save(ms, new JpegEncoder()); + ms.Seek(0, SeekOrigin.Begin); + + Image mirror = provider.Factory.CreateImage(1, 1); + using (JpegDecoderCore decoder = new JpegDecoderCore()) + { + decoder.Decode(mirror, ms, true); + + Assert.Equal(decoder.ImageWidth, image.Width); + Assert.Equal(decoder.ImageHeight, image.Height); + } + } + } } } \ No newline at end of file From c8517dbaf624d66b60eeae4e9898226b8659134b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 28 Jan 2017 03:24:12 +0100 Subject: [PATCH 26/26] minor comment fix --- src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs index 91fca3eb68..eca4d46229 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -163,7 +163,7 @@ namespace ImageSharp.Formats public int TotalMCUCount => this.MCUCountX * this.MCUCountY; /// - /// Decodes the image from the specified this._stream and sets + /// Decodes the image from the specified and sets /// the data to image. /// /// The pixel format.