From 86c5832d6bd9aaa2bcd1c80d2b61e499ccbbc0d6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 22 Jan 2017 20:25:41 +0100 Subject: [PATCH] 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 f80e75b99..ab08b0d4c 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 e33f852ff..176e0cfbe 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 4adc9fa7f..8b82184fa 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 33321bff9..18ba02a9a 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 b695e6812..5ed25ef04 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 b9f1dc77b..5571ba707 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 f2abce6e7..eb9747cce 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)]