diff --git a/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs b/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs
index 13475af09e..2b8c15ab3c 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs
@@ -6,6 +6,7 @@
namespace ImageSharp.Formats.Jpg
{
using System;
+ using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs
index 88aa8a3fe8..02f585be02 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs
@@ -17,13 +17,13 @@ namespace ImageSharp.Formats.Jpg
///
/// Gets or sets the accumulator.
///
- public uint Accumulator;
+ public int Accumulator;
///
/// Gets or sets the mask.
/// 0, with mask==0 when unreadbits==0.]]>
///
- public uint Mask;
+ public int Mask;
///
/// Gets or sets the number of unread bits in the accumulator.
@@ -36,72 +36,124 @@ 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
- /// Error code
+ /// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal JpegDecoderCore.ErrorCodes EnsureNBits(int n, JpegDecoderCore decoder)
+ public void EnsureNBits(int n, ref InputProcessor inputProcessor)
+ {
+ DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor);
+ errorCode.EnsureNoError();
+ }
+
+ ///
+ /// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
+ /// least n. For best performance (avoiding function calls inside hot loops),
+ /// the caller is the one responsible for first checking that bits.UnreadBits < n.
+ /// This method does not throw. Returns instead.
+ ///
+ /// The number of bits to ensure.
+ /// The
+ /// Error code
+ public DecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor)
{
while (true)
{
- JpegDecoderCore.ErrorCodes errorCode;
-
- // Grab the decode bytes, use them and then set them
- // back on the decoder.
- Bytes decoderBytes = decoder.Bytes;
- byte c = decoderBytes.ReadByteStuffedByte(decoder.InputStream, out errorCode);
- decoder.Bytes = decoderBytes;
-
- if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
+ DecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor);
+ if (errorCode != DecoderErrorCode.NoError || this.UnreadBits >= n)
{
return errorCode;
}
+ }
+ }
- this.Accumulator = (this.Accumulator << 8) | c;
- this.UnreadBits += 8;
- if (this.Mask == 0)
- {
- this.Mask = 1 << 7;
- }
- else
- {
- this.Mask <<= 8;
- }
+ ///
+ /// Unrolled version of for n==8
+ ///
+ /// The
+ /// A
+ public DecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor)
+ {
+ return this.EnsureBitsStepImpl(ref inputProcessor);
+ }
- if (this.UnreadBits >= n)
- {
- return JpegDecoderCore.ErrorCodes.NoError;
- }
- }
+ ///
+ /// Unrolled version of for n==1
+ ///
+ /// The
+ /// A
+ public DecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor)
+ {
+ return this.EnsureBitsStepImpl(ref inputProcessor);
}
///
/// Receive extend
///
/// Byte
- /// Jpeg decoder
+ /// The
/// Read bits value
- internal int ReceiveExtend(byte t, JpegDecoderCore decoder)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int ReceiveExtend(int t, ref InputProcessor inputProcessor)
+ {
+ int x;
+ DecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out x);
+ errorCode.EnsureNoError();
+ return x;
+ }
+
+ ///
+ /// Receive extend
+ ///
+ /// Byte
+ /// The
+ /// Read bits value
+ /// The
+ public DecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x)
{
if (this.UnreadBits < t)
{
- JpegDecoderCore.ErrorCodes errorCode = this.EnsureNBits(t, decoder);
- if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
+ DecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor);
+ if (errorCode != DecoderErrorCode.NoError)
{
- throw new JpegDecoderCore.MissingFF00Exception();
+ x = int.MaxValue;
+ return errorCode;
}
}
this.UnreadBits -= t;
this.Mask >>= t;
int s = 1 << t;
- int x = (int)((this.Accumulator >> this.UnreadBits) & (s - 1));
+ x = (int)((this.Accumulator >> this.UnreadBits) & (s - 1));
if (x < (s >> 1))
{
x += ((-1) << t) + 1;
}
- return x;
+ return DecoderErrorCode.NoError;
+ }
+
+ private DecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor)
+ {
+ int c;
+ DecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out c);
+
+ if (errorCode != DecoderErrorCode.NoError)
+ {
+ return errorCode;
+ }
+
+ this.Accumulator = (this.Accumulator << 8) | c;
+ this.UnreadBits += 8;
+ if (this.Mask == 0)
+ {
+ this.Mask = 1 << 7;
+ }
+ else
+ {
+ this.Mask <<= 8;
+ }
+
+ return errorCode;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs
index b91420b42e..0e57e98d89 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs
@@ -16,6 +16,11 @@ namespace ImageSharp.Formats.Jpg
///
internal struct Bytes : IDisposable
{
+ ///
+ /// Specifies the buffer size for and
+ ///
+ public const int BufferSize = 4096;
+
///
/// Gets or sets the buffer.
/// buffer[i:j] are the buffered bytes read from the underlying
@@ -23,6 +28,11 @@ namespace ImageSharp.Formats.Jpg
///
public byte[] Buffer;
+ ///
+ /// Values of converted to -s
+ ///
+ public int[] BufferAsInt;
+
///
/// Start of bytes read
///
@@ -39,7 +49,9 @@ namespace ImageSharp.Formats.Jpg
///
public int UnreadableBytes;
- private static readonly ArrayPool ArrayPool = ArrayPool.Create(4096, 50);
+ private static readonly ArrayPool BytePool = ArrayPool.Create(BufferSize, 50);
+
+ private static readonly ArrayPool IntPool = ArrayPool.Create(BufferSize, 50);
///
/// Creates a new instance of the , and initializes it's buffer.
@@ -47,7 +59,11 @@ namespace ImageSharp.Formats.Jpg
/// The bytes created
public static Bytes Create()
{
- return new Bytes { Buffer = ArrayPool.Rent(4096) };
+ return new Bytes
+ {
+ Buffer = BytePool.Rent(BufferSize),
+ BufferAsInt = IntPool.Rent(BufferSize)
+ };
}
///
@@ -57,68 +73,72 @@ namespace ImageSharp.Formats.Jpg
{
if (this.Buffer != null)
{
- ArrayPool.Return(this.Buffer);
+ BytePool.Return(this.Buffer);
+ IntPool.Return(this.BufferAsInt);
}
this.Buffer = null;
+ this.BufferAsInt = null;
}
///
/// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
///
/// Input stream
- /// Error code
- /// The
- internal byte ReadByteStuffedByte(Stream inputStream, out JpegDecoderCore.ErrorCodes errorCode)
+ /// The result byte as
+ /// The
+ public DecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x)
{
- byte x;
-
- errorCode = JpegDecoderCore.ErrorCodes.NoError;
-
// Take the fast path if bytes.buf contains at least two bytes.
if (this.I + 2 <= this.J)
{
- x = this.Buffer[this.I];
+ x = this.BufferAsInt[this.I];
this.I++;
this.UnreadableBytes = 1;
- if (x != JpegConstants.Markers.XFF)
+ if (x != JpegConstants.Markers.XFFInt)
{
- return x;
+ return DecoderErrorCode.NoError;
}
- if (this.Buffer[this.I] != 0x00)
+ if (this.BufferAsInt[this.I] != 0x00)
{
- errorCode = JpegDecoderCore.ErrorCodes.MissingFF00;
- return 0;
-
- // throw new MissingFF00Exception();
+ return DecoderErrorCode.MissingFF00;
}
this.I++;
this.UnreadableBytes = 2;
- return JpegConstants.Markers.XFF;
+ x = JpegConstants.Markers.XFF;
+ return DecoderErrorCode.NoError;
}
this.UnreadableBytes = 0;
- x = this.ReadByte(inputStream);
+ DecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 1;
+ if (errorCode != DecoderErrorCode.NoError)
+ {
+ return errorCode;
+ }
+
if (x != JpegConstants.Markers.XFF)
{
- return x;
+ return DecoderErrorCode.NoError;
}
- x = this.ReadByte(inputStream);
+ errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 2;
- if (x != 0x00)
+ if (errorCode != DecoderErrorCode.NoError)
{
- errorCode = JpegDecoderCore.ErrorCodes.MissingFF00;
- return 0;
+ return errorCode;
+ }
- // throw new MissingFF00Exception();
+ if (x != 0x00)
+ {
+ return DecoderErrorCode.MissingFF00;
}
- return JpegConstants.Markers.XFF;
+ x = JpegConstants.Markers.XFF;
+ return DecoderErrorCode.NoError;
}
///
@@ -127,30 +147,92 @@ namespace ImageSharp.Formats.Jpg
/// Input stream
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal byte ReadByte(Stream inputStream)
+ public byte ReadByte(Stream inputStream)
+ {
+ byte result;
+ DecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out result);
+ errorCode.EnsureNoError();
+ return result;
+ }
+
+ ///
+ /// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing.
+ /// This method does not throw on format error, it returns a instead.
+ ///
+ /// Input stream
+ /// The result as out parameter
+ /// The
+ public DecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result)
+ {
+ DecoderErrorCode errorCode = DecoderErrorCode.NoError;
+ while (this.I == this.J)
+ {
+ errorCode = this.FillUnsafe(inputStream);
+ if (errorCode != DecoderErrorCode.NoError)
+ {
+ result = 0;
+ return errorCode;
+ }
+ }
+
+ result = this.Buffer[this.I];
+ this.I++;
+ this.UnreadableBytes = 0;
+ return errorCode;
+ }
+
+ ///
+ /// Same as but the result is an
+ ///
+ /// The input stream
+ /// The result
+ /// A
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public DecoderErrorCode ReadByteAsIntUnsafe(Stream inputStream, out int result)
{
+ DecoderErrorCode errorCode = DecoderErrorCode.NoError;
while (this.I == this.J)
{
- this.Fill(inputStream);
+ errorCode = this.FillUnsafe(inputStream);
+ if (errorCode != DecoderErrorCode.NoError)
+ {
+ result = 0;
+ return errorCode;
+ }
}
- byte x = this.Buffer[this.I];
+ result = this.BufferAsInt[this.I];
this.I++;
this.UnreadableBytes = 0;
- return x;
+ return errorCode;
}
///
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
///
+ /// Thrown when reached end of stream unexpectedly.
/// Input stream
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void Fill(Stream inputStream)
+ public void Fill(Stream inputStream)
+ {
+ DecoderErrorCode errorCode = this.FillUnsafe(inputStream);
+ errorCode.EnsureNoError();
+ }
+
+ ///
+ /// Fills up the bytes buffer from the underlying stream.
+ /// It should only be called when there are no unread bytes in bytes.
+ /// This method does not throw , returns a instead!
+ ///
+ /// Input stream
+ /// The
+ public DecoderErrorCode FillUnsafe(Stream inputStream)
{
if (this.I != this.J)
{
- throw new ImageFormatException("Fill called when unread bytes exist.");
+ // Unrecoverable error in the input, throwing!
+ DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist();
}
// Move the last 2 bytes to the start of the buffer, in case we need
@@ -167,10 +249,17 @@ namespace ImageSharp.Formats.Jpg
int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J);
if (n == 0)
{
- throw new JpegDecoderCore.EOFException();
+ return DecoderErrorCode.UnexpectedEndOfStream;
}
this.J += n;
+
+ for (int i = 0; i < this.Buffer.Length; i++)
+ {
+ this.BufferAsInt[i] = this.Buffer[i];
+ }
+
+ return DecoderErrorCode.NoError;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs
new file mode 100644
index 0000000000..04ece04ee8
--- /dev/null
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs
@@ -0,0 +1,101 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpg
+{
+ using System;
+ using System.Buffers;
+
+ ///
+ /// A structure to store unprocessed instances and their coordinates while scanning the image.
+ ///
+ internal struct DecodedBlockMemento
+ {
+ ///
+ /// A value indicating whether the instance is initialized.
+ ///
+ public bool Initialized;
+
+ ///
+ /// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
+ ///
+ public int Bx;
+
+ ///
+ /// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
+ ///
+ public int By;
+
+ ///
+ /// The
+ ///
+ public Block8x8F Block;
+
+ ///
+ /// Store the block data into a at the given index of an .
+ ///
+ /// The array
+ /// The index in the array
+ /// X coordinate of the block
+ /// Y coordinate of the block
+ /// The
+ public static void Store(ref DecodedBlockMemento.Array blockArray, int index, int bx, int by, ref Block8x8F block)
+ {
+ if (index >= blockArray.Count)
+ {
+ throw new IndexOutOfRangeException("Block index is out of range in DecodedBlockMemento.Store()!");
+ }
+
+ blockArray.Buffer[index].Initialized = true;
+ blockArray.Buffer[index].Bx = bx;
+ blockArray.Buffer[index].By = by;
+ blockArray.Buffer[index].Block = block;
+ }
+
+ ///
+ /// Because has no information for rented arrays, we need to store the count and the buffer separately.
+ ///
+ public struct Array : IDisposable
+ {
+ ///
+ /// The used to pool data in .
+ /// Should always clean arrays when returning!
+ ///
+ private static readonly ArrayPool ArrayPool = ArrayPool.Create();
+
+ ///
+ /// Initializes a new instance of the struct. Rents a buffer.
+ ///
+ /// The number of valid -s
+ public Array(int count)
+ {
+ this.Count = count;
+ this.Buffer = ArrayPool.Rent(count);
+ }
+
+ ///
+ /// Gets the number of actual -s inside
+ ///
+ public int Count { get; }
+
+ ///
+ /// Gets the rented buffer.
+ ///
+ public DecodedBlockMemento[] Buffer { get; private set; }
+
+ ///
+ /// Returns the rented buffer to the pool.
+ ///
+ public void Dispose()
+ {
+ if (this.Buffer != null)
+ {
+ ArrayPool.Return(this.Buffer, true);
+ this.Buffer = null;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs
new file mode 100644
index 0000000000..8b82184faf
--- /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
+ ///
+ UnexpectedEndOfStream
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs
new file mode 100644
index 0000000000..9ce5ea4146
--- /dev/null
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs
@@ -0,0 +1,95 @@
+//
+// 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.UnexpectedEndOfStream:
+ 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);
+ }
+ }
+
+ ///
+ /// Throws an exception if the given is .
+ ///
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void EnsureNoEOF(this DecoderErrorCode errorCode)
+ {
+ if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
+ {
+ errorCode.ThrowExceptionForErrorCode();
+ }
+ }
+
+ ///
+ /// Encapsulates methods throwing different flavours of -s.
+ ///
+ public static class ThrowImageFormatException
+ {
+ ///
+ /// Throws "Fill called when unread bytes exist".
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void FillCalledWhenUnreadBytesExist()
+ {
+ throw new ImageFormatException("Fill called when unread bytes exist!");
+ }
+
+ ///
+ /// Throws "Bad Huffman code".
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void BadHuffmanCode()
+ {
+ throw new ImageFormatException("Bad Huffman code!");
+ }
+
+ ///
+ /// Throws "Uninitialized Huffman table".
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void UninitializedHuffmanTable()
+ {
+ throw new ImageFormatException("Uninitialized Huffman table");
+ }
+ }
+ }
+}
\ 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 0000000000..5ed25ef049
--- /dev/null
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs
@@ -0,0 +1,25 @@
+//
+// 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
+ /// TODO: Rename to UnexpectedEndOfStreamException
+ ///
+ internal class EOFException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ 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/HuffmanTree.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs
index e06d644a7f..03013219c3 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs
@@ -45,7 +45,7 @@ namespace ImageSharp.Formats.Jpg
///
/// The log-2 size of the Huffman decoder's look-up table.
///
- public const int LutSize = 8;
+ public const int LutSizeLog2 = 8;
///
/// Gets or sets the number of codes in the tree.
@@ -58,12 +58,12 @@ namespace ImageSharp.Formats.Jpg
/// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits.
///
- public ushort[] Lut;
+ public int[] Lut;
///
/// Gets the the decoded values, sorted by their encoding.
///
- public byte[] Values;
+ public int[] Values;
///
/// Gets the array of minimum codes.
@@ -82,11 +82,11 @@ namespace ImageSharp.Formats.Jpg
///
public int[] Indices;
- private static readonly ArrayPool UshortBuffer = ArrayPool.Create(1 << LutSize, 50);
+ private static readonly ArrayPool IntPool256 = ArrayPool.Create(MaxNCodes, 50);
- private static readonly ArrayPool ByteBuffer = ArrayPool.Create(MaxNCodes, 50);
+ private static readonly ArrayPool BytePool256 = ArrayPool.Create(MaxNCodes, 50);
- private static readonly ArrayPool IntBuffer = ArrayPool.Create(MaxCodeLength, 50);
+ private static readonly ArrayPool CodesPool16 = ArrayPool.Create(MaxCodeLength, 50);
///
/// Creates and initializes an array of instances of size
@@ -111,21 +111,21 @@ namespace ImageSharp.Formats.Jpg
///
public void Dispose()
{
- UshortBuffer.Return(this.Lut, true);
- ByteBuffer.Return(this.Values, true);
- IntBuffer.Return(this.MinCodes, true);
- IntBuffer.Return(this.MaxCodes, true);
- IntBuffer.Return(this.Indices, true);
+ IntPool256.Return(this.Lut, true);
+ IntPool256.Return(this.Values, true);
+ CodesPool16.Return(this.MinCodes, true);
+ CodesPool16.Return(this.MaxCodes, true);
+ CodesPool16.Return(this.Indices, true);
}
///
/// 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 InputProcessor inputProcessor,
byte[] defineHuffmanTablesData,
ref int remaining)
{
@@ -157,7 +157,21 @@ namespace ImageSharp.Formats.Jpg
throw new ImageFormatException("DHT has wrong length");
}
- decoder.ReadFull(this.Values, 0, this.Length);
+ byte[] values = null;
+ try
+ {
+ values = BytePool256.Rent(MaxNCodes);
+ inputProcessor.ReadFull(values, 0, this.Length);
+
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = values[i];
+ }
+ }
+ finally
+ {
+ BytePool256.Return(values, true);
+ }
// Derive the look-up table.
for (int i = 0; i < this.Lut.Length; i++)
@@ -165,9 +179,9 @@ namespace ImageSharp.Formats.Jpg
this.Lut[i] = 0;
}
- uint x = 0, code = 0;
+ int x = 0, code = 0;
- for (int i = 0; i < LutSize; i++)
+ for (int i = 0; i < LutSizeLog2; i++)
{
code <<= 1;
@@ -178,8 +192,8 @@ namespace ImageSharp.Formats.Jpg
// whose codeLength's high bits matches code.
// The high 8 bits of lutValue are the encoded value.
// The low 8 bits are 1 plus the codeLength.
- byte base2 = (byte)(code << (7 - i));
- ushort lutValue = (ushort)((this.Values[x] << 8) | (2 + i));
+ int base2 = code << (7 - i);
+ int lutValue = (this.Values[x] << 8) | (2 + i);
for (int k = 0; k < 1 << (7 - i); k++)
{
@@ -215,16 +229,27 @@ namespace ImageSharp.Formats.Jpg
}
}
+ ///
+ /// Gets the value for the given code and index.
+ ///
+ /// The code
+ /// The code length
+ /// The value
+ public int GetValue(int code, int codeLength)
+ {
+ return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]];
+ }
+
///
/// Initializes the Huffman tree
///
private void Init()
{
- this.Lut = UshortBuffer.Rent(1 << LutSize);
- this.Values = ByteBuffer.Rent(MaxNCodes);
- this.MinCodes = IntBuffer.Rent(MaxCodeLength);
- this.MaxCodes = IntBuffer.Rent(MaxCodeLength);
- this.Indices = IntBuffer.Rent(MaxCodeLength);
+ this.Lut = IntPool256.Rent(MaxNCodes);
+ this.Values = IntPool256.Rent(MaxNCodes);
+ this.MinCodes = CodesPool16.Rent(MaxCodeLength);
+ this.MaxCodes = CodesPool16.Rent(MaxCodeLength);
+ this.Indices = CodesPool16.Rent(MaxCodeLength);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs
new file mode 100644
index 0000000000..60042d36f8
--- /dev/null
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs
@@ -0,0 +1,368 @@
+//
+// 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 InputProcessor : 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 InputProcessor(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 a 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.
+ ///
+ /// The
+ /// indicating whether everything is OK
+ public bool CheckEOFEnsureNoError(DecoderErrorCode errorCode)
+ {
+ if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
+ {
+ this.UnexpectedEndOfStreamReached = true;
+ return false;
+ }
+
+ errorCode.EnsureNoError();
+ return true;
+ }
+
+ ///
+ /// If errorCode indicates unexpected EOF, sets to true and returns false.
+ /// Returns true otherwise.
+ ///
+ /// The
+ /// indicating whether everything is OK
+ public bool CheckEOF(DecoderErrorCode errorCode)
+ {
+ if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
+ {
+ this.UnexpectedEndOfStreamReached = true;
+ return false;
+ }
+
+ 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/JpegScanDecoder.ComputationData.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs
index ef04bf4188..06f170be5a 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs
@@ -13,7 +13,7 @@ namespace ImageSharp.Formats.Jpg
internal unsafe partial struct JpegScanDecoder
{
///
- /// Holds the "large" data blocks needed for computations
+ /// Holds the "large" data blocks needed for computations.
///
[StructLayout(LayoutKind.Sequential)]
public struct ComputationData
@@ -44,12 +44,12 @@ namespace ImageSharp.Formats.Jpg
public UnzigData Unzig;
///
- /// The no-idea-what's this data
+ /// The buffer storing the -s for each component
///
public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents];
///
- /// The DC component values
+ /// The DC values for each component
///
public fixed int Dc[JpegDecoderCore.MaxComponents];
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
index 9fef5010df..0e389771c0 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
@@ -2,7 +2,6 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
// ReSharper disable InconsistentNaming
namespace ImageSharp.Formats.Jpg
{
@@ -10,20 +9,39 @@ namespace ImageSharp.Formats.Jpg
using System.Runtime.CompilerServices;
///
- /// Encapsulates the impementation of Jpeg SOS decoder.
- /// See JpegScanDecoder.md!
+ /// Encapsulates the impementation of Jpeg SOS decoder. See JpegScanDecoder.md!
+ /// TODO: Split JpegScanDecoder: 1. JpegScanDecoder for Huffman-decoding () 2. JpegBlockProcessor for processing ()
+ /// and are the spectral selection bounds.
+ /// and are the successive approximation high and low values.
+ /// The spec calls these values Ss, Se, Ah and Al.
+ /// For progressive JPEGs, these are the two more-or-less independent
+ /// aspects of progression. Spectral selection progression is when not
+ /// all of a block's 64 DCT coefficients are transmitted in one pass.
+ /// For example, three passes could transmit coefficient 0 (the DC
+ /// component), coefficients 1-5, and coefficients 6-63, in zig-zag
+ /// order. Successive approximation is when not all of the bits of a
+ /// band of coefficients are transmitted in one pass. For example,
+ /// three passes could transmit the 6 most significant bits, followed
+ /// by the second-least significant bit, followed by the least
+ /// significant bit.
+ /// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
///
internal unsafe partial struct JpegScanDecoder
{
///
/// The AC table index
///
- private const int AcTableIndex = 1;
+ public const int AcTableIndex = 1;
///
/// The DC table index
///
- private const int DcTableIndex = 0;
+ public const int DcTableIndex = 0;
+
+ ///
+ /// The current component index
+ ///
+ public int ComponentIndex;
///
/// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
@@ -35,21 +53,6 @@ namespace ImageSharp.Formats.Jpg
///
private int by;
- // zigStart and zigEnd are the spectral selection bounds.
- // ah and al are the successive approximation high and low values.
- // The spec calls these values Ss, Se, Ah and Al.
- // For progressive JPEGs, these are the two more-or-less independent
- // aspects of progression. Spectral selection progression is when not
- // all of a block's 64 DCT coefficients are transmitted in one pass.
- // For example, three passes could transmit coefficient 0 (the DC
- // component), coefficients 1-5, and coefficients 6-63, in zig-zag
- // order. Successive approximation is when not all of the bits of a
- // band of coefficients are transmitted in one pass. For example,
- // three passes could transmit the 6 most significant bits, followed
- // by the second-least significant bit, followed by the least
- // significant bit.
- // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
-
///
/// Start index of the zig-zag selection bound
///
@@ -75,11 +78,6 @@ namespace ImageSharp.Formats.Jpg
///
private int componentScanCount;
- ///
- /// The current component index
- ///
- private int componentIndex;
-
///
/// Horizontal sampling factor at the current component index
///
@@ -88,36 +86,80 @@ namespace ImageSharp.Formats.Jpg
///
/// End-of-Band run, specified in section G.1.2.2.
///
- private ushort eobRun;
+ private int eobRun;
///
- /// The buffer
+ /// Pointers to elements of
///
- private ComputationData data;
+ private DataPointers pointers;
///
- /// Pointers to elements of
+ /// The buffer
///
- private DataPointers pointers;
+ private ComputationData data;
///
- /// Initializes the default instance after creation.
+ /// Initializes a default-constructed instance for reading data from -s stream.
///
/// Pointer to on the stack
/// The instance
/// The remaining bytes in the segment block.
- public static void Init(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining)
+ public static void InitStreamReading(JpegScanDecoder* p, JpegDecoderCore decoder, int remaining)
+ {
+ Init(p);
+ p->InitStreamReadingImpl(decoder, remaining);
+ }
+
+ ///
+ /// Initializes a default-constructed instance, filling the data and setting the pointers.
+ ///
+ /// Pointer to on the stack
+ public static void Init(JpegScanDecoder* p)
{
p->data = ComputationData.Create();
p->pointers = new DataPointers(&p->data);
- p->InitImpl(decoder, remaining);
}
///
- /// Reads the blocks from the -s stream, and processes them into the corresponding instances.
+ /// Loads the data from the given into the block.
+ ///
+ /// The
+ public void LoadMemento(ref DecodedBlockMemento memento)
+ {
+ this.bx = memento.Bx;
+ this.by = memento.By;
+ this.data.Block = memento.Block;
+ }
+
+ ///
+ /// Read Huffman data from Jpeg scans in ,
+ /// and decode it as into .
+ ///
+ /// The blocks are traversed one MCU at a time. For 4:2:0 chroma
+ /// subsampling, there are four Y 8x8 blocks in every 16x16 MCU.
+ /// For a baseline 32x16 pixel image, the Y blocks visiting order is:
+ /// 0 1 4 5
+ /// 2 3 6 7
+ /// For progressive images, the interleaved scans (those with component count > 1)
+ /// are traversed as above, but non-interleaved scans are traversed left
+ /// to right, top to bottom:
+ /// 0 1 2 3
+ /// 4 5 6 7
+ /// Only DC scans (zigStart == 0) can be interleave AC scans must have
+ /// only one component.
+ /// To further complicate matters, for non-interleaved scans, there is no
+ /// data for any blocks that are inside the image at the MCU level but
+ /// outside the image at the pixel level. For example, a 24x16 pixel 4:2:0
+ /// progressive image consists of two 16x16 MCUs. The interleaved scans
+ /// will process 8 Y blocks:
+ /// 0 1 4 5
+ /// 2 3 6 7
+ /// The non-interleaved scans will process only 6 Y blocks:
+ /// 0 1 2
+ /// 3 4 5
///
/// The instance
- public void ProcessBlocks(JpegDecoderCore decoder)
+ public void DecodeBlocks(JpegDecoderCore decoder)
{
int blockCount = 0;
int mcu = 0;
@@ -127,36 +169,14 @@ 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.hi = decoder.ComponentArray[this.componentIndex].HorizontalFactor;
- int vi = decoder.ComponentArray[this.componentIndex].VerticalFactor;
+ this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex;
+ this.hi = decoder.ComponentArray[this.ComponentIndex].HorizontalFactor;
+ int vi = decoder.ComponentArray[this.ComponentIndex].VerticalFactor;
for (int j = 0; j < this.hi * vi; j++)
{
- // The blocks are traversed one MCU at a time. For 4:2:0 chroma
- // subsampling, there are four Y 8x8 blocks in every 16x16 MCU.
- // For a baseline 32x16 pixel image, the Y blocks visiting order is:
- // 0 1 4 5
- // 2 3 6 7
- // For progressive images, the interleaved scans (those with component count > 1)
- // are traversed as above, but non-interleaved scans are traversed left
- // to right, top to bottom:
- // 0 1 2 3
- // 4 5 6 7
- // Only DC scans (zigStart == 0) can be interleave AC scans must have
- // only one component.
- // To further complicate matters, for non-interleaved scans, there is no
- // data for any blocks that are inside the image at the MCU level but
- // outside the image at the pixel level. For example, a 24x16 pixel 4:2:0
- // progressive image consists of two 16x16 MCUs. The interleaved scans
- // will process 8 Y blocks:
- // 0 1 4 5
- // 2 3 6 7
- // The non-interleaved scans will process only 6 Y blocks:
- // 0 1 2
- // 3 4 5
if (this.componentScanCount != 1)
{
this.bx = (this.hi * mx) + (j % this.hi);
@@ -174,23 +194,18 @@ namespace ImageSharp.Formats.Jpg
}
}
- int qtIndex = decoder.ComponentArray[this.componentIndex].Selector;
+ // Take an existing block (required when progressive):
+ int blockIndex = this.GetBlockIndex(decoder);
+ this.data.Block = decoder.DecodedBlocks[this.ComponentIndex].Buffer[blockIndex].Block;
- // TODO: Reading & processing blocks should be done in 2 separate loops. The second one could be parallelized. The first one could be async.
- this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex];
-
- // Load the previous partially decoded coefficients, if applicable.
- if (decoder.IsProgressive)
+ if (!decoder.InputProcessor.UnexpectedEndOfStreamReached)
{
- int blockIndex = this.GetBlockIndex(decoder);
- this.data.Block = decoder.DecodedBlocks[this.componentIndex][blockIndex];
- }
- else
- {
- this.data.Block.Clear();
+ this.DecodeBlock(decoder, scanIndex);
}
- this.ProcessBlockImpl(decoder, i);
+ // Store the decoded block
+ DecodedBlockMemento.Array blocks = decoder.DecodedBlocks[this.ComponentIndex];
+ DecodedBlockMemento.Store(ref blocks, blockIndex, this.bx, this.by, ref this.data.Block);
}
// for j
@@ -203,20 +218,26 @@ 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)
+ if (!decoder.InputProcessor.UnexpectedEndOfStreamReached)
{
- throw new ImageFormatException("Bad RST marker");
- }
+ DecoderErrorCode errorCode = decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2);
+ if (decoder.InputProcessor.CheckEOFEnsureNoError(errorCode))
+ {
+ if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst)
+ {
+ throw new ImageFormatException("Bad RST marker");
+ }
- expectedRst++;
- if (expectedRst == JpegConstants.Markers.RST7 + 1)
- {
- expectedRst = JpegConstants.Markers.RST0;
+ expectedRst++;
+ if (expectedRst == JpegConstants.Markers.RST7 + 1)
+ {
+ expectedRst = JpegConstants.Markers.RST0;
+ }
+ }
}
// Reset the Huffman decoder.
- decoder.Bits = default(Bits);
+ decoder.InputProcessor.Bits = default(Bits);
// Reset the DC components, as per section F.2.1.3.1.
this.ResetDc();
@@ -230,17 +251,37 @@ namespace ImageSharp.Formats.Jpg
}
}
+ ///
+ /// Dequantize, perform the inverse DCT and store the block to the into the corresponding instances.
+ ///
+ /// The instance
+ public void ProcessBlockColors(JpegDecoderCore decoder)
+ {
+ int qtIndex = decoder.ComponentArray[this.ComponentIndex].Selector;
+ this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex];
+
+ Block8x8F* b = this.pointers.Block;
+
+ Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig);
+
+ DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2);
+
+ var destChannel = decoder.GetDestinationChannel(this.ComponentIndex);
+ var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by);
+ destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2);
+ }
+
private void ResetDc()
{
Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * JpegDecoderCore.MaxComponents);
}
///
- /// The implementation part of as an instance method.
+ /// The implementation part of as an instance method.
///
/// The
/// The remaining bytes
- private void InitImpl(JpegDecoderCore decoder, int remaining)
+ private void InitStreamReadingImpl(JpegDecoderCore decoder, int remaining)
{
if (decoder.ComponentCount == 0)
{
@@ -252,7 +293,7 @@ namespace ImageSharp.Formats.Jpg
throw new ImageFormatException("SOS has wrong length");
}
- decoder.ReadFull(decoder.Temp, 0, remaining);
+ decoder.InputProcessor.ReadFull(decoder.Temp, 0, remaining);
this.componentScanCount = decoder.Temp[0];
int scanComponentCountX2 = 2 * this.componentScanCount;
@@ -265,7 +306,7 @@ namespace ImageSharp.Formats.Jpg
for (int i = 0; i < this.componentScanCount; i++)
{
- this.ProcessScanImpl(decoder, i, ref this.pointers.ComponentScan[i], ref totalHv);
+ this.InitComponentScan(decoder, i, ref this.pointers.ComponentScan[i], ref totalHv);
}
// Section B.2.3 states that if there is more than one component then the
@@ -303,18 +344,18 @@ namespace ImageSharp.Formats.Jpg
}
///
- /// Process the current block at (, )
+ /// Read the current the current block at (, ) from the decoders stream
///
/// The decoder
- /// The index of the scan
- private void ProcessBlockImpl(JpegDecoderCore decoder, int i)
+ /// The index of the scan
+ private void DecodeBlock(JpegDecoderCore decoder, int scanIndex)
{
var b = this.pointers.Block;
-
- int huffmannIdx = (AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[i].AcTableSelector;
+ 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.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al);
}
else
{
@@ -324,19 +365,32 @@ namespace ImageSharp.Formats.Jpg
zig++;
// 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]);
+ int value;
+ int huffmanIndex = (DcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector;
+ errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe(
+ ref decoder.HuffmanTrees[huffmanIndex],
+ out value);
+ if (!decoder.InputProcessor.CheckEOF(errorCode))
+ {
+ return;
+ }
+
if (value > 16)
{
throw new ImageFormatException("Excessive DC component");
}
- int deltaDC = decoder.Bits.ReceiveExtend(value, decoder);
- this.pointers.Dc[this.componentIndex] += deltaDC;
+ int deltaDC;
+ errorCode = decoder.InputProcessor.ReceiveExtendUnsafe(value, out deltaDC);
+ if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode))
+ {
+ return;
+ }
+
+ this.pointers.Dc[this.ComponentIndex] += deltaDC;
// b[0] = dc[compIndex] << al;
- Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[this.componentIndex] << this.al);
+ Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[this.ComponentIndex] << this.al);
}
if (zig <= this.zigEnd && this.eobRun > 0)
@@ -348,9 +402,15 @@ namespace ImageSharp.Formats.Jpg
// Decode the AC coefficients, as specified in section F.2.2.2.
for (; zig <= this.zigEnd; zig++)
{
- byte value = decoder.DecodeHuffman(ref decoder.HuffmanTrees[huffmannIdx]);
- byte val0 = (byte)(value >> 4);
- byte val1 = (byte)(value & 0x0f);
+ int value;
+ errorCode = decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out value);
+ if (!decoder.InputProcessor.CheckEOF(errorCode))
+ {
+ return;
+ }
+
+ int val0 = value >> 4;
+ int val1 = value & 0x0f;
if (val1 != 0)
{
zig += val0;
@@ -359,7 +419,12 @@ namespace ImageSharp.Formats.Jpg
break;
}
- int ac = decoder.Bits.ReceiveExtend(val1, decoder);
+ int ac;
+ errorCode = decoder.InputProcessor.ReceiveExtendUnsafe(val1, out ac);
+ if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode))
+ {
+ return;
+ }
// b[Unzig[zig]] = ac << al;
Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al);
@@ -371,7 +436,11 @@ namespace ImageSharp.Formats.Jpg
this.eobRun = (ushort)(1 << val0);
if (val0 != 0)
{
- this.eobRun |= (ushort)decoder.DecodeBits(val0);
+ errorCode = this.DecodeEobRun(val0, ref decoder.InputProcessor);
+ if (!decoder.InputProcessor.CheckEOFEnsureNoError(errorCode))
+ {
+ return;
+ }
}
this.eobRun--;
@@ -383,31 +452,19 @@ namespace ImageSharp.Formats.Jpg
}
}
}
+ }
- if (decoder.IsProgressive)
+ private DecoderErrorCode DecodeEobRun(int count, ref InputProcessor decoder)
+ {
+ int bitsResult;
+ DecoderErrorCode errorCode = decoder.DecodeBitsUnsafe(count, out bitsResult);
+ if (errorCode != DecoderErrorCode.NoError)
{
- if (this.zigEnd != Block8x8F.ScalarCount - 1 || this.al != 0)
- {
- // We haven't completely decoded this 8x8 block. Save the coefficients.
- decoder.DecodedBlocks[this.componentIndex][this.GetBlockIndex(decoder)] = *b;
-
- // At this point, we could execute the rest of the loop body to dequantize and
- // perform the inverse DCT, to save early stages of a progressive image to the
- // *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
- // the jpeg.Decode function does not return until the entire image is decoded,
- // so we "continue" here to avoid wasted computation.
- return;
- }
+ return errorCode;
}
- // Dequantize, perform the inverse DCT and store the block to the image.
- Block8x8F.UnZig(b, this.pointers.QuantiazationTable, this.pointers.Unzig);
-
- DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2);
-
- var destChannel = decoder.GetDestinationChannel(this.componentIndex);
- var destArea = destChannel.GetOffsetedSubAreaForBlock(this.bx, this.by);
- destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2);
+ this.eobRun |= bitsResult;
+ return DecoderErrorCode.NoError;
}
///
@@ -417,10 +474,10 @@ namespace ImageSharp.Formats.Jpg
/// The index
private int GetBlockIndex(JpegDecoderCore decoder)
{
- return ((this.@by * decoder.MCUCountX) * this.hi) + this.bx;
+ return ((this.by * decoder.MCUCountX) * this.hi) + this.bx;
}
- private void ProcessScanImpl(JpegDecoderCore decoder, int i, ref ComponentScan currentComponentScan, ref int totalHv)
+ private void InitComponentScan(JpegDecoderCore decoder, int i, ref ComponentScan currentComponentScan, ref int totalHv)
{
// Component selector.
int cs = decoder.Temp[1 + (2 * i)];
@@ -482,10 +539,10 @@ namespace ImageSharp.Formats.Jpg
///
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
///
- /// The decoder instance
+ /// The instance
/// The Huffman tree
/// The low transform offset
- private void Refine(JpegDecoderCore decoder, ref HuffmanTree h, int delta)
+ private void Refine(ref InputProcessor bp, ref HuffmanTree h, int delta)
{
Block8x8F* b = this.pointers.Block;
@@ -497,7 +554,13 @@ namespace ImageSharp.Formats.Jpg
throw new ImageFormatException("Invalid state for zig DC component");
}
- bool bit = decoder.DecodeBit();
+ bool bit;
+ DecoderErrorCode errorCode = bp.DecodeBitUnsafe(out bit);
+ if (!bp.CheckEOFEnsureNoError(errorCode))
+ {
+ return;
+ }
+
if (bit)
{
int stuff = (int)Block8x8F.GetScalarAt(b, 0);
@@ -520,7 +583,14 @@ namespace ImageSharp.Formats.Jpg
{
bool done = false;
int z = 0;
- byte val = decoder.DecodeHuffman(ref h);
+
+ int val;
+ DecoderErrorCode errorCode = bp.DecodeHuffmanUnsafe(ref h, out val);
+ if (!bp.CheckEOF(errorCode))
+ {
+ return;
+ }
+
int val0 = val >> 4;
int val1 = val & 0x0f;
@@ -529,10 +599,14 @@ namespace ImageSharp.Formats.Jpg
case 0:
if (val0 != 0x0f)
{
- this.eobRun = (ushort)(1 << val0);
+ this.eobRun = 1 << val0;
if (val0 != 0)
{
- this.eobRun |= (ushort)decoder.DecodeBits(val0);
+ errorCode = this.DecodeEobRun(val0, ref bp);
+ if (!bp.CheckEOFEnsureNoError(errorCode))
+ {
+ return;
+ }
}
done = true;
@@ -541,7 +615,14 @@ namespace ImageSharp.Formats.Jpg
break;
case 1:
z = delta;
- bool bit = decoder.DecodeBit();
+
+ bool bit;
+ errorCode = bp.DecodeBitUnsafe(out bit);
+ if (!bp.CheckEOFEnsureNoError(errorCode))
+ {
+ return;
+ }
+
if (!bit)
{
z = -z;
@@ -557,7 +638,12 @@ namespace ImageSharp.Formats.Jpg
break;
}
- zig = this.RefineNonZeroes(decoder, zig, val0, delta);
+ zig = this.RefineNonZeroes(ref bp, zig, val0, delta);
+ if (bp.UnexpectedEndOfStreamReached)
+ {
+ return;
+ }
+
if (zig > this.zigEnd)
{
throw new ImageFormatException($"Too many coefficients {zig} > {this.zigEnd}");
@@ -574,7 +660,7 @@ namespace ImageSharp.Formats.Jpg
if (this.eobRun > 0)
{
this.eobRun--;
- this.RefineNonZeroes(decoder, zig, -1, delta);
+ this.RefineNonZeroes(ref bp, zig, -1, delta);
}
}
@@ -582,12 +668,12 @@ namespace ImageSharp.Formats.Jpg
/// Refines non-zero entries of b in zig-zag order.
/// If >= 0, the first zero entries are skipped over.
///
- /// The decoder
+ /// The
/// The zig-zag start index
/// The non-zero entry
/// The low transform offset
/// The
- private int RefineNonZeroes(JpegDecoderCore decoder, int zig, int nz, int delta)
+ private int RefineNonZeroes(ref InputProcessor bp, int zig, int nz, int delta)
{
var b = this.pointers.Block;
for (; zig <= this.zigEnd; zig++)
@@ -607,7 +693,13 @@ namespace ImageSharp.Formats.Jpg
continue;
}
- bool bit = decoder.DecodeBit();
+ bool bit;
+ DecoderErrorCode errorCode = bp.DecodeBitUnsafe(out bit);
+ if (!bp.CheckEOFEnsureNoError(errorCode))
+ {
+ return int.MinValue;
+ }
+
if (!bit)
{
continue;
diff --git a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md
index 215f21807b..4ca4d1f642 100644
--- a/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md
+++ b/src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md
@@ -4,7 +4,7 @@ The implementation is optimized to hold most of the necessary data in a single v
#### Benefits:
- Maximized locality of reference by keeping most of the operation data on the stack
-- Reaching this without long parameter lists, most of the values describing the state of the decoder algorithm
+- Achieving this without long parameter lists, most of the values describing the state of the decoder algorithm
are members of the `JpegScanDecoder` struct
- Most of the logic related to Scan decoding is refactored & simplified now to live in the methods of `JpegScanDecoder`
- The first step is done towards separating the stream reading from block processing. They can be refactored later to be executed in two disctinct loops.
@@ -16,8 +16,8 @@ are members of the `JpegScanDecoder` struct
|JpegScanDecoder |
|-------------------|
|Variables |
-|ComputationData |
|DataPointers |
+|ComputationData |
- **ComputationData** holds the "large" data blocks needed for computations (Mostly `Block8x8F`-s)
- **DataPointers** contains pointers to the memory regions of `ComponentData` so they can be easily passed around to pointer based utility methods of `Block8x8F`
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 0000000000..f8c157237d
--- /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/JpegConstants.cs b/src/ImageSharp.Formats.Jpeg/JpegConstants.cs
index 19d726e708..74f9a3c07d 100644
--- a/src/ImageSharp.Formats.Jpeg/JpegConstants.cs
+++ b/src/ImageSharp.Formats.Jpeg/JpegConstants.cs
@@ -86,6 +86,11 @@ namespace ImageSharp.Formats
///
public const byte XFF = 0xff;
+ ///
+ /// Same as but of type
+ ///
+ public const int XFFInt = XFF;
+
///
/// Start of Image
///
diff --git a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
index 707f9d3e4d..eca4d46229 100644
--- a/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
@@ -5,8 +5,10 @@
namespace ImageSharp.Formats
{
using System;
+ using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
using System.Threading.Tasks;
using ImageSharp.Formats.Jpg;
@@ -21,23 +23,20 @@ namespace ImageSharp.Formats
///
public const int MaxComponents = 4;
- // 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.
+ /// The maximum number of quantization tables
///
- public Bits Bits;
+ public const int MaxTq = 3;
- ///
- /// The byte buffer.
- ///
- public Bytes Bytes;
-#pragma warning restore SA401
+ // Complex value type field + mutable + available to other classes = the field MUST NOT be private :P
+#pragma warning disable SA1401 // FieldsMustBePrivate
///
- /// The maximum number of quantization tables
+ /// Encapsulates stream reading and processing data and operations for .
+ /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
///
- private const int MaxTq = 3;
+ public InputProcessor InputProcessor;
+#pragma warning restore SA401
///
/// The App14 marker color-space
@@ -88,27 +87,7 @@ namespace ImageSharp.Formats
this.QuantizationTables = new Block8x8F[MaxTq + 1];
this.Temp = new byte[2 * Block8x8F.ScalarCount];
this.ComponentArray = new Component[MaxComponents];
- this.DecodedBlocks = new Block8x8F[MaxComponents][];
- this.Bits = default(Bits);
- 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
+ this.DecodedBlocks = new DecodedBlockMemento.Array[MaxComponents];
}
///
@@ -123,9 +102,9 @@ namespace ImageSharp.Formats
///
/// Gets the saved state between progressive-mode scans.
- /// TODO: Also store non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop)
+ /// TODO: Also save non-progressive data here. (Helps splitting and parallelizing JpegScanDecoder-s loop)
///
- public Block8x8F[][] DecodedBlocks { get; }
+ public DecodedBlockMemento.Array[] DecodedBlocks { get; }
///
/// Gets the quantization tables, in zigzag order.
@@ -184,20 +163,118 @@ namespace ImageSharp.Formats
public int TotalMCUCount => this.MCUCountX * this.MCUCountY;
///
- /// Decodes the image from the specified this._stream and sets
+ /// Decodes the image from the specified and sets
/// the data to image.
///
/// The pixel format.
/// The image, where the data should be set to.
/// The stream, where the image should be.
- /// Whether to decode metadata only.
- public void Decode(Image image, Stream stream, bool configOnly)
+ /// Whether to decode metadata only.
+ public void Decode(Image image, Stream stream, bool metadataOnly)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ this.ProcessStream(image, stream, metadataOnly);
+ if (!metadataOnly)
+ {
+ this.ProcessBlocksIntoJpegImageChannels();
+ this.ConvertJpegPixelsToImagePixels(image);
+ }
+ }
+
+ ///
+ /// Dispose
+ ///
+ public void Dispose()
+ {
+ for (int i = 0; i < this.HuffmanTrees.Length; i++)
+ {
+ this.HuffmanTrees[i].Dispose();
+ }
+
+ foreach (DecodedBlockMemento.Array blockArray in this.DecodedBlocks)
+ {
+ blockArray.Dispose();
+ }
+
+ this.ycbcrImage?.Dispose();
+ this.InputProcessor.Dispose();
+ this.grayImage.ReturnPooled();
+ this.blackImage.ReturnPooled();
+ }
+
+ ///
+ /// Gets the representing the channel at a given component index
+ ///
+ /// The component index
+ /// The of the channel
+ public JpegPixelArea GetDestinationChannel(int compIndex)
+ {
+ if (this.ComponentCount == 1)
+ {
+ return this.grayImage;
+ }
+ else
+ {
+ switch (compIndex)
+ {
+ case 0:
+ return this.ycbcrImage.YChannel;
+ case 1:
+ return this.ycbcrImage.CbChannel;
+ case 2:
+ return this.ycbcrImage.CrChannel;
+ case 3:
+ return this.blackImage;
+ default:
+ throw new ImageFormatException("Too many components");
+ }
+ }
+ }
+
+ ///
+ /// Optimized method to pack bytes to the image from the YCbCr color space.
+ /// This is faster than implicit casting as it avoids double packing.
+ ///
+ /// The pixel format.
+ /// The packed pixel.
+ /// The y luminance component.
+ /// The cb chroma component.
+ /// The cr chroma component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ int ccb = cb - 128;
+ int ccr = cr - 128;
+
+ // Speed up the algorithm by removing floating point calculation
+ // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result
+ int r0 = 91881 * ccr; // (1.402F * 65536) + .5F
+ int g0 = 22554 * ccb; // (0.34414F * 65536) + .5F
+ int g1 = 46802 * ccr; // (0.71414F * 65536) + .5F
+ int b0 = 116130 * ccb; // (1.772F * 65536) + .5F
+
+ byte r = (byte)(y + (r0 >> 16)).Clamp(0, 255);
+ byte g = (byte)(y - (g0 >> 16) - (g1 >> 16)).Clamp(0, 255);
+ byte b = (byte)(y + (b0 >> 16)).Clamp(0, 255);
+ packed.PackFromBytes(r, g, b, 255);
+ }
+
+ ///
+ /// Read metadata from stream and read the blocks in the scans into .
+ ///
+ /// The pixel type
+ /// The
+ /// The stream
+ /// Whether to decode metadata only.
+ private void ProcessStream(Image image, Stream stream, bool metadataOnly)
where TColor : struct, IPackedPixel, IEquatable
{
this.InputStream = stream;
+ this.InputProcessor = new InputProcessor(stream, this.Temp);
// Check for the Start Of Image marker.
- this.ReadFull(this.Temp, 0, 2);
+ this.InputProcessor.ReadFull(this.Temp, 0, 2);
if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
@@ -209,7 +286,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.InputProcessor.ReadFull(this.Temp, 0, 2);
while (this.Temp[0] != 0xff)
{
// Strictly speaking, this is a format error. However, libjpeg is
@@ -230,7 +307,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.InputProcessor.ReadByte();
}
byte marker = this.Temp[1];
@@ -244,7 +321,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.InputProcessor.ReadByte();
}
// End Of Image.
@@ -266,7 +343,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.InputProcessor.ReadFull(this.Temp, 0, 2);
int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2;
if (remaining < 0)
{
@@ -280,16 +357,16 @@ namespace ImageSharp.Formats
case JpegConstants.Markers.SOF2:
this.IsProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining);
- if (configOnly && this.isJfif)
+ if (metadataOnly && this.isJfif)
{
return;
}
break;
case JpegConstants.Markers.DHT:
- if (configOnly)
+ if (metadataOnly)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
}
else
{
@@ -298,9 +375,9 @@ namespace ImageSharp.Formats
break;
case JpegConstants.Markers.DQT:
- if (configOnly)
+ if (metadataOnly)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
}
else
{
@@ -309,7 +386,7 @@ namespace ImageSharp.Formats
break;
case JpegConstants.Markers.SOS:
- if (configOnly)
+ if (metadataOnly)
{
return;
}
@@ -317,17 +394,17 @@ namespace ImageSharp.Formats
// when this is a progressive image this gets called a number of times
// need to know how many times this should be called in total.
this.ProcessStartOfScan(remaining);
- if (!this.IsProgressive)
+ if (this.InputProcessor.UnexpectedEndOfStreamReached || !this.IsProgressive)
{
- // if this is not a progressive image we can stop processing bytes as we now have the image data.
+ // if unexpeced EOF reached or this is not a progressive image we can stop processing bytes as we now have the image data.
processBytes = false;
}
break;
case JpegConstants.Markers.DRI:
- if (configOnly)
+ if (metadataOnly)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
}
else
{
@@ -348,7 +425,7 @@ namespace ImageSharp.Formats
if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
|| marker == JpegConstants.Markers.COM)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
}
else if (marker < JpegConstants.Markers.SOF0)
{
@@ -363,7 +440,60 @@ namespace ImageSharp.Formats
break;
}
}
+ }
+
+ ///
+ /// 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.InputProcessor.Bits = default(Bits);
+ this.MakeImage();
+ scan.DecodeBlocks(this);
+ }
+
+ ///
+ /// Process the blocks in into Jpeg image channels ( and )
+ /// The blocks are expected in a "raw" frequency-domain decoded format. We need to apply IDCT and unzigging to transform them into color-space blocks.
+ /// We can copy these blocks into -s afterwards.
+ ///
+ /// The pixel type
+ private void ProcessBlocksIntoJpegImageChannels()
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ Parallel.For(
+ 0,
+ this.ComponentCount,
+ componentIndex =>
+ {
+ JpegScanDecoder scanDecoder = default(JpegScanDecoder);
+ JpegScanDecoder.Init(&scanDecoder);
+
+ scanDecoder.ComponentIndex = componentIndex;
+ DecodedBlockMemento.Array blockArray = this.DecodedBlocks[componentIndex];
+ for (int i = 0; i < blockArray.Count; i++)
+ {
+ scanDecoder.LoadMemento(ref blockArray.Buffer[i]);
+ scanDecoder.ProcessBlockColors(this);
+ }
+ });
+ }
+ ///
+ /// Convert the pixel data in and/or into pixels of
+ ///
+ /// The pixel type
+ /// The destination image
+ private void ConvertJpegPixelsToImagePixels(Image image)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
if (this.grayImage.IsInitialized)
{
this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image);
@@ -375,7 +505,7 @@ namespace ImageSharp.Formats
if (!this.adobeTransformValid)
{
throw new ImageFormatException(
- "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
+ "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
}
// See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
@@ -414,239 +544,6 @@ namespace ImageSharp.Formats
}
}
- ///
- /// Dispose
- ///
- public void Dispose()
- {
- for (int i = 0; i < this.HuffmanTrees.Length; i++)
- {
- this.HuffmanTrees[i].Dispose();
- }
-
- this.ycbcrImage?.Dispose();
- this.Bytes.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
- ///
- /// The
- public bool DecodeBit()
- {
- if (this.Bits.UnreadBits == 0)
- {
- ErrorCodes errorCode = this.Bits.EnsureNBits(1, this);
- if (errorCode != ErrorCodes.NoError)
- {
- throw new MissingFF00Exception();
- }
- }
-
- bool ret = (this.Bits.Accumulator & this.Bits.Mask) != 0;
- this.Bits.UnreadBits--;
- this.Bits.Mask >>= 1;
- return ret;
- }
-
- ///
- /// 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
- public void ReadFull(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;
- }
-
- 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;
-
- this.Bytes.Fill(this.InputStream);
- }
- }
- }
-
- ///
- /// Decodes the given number of bits
- ///
- /// The number of bits to decode.
- /// The
- public uint DecodeBits(int count)
- {
- if (this.Bits.UnreadBits < count)
- {
- ErrorCodes errorCode = this.Bits.EnsureNBits(count, this);
- if (errorCode != ErrorCodes.NoError)
- {
- throw new MissingFF00Exception();
- }
- }
-
- uint ret = this.Bits.Accumulator >> (this.Bits.UnreadBits - count);
- ret = (uint)(ret & ((1 << count) - 1));
- this.Bits.UnreadBits -= count;
- this.Bits.Mask >>= count;
- return ret;
- }
-
- ///
- /// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
- ///
- /// The huffman value
- /// The
- public byte DecodeHuffman(ref HuffmanTree huffmanTree)
- {
- // Copy stuff to the stack:
- if (huffmanTree.Length == 0)
- {
- throw new ImageFormatException("Uninitialized Huffman table");
- }
-
- if (this.Bits.UnreadBits < 8)
- {
- ErrorCodes errorCode = this.Bits.EnsureNBits(8, this);
-
- if (errorCode == ErrorCodes.NoError)
- {
- ushort v = huffmanTree.Lut[(this.Bits.Accumulator >> (this.Bits.UnreadBits - HuffmanTree.LutSize)) & 0xFF];
-
- if (v != 0)
- {
- int n = (v & 0xFF) - 1;
- this.Bits.UnreadBits -= n;
- this.Bits.Mask >>= n;
- return (byte)(v >> 8);
- }
- }
- else
- {
- this.UnreadByteStuffedByte();
- }
- }
-
- int code = 0;
- for (int i = 0; i < HuffmanTree.MaxCodeLength; i++)
- {
- if (this.Bits.UnreadBits == 0)
- {
- ErrorCodes errorCode = this.Bits.EnsureNBits(1, this);
- if (errorCode != ErrorCodes.NoError)
- {
- throw new MissingFF00Exception();
- }
- }
-
- if ((this.Bits.Accumulator & this.Bits.Mask) != 0)
- {
- code |= 1;
- }
-
- this.Bits.UnreadBits--;
- this.Bits.Mask >>= 1;
-
- if (code <= huffmanTree.MaxCodes[i])
- {
- return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]];
- }
-
- code <<= 1;
- }
-
- throw new ImageFormatException("Bad Huffman code");
- }
-
- ///
- /// Gets the representing the channel at a given component index
- ///
- /// The component index
- /// The of the channel
- public JpegPixelArea GetDestinationChannel(int compIndex)
- {
- if (this.ComponentCount == 1)
- {
- return this.grayImage;
- }
- else
- {
- switch (compIndex)
- {
- case 0:
- return this.ycbcrImage.YChannel;
- case 1:
- return this.ycbcrImage.CbChannel;
- case 2:
- return this.ycbcrImage.CrChannel;
- case 3:
- return this.blackImage;
- default:
- throw new ImageFormatException("Too many components");
- }
- }
- }
-
- ///
- /// Optimized method to pack bytes to the image from the YCbCr color space.
- /// This is faster than implicit casting as it avoids double packing.
- ///
- /// The pixel format.
- /// The packed pixel.
- /// The y luminance component.
- /// The cb chroma component.
- /// The cr chroma component.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr)
- where TColor : struct, IPackedPixel, IEquatable
- {
- int ccb = cb - 128;
- int ccr = cr - 128;
-
- // Speed up the algorithm by removing floating point calculation
- // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result
- int r0 = 91881 * ccr; // (1.402F * 65536) + .5F
- int g0 = 22554 * ccb; // (0.34414F * 65536) + .5F
- int g1 = 46802 * ccr; // (0.71414F * 65536) + .5F
- int b0 = 116130 * ccb; // (1.772F * 65536) + .5F
-
- byte r = (byte)(y + (r0 >> 16)).Clamp(0, 255);
- byte g = (byte)(y - (g0 >> 16) - (g1 >> 16)).Clamp(0, 255);
- byte b = (byte)(y + (b0 >> 16)).Clamp(0, 255);
- packed.PackFromBytes(r, g, b, 255);
- }
-
///
/// Assigns the horizontal and vertical resolution to the image if it has a JFIF header.
///
@@ -1020,11 +917,11 @@ namespace ImageSharp.Formats
{
if (remaining < 12)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
return;
}
- this.ReadFull(this.Temp, 0, 12);
+ this.InputProcessor.ReadFull(this.Temp, 0, 12);
remaining -= 12;
if (this.Temp[0] == 'A' && this.Temp[1] == 'd' && this.Temp[2] == 'o' && this.Temp[3] == 'b'
@@ -1036,7 +933,7 @@ namespace ImageSharp.Formats
if (remaining > 0)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
}
}
@@ -1051,12 +948,12 @@ namespace ImageSharp.Formats
{
if (remaining < 6)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
return;
}
byte[] profile = new byte[remaining];
- this.ReadFull(profile, 0, remaining);
+ this.InputProcessor.ReadFull(profile, 0, remaining);
if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
&& profile[5] == '\0')
@@ -1073,11 +970,11 @@ namespace ImageSharp.Formats
{
if (remaining < 5)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
return;
}
- this.ReadFull(this.Temp, 0, 13);
+ this.InputProcessor.ReadFull(this.Temp, 0, 13);
remaining -= 13;
// TODO: We should be using constants for this.
@@ -1092,7 +989,7 @@ namespace ImageSharp.Formats
if (remaining > 0)
{
- this.Skip(remaining);
+ this.InputProcessor.Skip(remaining);
}
}
@@ -1110,7 +1007,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException("DHT has wrong length");
}
- this.ReadFull(this.Temp, 0, 17);
+ this.InputProcessor.ReadFull(this.Temp, 0, 17);
int tc = this.Temp[0] >> 4;
if (tc > HuffmanTree.MaxTc)
@@ -1125,7 +1022,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.InputProcessor,
+ this.Temp,
+ ref remaining);
}
}
@@ -1141,7 +1041,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException("DRI has wrong length");
}
- this.ReadFull(this.Temp, 0, remaining);
+ this.InputProcessor.ReadFull(this.Temp, 0, remaining);
this.RestartInterval = ((int)this.Temp[0] << 8) + (int)this.Temp[1];
}
@@ -1159,7 +1059,7 @@ namespace ImageSharp.Formats
bool done = false;
remaining--;
- byte x = this.ReadByte();
+ byte x = this.InputProcessor.ReadByte();
int tq = x & 0x0F;
if (tq > MaxTq)
{
@@ -1176,7 +1076,7 @@ namespace ImageSharp.Formats
}
remaining -= Block8x8F.ScalarCount;
- this.ReadFull(this.Temp, 0, Block8x8F.ScalarCount);
+ this.InputProcessor.ReadFull(this.Temp, 0, Block8x8F.ScalarCount);
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
@@ -1192,7 +1092,7 @@ namespace ImageSharp.Formats
}
remaining -= 2 * Block8x8F.ScalarCount;
- this.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount);
+ this.InputProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.ScalarCount);
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
@@ -1242,7 +1142,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Incorrect number of components");
}
- this.ReadFull(this.Temp, 0, remaining);
+ this.InputProcessor.ReadFull(this.Temp, 0, remaining);
// We only support 8-bit precision.
if (this.Temp[0] != 8)
@@ -1411,100 +1311,13 @@ namespace ImageSharp.Formats
this.MCUCountX = (this.ImageWidth + (8 * h0) - 1) / (8 * h0);
this.MCUCountY = (this.ImageHeight + (8 * v0) - 1) / (8 * v0);
+ // As a preparation for parallelizing Scan decoder, we also allocate DecodedBlocks in the non-progressive case!
for (int i = 0; i < this.ComponentCount; i++)
{
- int size = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor
+ int count = this.TotalMCUCount * this.ComponentArray[i].HorizontalFactor
* this.ComponentArray[i].VerticalFactor;
- this.DecodedBlocks[i] = new Block8x8F[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.Init(&scan, this, remaining);
- this.Bits = default(Bits);
- this.MakeImage();
- scan.ProcessBlocks(this);
- }
-
- ///
- /// Skips the next n bytes.
- ///
- /// The number of bytes to ignore.
- private void Skip(int count)
- {
- // Unread the overshot bytes, if any.
- if (this.Bytes.UnreadableBytes != 0)
- {
- if (this.Bits.UnreadBits >= 8)
- {
- this.UnreadByteStuffedByte();
- }
-
- this.Bytes.UnreadableBytes = 0;
+ this.DecodedBlocks[i] = new DecodedBlockMemento.Array(count);
}
-
- 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;
- }
-
- this.Bytes.Fill(this.InputStream);
- }
- }
-
- ///
- /// 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;
- }
- }
-
- ///
- /// 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.Benchmarks/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
index 9007ef75b6..26aad07b8d 100644
--- a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
+++ b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
@@ -86,6 +86,10 @@ namespace ImageSharp.Benchmarks.Image
[Setup]
public void ReadImages()
{
+ if (!Vector.IsHardwareAccelerated)
+ {
+ throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!");
+ }
// Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated);
this.ReadFilesImpl();
}
diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
index 5a27190dbf..305fac6369 100644
--- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
+++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
@@ -6,7 +6,7 @@
DebugAnyCPU{96188137-5FA6-4924-AB6E-4EFF79C6E0BB}
- Library
+ ExePropertiesImageSharpImageSharp.Sandbox46
@@ -24,6 +24,7 @@
prompt4true
+ falsepdbonly
@@ -33,17 +34,127 @@
prompt4true
+ false
+
+
+ ImageSharp.Sandbox46.Program
+
+ ..\..\packages\BenchmarkDotNet.0.10.2\lib\net45\BenchmarkDotNet.dll
+ True
+
+
+ ..\..\packages\BenchmarkDotNet.Core.0.10.2\lib\net45\BenchmarkDotNet.Core.dll
+ True
+
+
+ ..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.2\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll
+ True
+
+
+ ..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.2\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll
+ True
+
+
+ ..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll
+ True
+
+
+ ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll
+ True
+
+
+ ..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll
+ True
+
+
+ ..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll
+ True
+
+
+ ..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll
+ True
+
+
+
+ ..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll
+ True
+
+
+ ..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll
+ True
+
+
+ ..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll
+ True
+
+
+ ..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll
+ True
+
+
+
+ ..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll
+ True
+
+
+ ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll
+ True
+
+
+ ..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll
+ True
+
+
+ ..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll
+ True
+
+
+ ..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll
+ True
+
+
+ ..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll
+ True
+
+
+ ..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll
+ True
+
+
+ ..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll
+ True
+
+
+ ..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll
+ True
+ ..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dllTrue
@@ -199,22 +310,33 @@
Tests\TestUtilities\TestUtilityExtensions.cs
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+