diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs index 88aa8a3fe..8e46e1806 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 b91420b42..e33f852ff 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 000000000..4adc9fa7f --- /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 000000000..33321bff9 --- /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 000000000..81857b456 --- /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 9fef5010d..8c2f07970 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 000000000..f8c157237 --- /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 707f9d3e4..3bb774b69 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 94ae9a2b1..628bc4ea9 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