diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs index da7bb52a94..73e597ccd5 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs @@ -35,6 +35,8 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private int successiveACNextValue; + private bool endOfStreamReached; + /// /// Decodes the spectral scan /// @@ -50,8 +52,7 @@ namespace ImageSharp.Formats.Jpeg.Port.Components /// The spectral selection end /// The successive approximation bit high end /// The successive approximation bit low end - /// The representing the processed length in bytes - public long DecodeScan( + public void DecodeScan( Frame frame, Stream stream, HuffmanTables dcHuffmanTables, @@ -69,9 +70,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components this.specStart = spectralStart; this.specEnd = spectralEnd; this.successiveState = successive; + this.endOfStreamReached = false; + bool progressive = frame.Progressive; int mcusPerLine = frame.McusPerLine; - long startPosition = stream.Position; int mcu = 0; int mcuExpected; @@ -129,7 +131,6 @@ namespace ImageSharp.Formats.Jpeg.Port.Components // Find marker this.bitsCount = 0; - long position = stream.Position; fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past @@ -137,20 +138,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (fileMarker.Invalid) { #if DEBUG - Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker}"); - + Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); #endif - // stream.Position = fileMarker.Position; } ushort marker = fileMarker.Marker; - // if (marker <= 0xFF00) - // { - // throw new ImageFormatException("Marker was not found"); - // } - - // RSTn We've alread read the bytes and altered the position so no need to skip + // RSTn - We've alread read the bytes and altered the position so no need to skip if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) { continue; @@ -159,13 +153,10 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (!fileMarker.Invalid) { // We've found a valid marker. - // Rewind the stream to the position of the marker and beak + // Rewind the stream to the position of the marker and break stream.Position = fileMarker.Position; break; } - - // Rewind the stream - stream.Position = position; } fileMarker = JpegDecoderCore.FindNextFileMarkerNew(stream); @@ -175,12 +166,15 @@ namespace ImageSharp.Formats.Jpeg.Port.Components if (fileMarker.Invalid) { #if DEBUG - Debug.WriteLine($"DecodeScan - Unexpected MCU data, next marker is: {fileMarker.Marker}"); + Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); #endif + } + else + { + // We've found a valid marker. + // Rewind the stream to the position of the marker stream.Position = fileMarker.Position; } - - return stream.Position - startPosition; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -205,6 +199,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcu, stream); mcu++; } @@ -222,6 +221,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuBaseline(dcHuffmanTables, acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -247,6 +251,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockDCFirst(dcHuffmanTables, ref component, mcu, stream); mcu++; } @@ -264,6 +273,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuDCFirst(dcHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -288,6 +302,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockDCSuccessive(ref component, mcu, stream); mcu++; } @@ -305,6 +324,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuDCSuccessive(ref component, mcusPerLine, mcu, j, k, stream); } } @@ -330,6 +354,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockACFirst(acHuffmanTables, ref component, mcu, stream); mcu++; } @@ -347,6 +376,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuACFirst(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -372,6 +406,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components ref FrameComponent component = ref components[this.compIndex]; for (int n = 0; n < mcuToRead; n++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeBlockACSuccessive(acHuffmanTables, ref component, mcu, stream); mcu++; } @@ -389,6 +428,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { for (int k = 0; k < h; k++) { + if (this.endOfStreamReached) + { + continue; + } + this.DecodeMcuACSuccessive(acHuffmanTables, ref component, mcusPerLine, mcu, j, k, stream); } } @@ -509,6 +553,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components } this.bitsData = stream.ReadByte(); + + if (this.bitsData == -0x1) + { + // We've encountered the end of the file stream which means there's no EOI marker in the image + this.endOfStreamReached = true; + } + if (this.bitsData == 0xFF) { int nextByte = stream.ReadByte(); @@ -527,10 +578,16 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private short DecodeHuffman(HuffmanBranch[] tree, Stream stream) { + // TODO: This is our bottleneck. We should use a faster algorithm with a LUT. HuffmanBranch[] node = tree; while (true) { int index = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return -1; + } + HuffmanBranch branch = node[index]; if (branch.Value > -1) @@ -548,7 +605,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components int n = 0; while (length > 0) { - n = (n << 1) | this.ReadBit(stream); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return -1; + } + + n = (n << 1) | bit; length--; } @@ -576,6 +639,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private void DecodeBaseline(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); component.BlockData[offset] = (short)(component.Pred += diff); @@ -583,6 +651,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (k < 64) { int rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int s = rs & 15; int r = rs >> 4; @@ -609,6 +682,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components private void DecodeDCFirst(ref FrameComponent component, int offset, HuffmanTables dcHuffmanTables, Stream stream) { int t = this.DecodeHuffman(dcHuffmanTables[component.DCHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; component.BlockData[offset] = (short)(component.Pred += diff); } @@ -616,7 +694,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeDCSuccessive(ref FrameComponent component, int offset, Stream stream) { - component.BlockData[offset] |= (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset] |= (short)(bit << this.successiveState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -633,6 +717,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components while (k <= e) { short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int s = rs & 15; int r = rs >> 4; @@ -668,6 +757,11 @@ namespace ImageSharp.Formats.Jpeg.Port.Components { case 0: // Initial state short rs = this.DecodeHuffman(acHuffmanTables[component.ACHuffmanTableId], stream); + if (this.endOfStreamReached) + { + return; + } + int s = rs & 15; r = rs >> 4; if (s == 0) @@ -699,7 +793,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components case 2: if (component.BlockData[offset + z] != 0) { - component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset + z] += (short)(bit << this.successiveState); } else { @@ -714,7 +814,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components case 3: // Set value for a zero item if (component.BlockData[offset + z] != 0) { - component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset + z] += (short)(bit << this.successiveState); } else { @@ -726,7 +832,13 @@ namespace ImageSharp.Formats.Jpeg.Port.Components case 4: // Eob if (component.BlockData[offset + z] != 0) { - component.BlockData[offset + z] += (short)(this.ReadBit(stream) << this.successiveState); + int bit = this.ReadBit(stream); + if (this.endOfStreamReached) + { + return; + } + + component.BlockData[offset + z] += (short)(bit << this.successiveState); } break; diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs index 2c2cd57a5e..152a2b43a2 100644 --- a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs @@ -720,7 +720,7 @@ namespace ImageSharp.Formats.Jpeg.Port int successiveApproximation = this.temp[2]; var scanDecoder = default(ScanDecoder); - long position = scanDecoder.DecodeScan( + scanDecoder.DecodeScan( this.frame, this.InputStream, this.dcHuffmanTables, @@ -733,20 +733,6 @@ namespace ImageSharp.Formats.Jpeg.Port spectralEnd, successiveApproximation >> 4, successiveApproximation & 15); - - this.InputStream.Position += position; - - Debug.WriteLine("spectralStart= " + spectralStart); - Debug.WriteLine("spectralEnd= " + spectralEnd); - Debug.WriteLine("successiveApproximation= " + successiveApproximation); - Debug.WriteLine("Components after"); - //for (int i = 0; i < 3; i++) - //{ - // for (int j = 0; j < 10; j++) - // { - // Debug.WriteLine("component [" + i + "] : value [" + j + "] =" + this.frame.Components[i].BlockData[j] + "]"); - // } - //} } ///