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 @@
-
-
-
-