From dcb10179342e5ec324d723f156c777b3ff793fc2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 24 Jan 2017 01:55:24 +0100 Subject: [PATCH] BufferProcessor --- .../Components/Decoder/Bits.cs | 41 +- .../Components/Decoder/BufferProcessor.cs | 350 +++++++++++++++++ .../Components/Decoder/HuffmanTree.cs | 6 +- .../Components/Decoder/JpegScanDecoder.cs | 50 +-- .../JpegDecoderCore.cs | 356 +++--------------- .../ImageSharp.Sandbox46.csproj | 5 - 6 files changed, 450 insertions(+), 358 deletions(-) create mode 100644 src/ImageSharp.Formats.Jpeg/Components/Decoder/BufferProcessor.cs diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs index 05b49f57c..21d4f8c43 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Formats.Jpg { - using System; using System.Runtime.CompilerServices; /// @@ -37,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. - /// Jpeg decoder + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EnsureNBits(int n, JpegDecoderCore decoder) + public void EnsureNBits(int n, ref BufferProcessor bufferProcessor) { - DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, decoder); + DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref bufferProcessor); errorCode.EnsureNoError(); } @@ -52,13 +51,13 @@ namespace ImageSharp.Formats.Jpg /// This method does not throw. Returns instead. /// /// The number of bits to ensure. - /// Jpeg decoder + /// The /// Error code - public DecoderErrorCode EnsureNBitsUnsafe(int n, JpegDecoderCore decoder) + public DecoderErrorCode EnsureNBitsUnsafe(int n, ref BufferProcessor bufferProcessor) { while (true) { - DecoderErrorCode errorCode = this.EnsureBitsStepImpl(decoder); + DecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref bufferProcessor); if (errorCode != DecoderErrorCode.NoError || this.UnreadBits >= n) { return errorCode; @@ -69,34 +68,34 @@ namespace ImageSharp.Formats.Jpg /// /// Unrolled version of for n==8 /// - /// The + /// The /// A - public DecoderErrorCode Ensure8BitsUnsafe(JpegDecoderCore decoder) + public DecoderErrorCode Ensure8BitsUnsafe(ref BufferProcessor bufferProcessor) { - return this.EnsureBitsStepImpl(decoder); + return this.EnsureBitsStepImpl(ref bufferProcessor); } /// /// Unrolled version of for n==1 /// - /// The + /// The /// A - public DecoderErrorCode Ensure1BitUnsafe(JpegDecoderCore decoder) + public DecoderErrorCode Ensure1BitUnsafe(ref BufferProcessor bufferProcessor) { - return this.EnsureBitsStepImpl(decoder); + return this.EnsureBitsStepImpl(ref bufferProcessor); } /// /// Receive extend /// /// Byte - /// Jpeg decoder + /// The /// Read bits value [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReceiveExtend(int t, JpegDecoderCore decoder) + public int ReceiveExtend(int t, ref BufferProcessor bufferProcessor) { int x; - DecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, decoder, out x); + DecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref bufferProcessor, out x); errorCode.EnsureNoError(); return x; } @@ -105,14 +104,14 @@ namespace ImageSharp.Formats.Jpg /// Receive extend /// /// Byte - /// Jpeg decoder + /// The /// Read bits value /// The - public DecoderErrorCode ReceiveExtendUnsafe(int t, JpegDecoderCore decoder, out int x) + public DecoderErrorCode ReceiveExtendUnsafe(int t, ref BufferProcessor bufferProcessor, out int x) { if (this.UnreadBits < t) { - DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, decoder); + DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref bufferProcessor); if (errorCode != DecoderErrorCode.NoError) { x = int.MaxValue; @@ -133,10 +132,10 @@ namespace ImageSharp.Formats.Jpg return DecoderErrorCode.NoError; } - private DecoderErrorCode EnsureBitsStepImpl(JpegDecoderCore decoder) + private DecoderErrorCode EnsureBitsStepImpl(ref BufferProcessor bufferProcessor) { int c; - DecoderErrorCode errorCode = decoder.Bytes.ReadByteStuffedByteUnsafe(decoder.InputStream, out c); + DecoderErrorCode errorCode = bufferProcessor.Bytes.ReadByteStuffedByteUnsafe(bufferProcessor.InputStream, out c); if (errorCode != DecoderErrorCode.NoError) { diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/BufferProcessor.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/BufferProcessor.cs new file mode 100644 index 000000000..a8ceb7080 --- /dev/null +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/BufferProcessor.cs @@ -0,0 +1,350 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Formats.Jpg +{ + using System; + using System.IO; + using System.Runtime.CompilerServices; + + /// + /// 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 + { + /// + /// Holds the unprocessed bits that have been taken from the byte-stream. + /// + public Bits Bits; + + /// + /// The byte buffer + /// + public Bytes Bytes; + + /// + /// Initializes a new instance of the struct. + /// + /// The input + /// Temporal buffer, same as + public BufferProcessor(Stream inputStream, byte[] temp) + { + this.Bits = default(Bits); + this.Bytes = Bytes.Create(); + this.InputStream = inputStream; + this.Temp = temp; + this.UnexpectedEndOfStreamReached = false; + } + + /// + /// Gets the input stream + /// + public Stream InputStream { get; } + + /// + /// Gets the temporal buffer, same instance as + /// + public byte[] Temp { get; } + + /// + /// Gets or sets the value indicating whether an unexpected EOF reached in . + /// + public bool UnexpectedEndOfStreamReached { get; set; } + + /// + /// If errorCode indicates unexpected EOF, sets to true and returns false. + /// Calls and returns true otherwise. + /// + /// + /// + public bool CheckEOFEnsureNoError(DecoderErrorCode errorCode) + { + if (errorCode == DecoderErrorCode.UnexpectedEndOfStream) + { + this.UnexpectedEndOfStreamReached = true; + return false; + } + errorCode.EnsureNoError(); + return true; + } + + /// + /// Dispose + /// + public void Dispose() + { + this.Bytes.Dispose(); + } + + /// + /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte() + { + return this.Bytes.ReadByte(this.InputStream); + } + + /// + /// Decodes a single bit + /// TODO: This method (and also the usages) could be optimized by batching! + /// + /// The decoded bit as a + /// The + public DecoderErrorCode DecodeBitUnsafe(out bool result) + { + if (this.Bits.UnreadBits == 0) + { + DecoderErrorCode errorCode = this.Bits.Ensure1BitUnsafe(ref this); + if (errorCode != DecoderErrorCode.NoError) + { + result = false; + return errorCode; + } + } + + result = (this.Bits.Accumulator & this.Bits.Mask) != 0; + this.Bits.UnreadBits--; + this.Bits.Mask >>= 1; + return DecoderErrorCode.NoError; + } + + /// + /// 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) + { + if (this.Bits.UnreadBits >= 8) + { + this.UnreadByteStuffedByte(); + } + + this.Bytes.UnreadableBytes = 0; + } + + DecoderErrorCode errorCode = DecoderErrorCode.NoError; + while (length > 0) + { + if (this.Bytes.J - this.Bytes.I >= length) + { + Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, length); + this.Bytes.I += length; + length -= length; + } + else + { + Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, this.Bytes.J - this.Bytes.I); + offset += this.Bytes.J - this.Bytes.I; + length -= this.Bytes.J - this.Bytes.I; + this.Bytes.I += this.Bytes.J - this.Bytes.I; + + errorCode = this.Bytes.FillUnsafe(this.InputStream); + } + } + + return errorCode; + } + + /// + /// Decodes the given number of bits + /// + /// The number of bits to decode. + /// The result + /// The + public DecoderErrorCode DecodeBitsUnsafe(int count, out int result) + { + if (this.Bits.UnreadBits < count) + { + this.Bits.EnsureNBits(count, ref this); + } + + result = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); + result = result & ((1 << count) - 1); + this.Bits.UnreadBits -= count; + this.Bits.Mask >>= count; + return DecoderErrorCode.NoError; + } + + /// + /// Extracts the next Huffman-coded value from the bit-stream into result, decoded according to the given value. + /// + /// The huffman value + /// The decoded + /// The + public DecoderErrorCode DecodeHuffmanUnsafe(ref HuffmanTree huffmanTree, out int result) + { + result = 0; + + if (huffmanTree.Length == 0) + { + DecoderThrowHelper.ThrowImageFormatException.UninitializedHuffmanTable(); + } + + if (this.Bits.UnreadBits < 8) + { + DecoderErrorCode errorCode = this.Bits.Ensure8BitsUnsafe(ref this); + + if (errorCode == DecoderErrorCode.NoError) + { + int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSizeLog2)) & 0xFF; + int v = huffmanTree.Lut[lutIndex]; + + if (v != 0) + { + int n = (v & 0xFF) - 1; + this.Bits.UnreadBits -= n; + this.Bits.Mask >>= n; + result = v >> 8; + return errorCode; + } + } + else + { + this.UnreadByteStuffedByte(); + return errorCode; + } + } + + int code = 0; + for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) + { + if (this.Bits.UnreadBits == 0) + { + this.Bits.EnsureNBits(1, ref this); + } + + if ((this.Bits.Accumulator & this.Bits.Mask) != 0) + { + code |= 1; + } + + this.Bits.UnreadBits--; + this.Bits.Mask >>= 1; + + if (code <= huffmanTree.MaxCodes[i]) + { + result = huffmanTree.GetValue(code, i); + return DecoderErrorCode.NoError; + } + + code <<= 1; + } + + // Unrecoverable error, throwing: + DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode(); + + // DUMMY RETURN! C# doesn't know we have thrown an exception! + return DecoderErrorCode.NoError; + } + + /// + /// Skips the next n bytes. + /// + /// The number of bytes to ignore. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public 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 + public DecoderErrorCode SkipUnsafe(int count) + { + // Unread the overshot bytes, if any. + if (this.Bytes.UnreadableBytes != 0) + { + if (this.Bits.UnreadBits >= 8) + { + this.UnreadByteStuffedByte(); + } + + this.Bytes.UnreadableBytes = 0; + } + + while (true) + { + int m = this.Bytes.J - this.Bytes.I; + if (m > count) + { + m = count; + } + + this.Bytes.I += m; + count -= m; + if (count == 0) + { + break; + } + + DecoderErrorCode errorCode = this.Bytes.FillUnsafe(this.InputStream); + if (errorCode != DecoderErrorCode.NoError) + { + return errorCode; + } + } + + return DecoderErrorCode.NoError; + } + + /// + /// Reads exactly length bytes into data. It does not care about byte stuffing. + /// + /// 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(); + } + + /// + /// Undoes the most recent ReadByteStuffedByte call, + /// giving a byte of data back from bits to bytes. The Huffman look-up table + /// requires at least 8 bits for look-up, which means that Huffman decoding can + /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot + /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte. + /// + public void UnreadByteStuffedByte() + { + this.Bytes.I -= this.Bytes.UnreadableBytes; + this.Bytes.UnreadableBytes = 0; + if (this.Bits.UnreadBits >= 8) + { + this.Bits.Accumulator >>= 8; + this.Bits.UnreadBits -= 8; + this.Bits.Mask >>= 8; + } + } + + /// + /// Receive extend + /// + /// Byte + /// Read bits value + /// The + public DecoderErrorCode ReceiveExtendUnsafe(int t, out int x) + { + return this.Bits.ReceiveExtendUnsafe(t, ref this, out x); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs index b563d2652..cd8781a5f 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( - JpegDecoderCore decoder, + ref BufferProcessor bufferProcessor, byte[] defineHuffmanTablesData, ref int remaining) { @@ -163,7 +163,7 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("DHT has wrong length"); } - decoder.ReadFull(this.Values, 0, this.Length); + bufferProcessor.ReadFull(this.Values, 0, this.Length); for (int i = 0; i < this.Values.Length; i++) { diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs index 6a3072f8e..7968491f9 100644 --- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -194,7 +194,15 @@ 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.DecodeBlock(decoder, scanIndex); + + // Store the decoded block + DecodedBlockMemento[] blocks = decoder.DecodedBlocks[this.ComponentIndex]; + DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref this.data.Block); } // for j @@ -207,7 +215,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); + decoder.BufferProcessor.ReadFull(decoder.Temp, 0, 2); if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) { @@ -221,7 +229,7 @@ namespace ImageSharp.Formats.Jpg } // Reset the Huffman decoder. - decoder.Bits = default(Bits); + decoder.BufferProcessor.Bits = default(Bits); // Reset the DC components, as per section F.2.1.3.1. this.ResetDc(); @@ -277,7 +285,7 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("SOS has wrong length"); } - decoder.ReadFull(decoder.Temp, 0, remaining); + decoder.BufferProcessor.ReadFull(decoder.Temp, 0, remaining); this.componentScanCount = decoder.Temp[0]; int scanComponentCountX2 = 2 * this.componentScanCount; @@ -334,15 +342,12 @@ namespace ImageSharp.Formats.Jpg /// The index of the scan private void DecodeBlock(JpegDecoderCore decoder, int scanIndex) { - int blockIndex = this.GetBlockIndex(decoder); - this.data.Block = decoder.DecodedBlocks[this.ComponentIndex][blockIndex].Block; - var b = this.pointers.Block; DecoderErrorCode errorCode; int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { - this.Refine(decoder, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); + this.Refine(ref decoder.BufferProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); } else { @@ -354,7 +359,7 @@ 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.DecodeHuffmanUnsafe( + errorCode = decoder.BufferProcessor.DecodeHuffmanUnsafe( ref decoder.HuffmanTrees[huffmanIndex], out value); errorCode.EnsureNoEOF(); @@ -364,7 +369,9 @@ namespace ImageSharp.Formats.Jpg throw new ImageFormatException("Excessive DC component"); } - int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); + int deltaDC; + errorCode = decoder.BufferProcessor.ReceiveExtendUnsafe(value, out deltaDC); + errorCode.EnsureNoError(); this.pointers.Dc[this.ComponentIndex] += deltaDC; @@ -382,7 +389,7 @@ namespace ImageSharp.Formats.Jpg for (; zig <= this.zigEnd; zig++) { int value; - errorCode = decoder.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value); + errorCode = decoder.BufferProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value); errorCode.EnsureNoEOF(); int val0 = value >> 4; @@ -395,7 +402,9 @@ namespace ImageSharp.Formats.Jpg break; } - int ac = decoder.Bits.ReceiveExtend(val1, decoder); + int ac; + errorCode = decoder.BufferProcessor.ReceiveExtendUnsafe(val1, out ac); + errorCode.EnsureNoError(); // b[Unzig[zig]] = ac << al; Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al); @@ -407,7 +416,7 @@ namespace ImageSharp.Formats.Jpg this.eobRun = (ushort)(1 << val0); if (val0 != 0) { - errorCode = this.DecodeEobRun(val0, decoder); + errorCode = this.DecodeEobRun(val0, ref decoder.BufferProcessor); errorCode.EnsureNoError(); } @@ -419,13 +428,10 @@ namespace ImageSharp.Formats.Jpg } } } - } - - DecodedBlockMemento[] blocks = decoder.DecodedBlocks[this.ComponentIndex]; - DecodedBlockMemento.Store(blocks, blockIndex, this.bx, this.by, ref *b); + } } - private DecoderErrorCode DecodeEobRun(int count, JpegDecoderCore decoder) + private DecoderErrorCode DecodeEobRun(int count, ref BufferProcessor decoder) { int bitsResult; DecoderErrorCode errorCode = decoder.DecodeBitsUnsafe(count, out bitsResult); @@ -513,7 +519,7 @@ namespace ImageSharp.Formats.Jpg /// The decoder instance /// The Huffman tree /// The low transform offset - private void Refine(JpegDecoderCore decoder, ref HuffmanTree h, int delta) + private void Refine(ref BufferProcessor decoder, ref HuffmanTree h, int delta) { Block8x8F* b = this.pointers.Block; @@ -566,7 +572,7 @@ namespace ImageSharp.Formats.Jpg this.eobRun = 1 << val0; if (val0 != 0) { - errorCode = this.DecodeEobRun(val0, decoder); + errorCode = this.DecodeEobRun(val0, ref decoder); errorCode.EnsureNoError(); } @@ -596,7 +602,7 @@ namespace ImageSharp.Formats.Jpg break; } - zig = this.RefineNonZeroes(decoder, zig, val0, delta); + zig = this.RefineNonZeroes(ref decoder, zig, val0, delta); if (zig > this.zigEnd) { throw new ImageFormatException($"Too many coefficients {zig} > {this.zigEnd}"); @@ -613,7 +619,7 @@ namespace ImageSharp.Formats.Jpg if (this.eobRun > 0) { this.eobRun--; - this.RefineNonZeroes(decoder, zig, -1, delta); + this.RefineNonZeroes(ref decoder, zig, -1, delta); } } @@ -626,7 +632,7 @@ namespace ImageSharp.Formats.Jpg /// The non-zero entry /// The low transform offset /// The - private int RefineNonZeroes(JpegDecoderCore decoder, int zig, int nz, int delta) + private int RefineNonZeroes(ref BufferProcessor decoder, 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 e79711dd4..6e2825447 100644 --- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs @@ -30,15 +30,12 @@ namespace ImageSharp.Formats // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P #pragma warning disable SA1401 // FieldsMustBePrivate - /// - /// Holds the unprocessed bits that have been taken from the byte-stream. - /// - public Bits Bits; /// - /// The byte buffer. + /// 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 Bytes Bytes; + public BufferProcessor BufferProcessor; #pragma warning restore SA401 /// @@ -91,8 +88,6 @@ namespace ImageSharp.Formats this.Temp = new byte[2 * Block8x8F.ScalarCount]; this.ComponentArray = new Component[MaxComponents]; this.DecodedBlocks = new DecodedBlockMemento[MaxComponents][]; - this.Bits = default(Bits); - this.Bytes = Bytes.Create(); } /// @@ -205,194 +200,11 @@ namespace ImageSharp.Formats } this.ycbcrImage?.Dispose(); - this.Bytes.Dispose(); + this.BufferProcessor.Dispose(); this.grayImage.ReturnPooled(); this.blackImage.ReturnPooled(); } - /// - /// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing. - /// - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte() - { - return this.Bytes.ReadByte(this.InputStream); - } - - /// - /// Decodes a single bit - /// TODO: This method (and also the usages) could be optimized by batching! - /// - /// The decoded bit as a - /// The - public DecoderErrorCode DecodeBitUnsafe(out bool result) - { - if (this.Bits.UnreadBits == 0) - { - DecoderErrorCode errorCode = this.Bits.Ensure1BitUnsafe(this); - if (errorCode != DecoderErrorCode.NoError) - { - result = false; - return errorCode; - } - } - - result = (this.Bits.Accumulator & this.Bits.Mask) != 0; - this.Bits.UnreadBits--; - this.Bits.Mask >>= 1; - return DecoderErrorCode.NoError; - } - - /// - /// Reads exactly length bytes into data. It does not care about byte stuffing. - /// - /// 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) - { - if (this.Bits.UnreadBits >= 8) - { - this.UnreadByteStuffedByte(); - } - - this.Bytes.UnreadableBytes = 0; - } - - DecoderErrorCode errorCode = DecoderErrorCode.NoError; - while (length > 0) - { - if (this.Bytes.J - this.Bytes.I >= length) - { - Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, length); - this.Bytes.I += length; - length -= length; - } - else - { - Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, this.Bytes.J - this.Bytes.I); - offset += this.Bytes.J - this.Bytes.I; - length -= this.Bytes.J - this.Bytes.I; - this.Bytes.I += this.Bytes.J - this.Bytes.I; - - errorCode = this.Bytes.FillUnsafe(this.InputStream); - } - } - - return errorCode; - } - - /// - /// Decodes the given number of bits - /// - /// The number of bits to decode. - /// The result - /// The - public DecoderErrorCode DecodeBitsUnsafe(int count, out int result) - { - if (this.Bits.UnreadBits < count) - { - this.Bits.EnsureNBits(count, this); - } - - result = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); - result = result & ((1 << count) - 1); - this.Bits.UnreadBits -= count; - this.Bits.Mask >>= count; - return DecoderErrorCode.NoError; - } - - /// - /// Extracts the next Huffman-coded value from the bit-stream into result, decoded according to the given value. - /// - /// The huffman value - /// The decoded - /// The - public DecoderErrorCode DecodeHuffmanUnsafe(ref HuffmanTree huffmanTree, out int result) - { - result = 0; - - if (huffmanTree.Length == 0) - { - DecoderThrowHelper.ThrowImageFormatException.UninitializedHuffmanTable(); - } - - if (this.Bits.UnreadBits < 8) - { - DecoderErrorCode errorCode = this.Bits.Ensure8BitsUnsafe(this); - - if (errorCode == DecoderErrorCode.NoError) - { - int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSizeLog2)) & 0xFF; - int v = huffmanTree.Lut[lutIndex]; - - if (v != 0) - { - int n = (v & 0xFF) - 1; - this.Bits.UnreadBits -= n; - this.Bits.Mask >>= n; - result = v >> 8; - return errorCode; - } - } - else - { - this.UnreadByteStuffedByte(); - return errorCode; - } - } - - int code = 0; - for (int i = 0; i < HuffmanTree.MaxCodeLength; i++) - { - if (this.Bits.UnreadBits == 0) - { - this.Bits.EnsureNBits(1, this); - } - - if ((this.Bits.Accumulator & this.Bits.Mask) != 0) - { - code |= 1; - } - - this.Bits.UnreadBits--; - this.Bits.Mask >>= 1; - - if (code <= huffmanTree.MaxCodes[i]) - { - result = huffmanTree.GetValue(code, i); - return DecoderErrorCode.NoError; - } - - code <<= 1; - } - - // Unrecoverable error, throwing: - DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode(); - - // DUMMY RETURN! C# doesn't know we have thrown an exception! - return DecoderErrorCode.NoError; - } - /// /// Gets the representing the channel at a given component index /// @@ -462,9 +274,10 @@ namespace ImageSharp.Formats where TColor : struct, IPackedPixel, IEquatable { this.InputStream = stream; + this.BufferProcessor = new BufferProcessor(stream, this.Temp); // Check for the Start Of Image marker. - this.ReadFull(this.Temp, 0, 2); + this.BufferProcessor.ReadFull(this.Temp, 0, 2); if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI) { throw new ImageFormatException("Missing SOI marker."); @@ -476,7 +289,7 @@ namespace ImageSharp.Formats // we can't currently short circute progressive images so don't try. while (processBytes) { - this.ReadFull(this.Temp, 0, 2); + this.BufferProcessor.ReadFull(this.Temp, 0, 2); while (this.Temp[0] != 0xff) { // Strictly speaking, this is a format error. However, libjpeg is @@ -497,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.ReadByte(); + this.Temp[1] = this.BufferProcessor.ReadByte(); } byte marker = this.Temp[1]; @@ -511,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.ReadByte(); + marker = this.BufferProcessor.ReadByte(); } // End Of Image. @@ -533,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.ReadFull(this.Temp, 0, 2); + this.BufferProcessor.ReadFull(this.Temp, 0, 2); int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; if (remaining < 0) { @@ -556,7 +369,7 @@ namespace ImageSharp.Formats case JpegConstants.Markers.DHT: if (metadataOnly) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); } else { @@ -567,7 +380,7 @@ namespace ImageSharp.Formats case JpegConstants.Markers.DQT: if (metadataOnly) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); } else { @@ -594,7 +407,7 @@ namespace ImageSharp.Formats case JpegConstants.Markers.DRI: if (metadataOnly) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); } else { @@ -615,7 +428,7 @@ namespace ImageSharp.Formats if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15) || marker == JpegConstants.Markers.COM) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); } else if (marker < JpegConstants.Markers.SOF0) { @@ -632,6 +445,23 @@ namespace ImageSharp.Formats } } + /// + /// Processes the SOS (Start of scan marker). + /// + /// The remaining bytes in the segment block. + /// + /// Missing SOF Marker + /// SOS has wrong length + /// + private void ProcessStartOfScan(int remaining) + { + JpegScanDecoder scan = default(JpegScanDecoder); + JpegScanDecoder.InitStreamReading(&scan, this, remaining); + this.BufferProcessor.Bits = default(Bits); + this.MakeImage(); + scan.DecodeBlocks(this); + } + /// /// Process the blocks in into Jpeg image channels ( and ) /// @@ -1085,11 +915,11 @@ namespace ImageSharp.Formats { if (remaining < 12) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); return; } - this.ReadFull(this.Temp, 0, 12); + this.BufferProcessor.ReadFull(this.Temp, 0, 12); remaining -= 12; if (this.Temp[0] == 'A' && this.Temp[1] == 'd' && this.Temp[2] == 'o' && this.Temp[3] == 'b' @@ -1101,7 +931,7 @@ namespace ImageSharp.Formats if (remaining > 0) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); } } @@ -1116,12 +946,12 @@ namespace ImageSharp.Formats { if (remaining < 6) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); return; } byte[] profile = new byte[remaining]; - this.ReadFull(profile, 0, remaining); + this.BufferProcessor.ReadFull(profile, 0, remaining); if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0' && profile[5] == '\0') @@ -1138,11 +968,11 @@ namespace ImageSharp.Formats { if (remaining < 5) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); return; } - this.ReadFull(this.Temp, 0, 13); + this.BufferProcessor.ReadFull(this.Temp, 0, 13); remaining -= 13; // TODO: We should be using constants for this. @@ -1157,7 +987,7 @@ namespace ImageSharp.Formats if (remaining > 0) { - this.Skip(remaining); + this.BufferProcessor.Skip(remaining); } } @@ -1175,7 +1005,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("DHT has wrong length"); } - this.ReadFull(this.Temp, 0, 17); + this.BufferProcessor.ReadFull(this.Temp, 0, 17); int tc = this.Temp[0] >> 4; if (tc > HuffmanTree.MaxTc) @@ -1190,7 +1020,10 @@ namespace ImageSharp.Formats } int huffTreeIndex = (tc * HuffmanTree.ThRowSize) + th; - this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(this, this.Temp, ref remaining); + this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop( + ref this.BufferProcessor, + this.Temp, + ref remaining); } } @@ -1206,7 +1039,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("DRI has wrong length"); } - this.ReadFull(this.Temp, 0, remaining); + this.BufferProcessor.ReadFull(this.Temp, 0, remaining); this.RestartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1]; } @@ -1224,7 +1057,7 @@ namespace ImageSharp.Formats bool done = false; remaining--; - byte x = this.ReadByte(); + byte x = this.BufferProcessor.ReadByte(); int tq = x & 0x0F; if (tq > MaxTq) { @@ -1241,7 +1074,7 @@ namespace ImageSharp.Formats } remaining -= Block8x8F.ScalarCount; - this.ReadFull(this.Temp, 0, Block8x8F.ScalarCount); + this.BufferProcessor.ReadFull(this.Temp, 0, Block8x8F.ScalarCount); for (int i = 0; i < Block8x8F.ScalarCount; i++) { @@ -1257,7 +1090,7 @@ namespace ImageSharp.Formats } remaining -= 2 * Block8x8F.ScalarCount; - this.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount); + this.BufferProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount); for (int i = 0; i < Block8x8F.ScalarCount; i++) { @@ -1307,7 +1140,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Incorrect number of components"); } - this.ReadFull(this.Temp, 0, remaining); + this.BufferProcessor.ReadFull(this.Temp, 0, remaining); // We only support 8-bit precision. if (this.Temp[0] != 8) @@ -1484,96 +1317,5 @@ namespace ImageSharp.Formats this.DecodedBlocks[i] = DecodedBlockMemento.RentArray(size); } } - - /// - /// Processes the SOS (Start of scan marker). - /// - /// The remaining bytes in the segment block. - /// - /// Missing SOF Marker - /// SOS has wrong length - /// - private void ProcessStartOfScan(int remaining) - { - JpegScanDecoder scan = default(JpegScanDecoder); - JpegScanDecoder.InitStreamReading(&scan, this, remaining); - this.Bits = default(Bits); - this.MakeImage(); - scan.DecodeBlocks(this); - } - - /// - /// 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) - { - if (this.Bits.UnreadBits >= 8) - { - this.UnreadByteStuffedByte(); - } - - this.Bytes.UnreadableBytes = 0; - } - - while (true) - { - int m = this.Bytes.J - this.Bytes.I; - if (m > count) - { - m = count; - } - - this.Bytes.I += m; - count -= m; - if (count == 0) - { - break; - } - - DecoderErrorCode errorCode = this.Bytes.FillUnsafe(this.InputStream); - if (errorCode != DecoderErrorCode.NoError) - { - return errorCode; - } - } - - return DecoderErrorCode.NoError; - } - - /// - /// Undoes the most recent ReadByteStuffedByte call, - /// giving a byte of data back from bits to bytes. The Huffman look-up table - /// requires at least 8 bits for look-up, which means that Huffman decoding can - /// sometimes overshoot and read one or two too many bytes. Two-byte overshoot - /// can happen when expecting to read a 0xff 0x00 byte-stuffed byte. - /// - private void UnreadByteStuffedByte() - { - this.Bytes.I -= this.Bytes.UnreadableBytes; - this.Bytes.UnreadableBytes = 0; - if (this.Bits.UnreadBits >= 8) - { - this.Bits.Accumulator >>= 8; - this.Bits.UnreadBits -= 8; - this.Bits.Mask >>= 8; - } - } } } diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index c4c7fb042..305fac636 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -317,7 +317,6 @@ - @@ -329,10 +328,6 @@ - - - -