Browse Source

Merge branch 'refs/heads/antonfirsov-progressive-playground'

af/merge-core
James Jackson-South 9 years ago
parent
commit
b6361a02c3
  1. 1
      src/ImageSharp.Formats.Jpeg/Components/Block8x8F.cs
  2. 124
      src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs
  3. 157
      src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs
  4. 101
      src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs
  5. 29
      src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs
  6. 95
      src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs
  7. 25
      src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs
  8. 71
      src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs
  9. 368
      src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs
  10. 6
      src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs
  11. 370
      src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
  12. 4
      src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.md
  13. 17
      src/ImageSharp.Formats.Jpeg/Components/Decoder/MissingFF00Exception.cs
  14. 5
      src/ImageSharp.Formats.Jpeg/JpegConstants.cs
  15. 599
      src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
  16. 4
      tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
  17. 124
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  18. 53
      tests/ImageSharp.Sandbox46/Program.cs
  19. 15
      tests/ImageSharp.Sandbox46/app.config
  20. 50
      tests/ImageSharp.Sandbox46/packages.config
  21. 2
      tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs
  22. 56
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  23. 29
      tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs

1
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;

124
src/ImageSharp.Formats.Jpeg/Components/Decoder/Bits.cs

@ -17,13 +17,13 @@ namespace ImageSharp.Formats.Jpg
/// <summary>
/// Gets or sets the accumulator.
/// </summary>
public uint Accumulator;
public int Accumulator;
/// <summary>
/// Gets or sets the mask.
/// <![CDATA[mask==1<<(unreadbits-1) when unreadbits>0, with mask==0 when unreadbits==0.]]>
/// </summary>
public uint Mask;
public int Mask;
/// <summary>
/// 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 &lt; n.
/// </summary>
/// <param name="n">The number of bits to ensure.</param>
/// <param name="decoder">Jpeg decoder</param>
/// <returns>Error code</returns>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
[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();
}
/// <summary>
/// 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 &lt; n.
/// This method does not throw. Returns <see cref="DecoderErrorCode"/> instead.
/// </summary>
/// <param name="n">The number of bits to ensure.</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>Error code</returns>
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;
}
/// <summary>
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==8
/// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="DecoderErrorCode"/></returns>
public DecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor)
{
return this.EnsureBitsStepImpl(ref inputProcessor);
}
if (this.UnreadBits >= n)
{
return JpegDecoderCore.ErrorCodes.NoError;
}
}
/// <summary>
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==1
/// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="DecoderErrorCode"/></returns>
public DecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor)
{
return this.EnsureBitsStepImpl(ref inputProcessor);
}
/// <summary>
/// Receive extend
/// </summary>
/// <param name="t">Byte</param>
/// <param name="decoder">Jpeg decoder</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>Read bits value</returns>
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;
}
/// <summary>
/// Receive extend
/// </summary>
/// <param name="t">Byte</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <param name="x">Read bits value</param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
}
}

157
src/ImageSharp.Formats.Jpeg/Components/Decoder/Bytes.cs

@ -16,6 +16,11 @@ namespace ImageSharp.Formats.Jpg
/// </summary>
internal struct Bytes : IDisposable
{
/// <summary>
/// Specifies the buffer size for <see cref="Buffer"/> and <see cref="BufferAsInt"/>
/// </summary>
public const int BufferSize = 4096;
/// <summary>
/// Gets or sets the buffer.
/// buffer[i:j] are the buffered bytes read from the underlying
@ -23,6 +28,11 @@ namespace ImageSharp.Formats.Jpg
/// </summary>
public byte[] Buffer;
/// <summary>
/// Values of <see cref="Buffer"/> converted to <see cref="int"/>-s
/// </summary>
public int[] BufferAsInt;
/// <summary>
/// Start of bytes read
/// </summary>
@ -39,7 +49,9 @@ namespace ImageSharp.Formats.Jpg
/// </summary>
public int UnreadableBytes;
private static readonly ArrayPool<byte> ArrayPool = ArrayPool<byte>.Create(4096, 50);
private static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(BufferSize, 50);
private static readonly ArrayPool<int> IntPool = ArrayPool<int>.Create(BufferSize, 50);
/// <summary>
/// Creates a new instance of the <see cref="Bytes"/>, and initializes it's buffer.
@ -47,7 +59,11 @@ namespace ImageSharp.Formats.Jpg
/// <returns>The bytes created</returns>
public static Bytes Create()
{
return new Bytes { Buffer = ArrayPool.Rent(4096) };
return new Bytes
{
Buffer = BytePool.Rent(BufferSize),
BufferAsInt = IntPool.Rent(BufferSize)
};
}
/// <summary>
@ -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;
}
/// <summary>
/// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <param name="errorCode">Error code</param>
/// <returns>The <see cref="byte"/></returns>
internal byte ReadByteStuffedByte(Stream inputStream, out JpegDecoderCore.ErrorCodes errorCode)
/// <param name="x">The result byte as <see cref="int"/></param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
/// <summary>
@ -127,30 +147,92 @@ namespace ImageSharp.Formats.Jpg
/// <param name="inputStream">Input stream</param>
/// <returns>The <see cref="byte"/></returns>
[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;
}
/// <summary>
/// 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 <see cref="DecoderErrorCode"/> instead.
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <param name="result">The result <see cref="byte"/> as out parameter</param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
/// <summary>
/// Same as <see cref="ReadByteUnsafe"/> but the result is an <see cref="int"/>
/// </summary>
/// <param name="inputStream">The input stream</param>
/// <param name="result">The result <see cref="int"/></param>
/// <returns>A <see cref="DecoderErrorCode"/></returns>
[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;
}
/// <summary>
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
/// </summary>
/// <exception cref="EOFException">Thrown when reached end of stream unexpectedly.</exception>
/// <param name="inputStream">Input stream</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Fill(Stream inputStream)
public void Fill(Stream inputStream)
{
DecoderErrorCode errorCode = this.FillUnsafe(inputStream);
errorCode.EnsureNoError();
}
/// <summary>
/// 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 <see cref="EOFException"/>, returns a <see cref="DecoderErrorCode"/> instead!
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
}
}

101
src/ImageSharp.Formats.Jpeg/Components/Decoder/DecodedBlockMemento.cs

@ -0,0 +1,101 @@
// <copyright file="DecodedBlockMemento.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Buffers;
/// <summary>
/// A structure to store unprocessed <see cref="Block8x8F"/> instances and their coordinates while scanning the image.
/// </summary>
internal struct DecodedBlockMemento
{
/// <summary>
/// A value indicating whether the <see cref="DecodedBlockMemento"/> instance is initialized.
/// </summary>
public bool Initialized;
/// <summary>
/// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
/// </summary>
public int Bx;
/// <summary>
/// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
/// </summary>
public int By;
/// <summary>
/// The <see cref="Block8x8F"/>
/// </summary>
public Block8x8F Block;
/// <summary>
/// Store the block data into a <see cref="DecodedBlockMemento"/> at the given index of an <see cref="DecodedBlockMemento.Array"/>.
/// </summary>
/// <param name="blockArray">The array <see cref="DecodedBlockMemento.Array"/></param>
/// <param name="index">The index in the array</param>
/// <param name="bx">X coordinate of the block</param>
/// <param name="by">Y coordinate of the block</param>
/// <param name="block">The <see cref="Block8x8F"/></param>
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;
}
/// <summary>
/// Because <see cref="System.Array.Length"/> has no information for rented arrays, we need to store the count and the buffer separately.
/// </summary>
public struct Array : IDisposable
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> used to pool data in <see cref="JpegDecoderCore.DecodedBlocks"/>.
/// Should always clean arrays when returning!
/// </summary>
private static readonly ArrayPool<DecodedBlockMemento> ArrayPool = ArrayPool<DecodedBlockMemento>.Create();
/// <summary>
/// Initializes a new instance of the <see cref="Array"/> struct. Rents a buffer.
/// </summary>
/// <param name="count">The number of valid <see cref="DecodedBlockMemento"/>-s</param>
public Array(int count)
{
this.Count = count;
this.Buffer = ArrayPool.Rent(count);
}
/// <summary>
/// Gets the number of actual <see cref="DecodedBlockMemento"/>-s inside <see cref="Buffer"/>
/// </summary>
public int Count { get; }
/// <summary>
/// Gets the rented buffer.
/// </summary>
public DecodedBlockMemento[] Buffer { get; private set; }
/// <summary>
/// Returns the rented buffer to the pool.
/// </summary>
public void Dispose()
{
if (this.Buffer != null)
{
ArrayPool.Return(this.Buffer, true);
this.Buffer = null;
}
}
}
}
}

29
src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderErrorCode.cs

@ -0,0 +1,29 @@
// <copyright file="DecoderErrorCode.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Represents "recoverable" decoder errors.
/// </summary>
internal enum DecoderErrorCode
{
/// <summary>
/// NoError
/// </summary>
NoError,
/// <summary>
/// MissingFF00
/// </summary>
// ReSharper disable once InconsistentNaming
MissingFF00,
/// <summary>
/// End of stream reached unexpectedly
/// </summary>
UnexpectedEndOfStream
}
}

95
src/ImageSharp.Formats.Jpeg/Components/Decoder/DecoderThrowHelper.cs

@ -0,0 +1,95 @@
// <copyright file="DecoderThrowHelper.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System;
using System.Runtime.CompilerServices;
/// <summary>
/// Encapsulates exception thrower methods for the Jpeg Encoder
/// </summary>
internal static class DecoderThrowHelper
{
/// <summary>
/// Throws an exception that belongs to the given <see cref="DecoderErrorCode"/>
/// </summary>
/// <param name="errorCode">The <see cref="DecoderErrorCode"/></param>
[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);
}
}
/// <summary>
/// Throws an exception if the given <see cref="DecoderErrorCode"/> defines an error.
/// </summary>
/// <param name="errorCode">The <see cref="DecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureNoError(this DecoderErrorCode errorCode)
{
if (errorCode != DecoderErrorCode.NoError)
{
ThrowExceptionForErrorCode(errorCode);
}
}
/// <summary>
/// Throws an exception if the given <see cref="DecoderErrorCode"/> is <see cref="DecoderErrorCode.UnexpectedEndOfStream"/>.
/// </summary>
/// <param name="errorCode">The <see cref="DecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureNoEOF(this DecoderErrorCode errorCode)
{
if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
{
errorCode.ThrowExceptionForErrorCode();
}
}
/// <summary>
/// Encapsulates methods throwing different flavours of <see cref="ImageFormatException"/>-s.
/// </summary>
public static class ThrowImageFormatException
{
/// <summary>
/// Throws "Fill called when unread bytes exist".
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void FillCalledWhenUnreadBytesExist()
{
throw new ImageFormatException("Fill called when unread bytes exist!");
}
/// <summary>
/// Throws "Bad Huffman code".
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void BadHuffmanCode()
{
throw new ImageFormatException("Bad Huffman code!");
}
/// <summary>
/// Throws "Uninitialized Huffman table".
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void UninitializedHuffmanTable()
{
throw new ImageFormatException("Uninitialized Huffman table");
}
}
}
}

25
src/ImageSharp.Formats.Jpeg/Components/Decoder/EOFException.cs

@ -0,0 +1,25 @@
// <copyright file="EOFException.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System;
/// <summary>
/// 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
/// </summary>
internal class EOFException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="EOFException"/> class.
/// </summary>
public EOFException()
: base("Reached end of stream before proceeding EOI marker!")
{
}
}
}

71
src/ImageSharp.Formats.Jpeg/Components/Decoder/HuffmanTree.cs

@ -45,7 +45,7 @@ namespace ImageSharp.Formats.Jpg
/// <summary>
/// The log-2 size of the Huffman decoder's look-up table.
/// </summary>
public const int LutSize = 8;
public const int LutSizeLog2 = 8;
/// <summary>
/// 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.
/// </summary>
public ushort[] Lut;
public int[] Lut;
/// <summary>
/// Gets the the decoded values, sorted by their encoding.
/// </summary>
public byte[] Values;
public int[] Values;
/// <summary>
/// Gets the array of minimum codes.
@ -82,11 +82,11 @@ namespace ImageSharp.Formats.Jpg
/// </summary>
public int[] Indices;
private static readonly ArrayPool<ushort> UshortBuffer = ArrayPool<ushort>.Create(1 << LutSize, 50);
private static readonly ArrayPool<int> IntPool256 = ArrayPool<int>.Create(MaxNCodes, 50);
private static readonly ArrayPool<byte> ByteBuffer = ArrayPool<byte>.Create(MaxNCodes, 50);
private static readonly ArrayPool<byte> BytePool256 = ArrayPool<byte>.Create(MaxNCodes, 50);
private static readonly ArrayPool<int> IntBuffer = ArrayPool<int>.Create(MaxCodeLength, 50);
private static readonly ArrayPool<int> CodesPool16 = ArrayPool<int>.Create(MaxCodeLength, 50);
/// <summary>
/// Creates and initializes an array of <see cref="HuffmanTree" /> instances of size <see cref="NumberOfTrees" />
@ -111,21 +111,21 @@ namespace ImageSharp.Formats.Jpg
/// </summary>
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);
}
/// <summary>
/// Internal part of the DHT processor, whatever does it mean
/// </summary>
/// <param name="decoder">The decoder instance</param>
/// <param name="inputProcessor">The decoder instance</param>
/// <param name="defineHuffmanTablesData">The temporal buffer that holds the data that has been read from the Jpeg stream</param>
/// <param name="remaining">Remaining bits</param>
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
}
}
/// <summary>
/// Gets the value for the given code and index.
/// </summary>
/// <param name="code">The code</param>
/// <param name="codeLength">The code length</param>
/// <returns>The value</returns>
public int GetValue(int code, int codeLength)
{
return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]];
}
/// <summary>
/// Initializes the Huffman tree
/// </summary>
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);
}
}
}

368
src/ImageSharp.Formats.Jpeg/Components/Decoder/InputProcessor.cs

@ -0,0 +1,368 @@
// <copyright file="InputProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System;
using System.IO;
using System.Runtime.CompilerServices;
/// <summary>
/// Encapsulates stream reading and processing data and operations for <see cref="JpegDecoderCore"/>.
/// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
/// </summary>
internal struct InputProcessor : IDisposable
{
/// <summary>
/// Holds the unprocessed bits that have been taken from the byte-stream.
/// </summary>
public Bits Bits;
/// <summary>
/// The byte buffer
/// </summary>
public Bytes Bytes;
/// <summary>
/// Initializes a new instance of the <see cref="InputProcessor"/> struct.
/// </summary>
/// <param name="inputStream">The input <see cref="Stream"/></param>
/// <param name="temp">Temporal buffer, same as <see cref="JpegDecoderCore.Temp"/></param>
public InputProcessor(Stream inputStream, byte[] temp)
{
this.Bits = default(Bits);
this.Bytes = Bytes.Create();
this.InputStream = inputStream;
this.Temp = temp;
this.UnexpectedEndOfStreamReached = false;
}
/// <summary>
/// Gets the input stream
/// </summary>
public Stream InputStream { get; }
/// <summary>
/// Gets the temporal buffer, same instance as <see cref="JpegDecoderCore.Temp"/>
/// </summary>
public byte[] Temp { get; }
/// <summary>
/// Gets or sets a value indicating whether an unexpected EOF reached in <see cref="InputStream"/>.
/// </summary>
public bool UnexpectedEndOfStreamReached { get; set; }
/// <summary>
/// If errorCode indicates unexpected EOF, sets <see cref="UnexpectedEndOfStreamReached"/> to true and returns false.
/// Calls <see cref="DecoderThrowHelper.EnsureNoError"/> and returns true otherwise.
/// </summary>
/// <param name="errorCode">The <see cref="DecoderErrorCode"/></param>
/// <returns><see cref="bool"/> indicating whether everything is OK</returns>
public bool CheckEOFEnsureNoError(DecoderErrorCode errorCode)
{
if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
{
this.UnexpectedEndOfStreamReached = true;
return false;
}
errorCode.EnsureNoError();
return true;
}
/// <summary>
/// If errorCode indicates unexpected EOF, sets <see cref="UnexpectedEndOfStreamReached"/> to true and returns false.
/// Returns true otherwise.
/// </summary>
/// <param name="errorCode">The <see cref="DecoderErrorCode"/></param>
/// <returns><see cref="bool"/> indicating whether everything is OK</returns>
public bool CheckEOF(DecoderErrorCode errorCode)
{
if (errorCode == DecoderErrorCode.UnexpectedEndOfStream)
{
this.UnexpectedEndOfStreamReached = true;
return false;
}
return true;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
this.Bytes.Dispose();
}
/// <summary>
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
/// </summary>
/// <returns>The <see cref="byte" /></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
return this.Bytes.ReadByte(this.InputStream);
}
/// <summary>
/// Decodes a single bit
/// TODO: This method (and also the usages) could be optimized by batching!
/// </summary>
/// <param name="result">The decoded bit as a <see cref="bool"/></param>
/// <returns>The <see cref="DecoderErrorCode" /></returns>
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;
}
/// <summary>
/// Reads exactly length bytes into data. It does not care about byte stuffing.
/// Does not throw on errors, returns <see cref="JpegDecoderCore"/> instead!
/// </summary>
/// <param name="data">The data to write to.</param>
/// <param name="offset">The offset in the source buffer</param>
/// <param name="length">The number of bytes to read</param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
/// <summary>
/// Decodes the given number of bits
/// </summary>
/// <param name="count">The number of bits to decode.</param>
/// <param name="result">The <see cref="uint" /> result</param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
/// <summary>
/// Extracts the next Huffman-coded value from the bit-stream into result, decoded according to the given value.
/// </summary>
/// <param name="huffmanTree">The huffman value</param>
/// <param name="result">The decoded <see cref="byte" /></param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
/// <summary>
/// Skips the next n bytes.
/// </summary>
/// <param name="count">The number of bytes to ignore.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Skip(int count)
{
DecoderErrorCode errorCode = this.SkipUnsafe(count);
errorCode.EnsureNoError();
}
/// <summary>
/// Skips the next n bytes.
/// Does not throw, returns <see cref="DecoderErrorCode"/> instead!
/// </summary>
/// <param name="count">The number of bytes to ignore.</param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
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;
}
/// <summary>
/// Reads exactly length bytes into data. It does not care about byte stuffing.
/// </summary>
/// <param name="data">The data to write to.</param>
/// <param name="offset">The offset in the source buffer</param>
/// <param name="length">The number of bytes to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadFull(byte[] data, int offset, int length)
{
DecoderErrorCode errorCode = this.ReadFullUnsafe(data, offset, length);
errorCode.EnsureNoError();
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
/// <summary>
/// Receive extend
/// </summary>
/// <param name="t">Byte</param>
/// <param name="x">Read bits value</param>
/// <returns>The <see cref="DecoderErrorCode"/></returns>
public DecoderErrorCode ReceiveExtendUnsafe(int t, out int x)
{
return this.Bits.ReceiveExtendUnsafe(t, ref this, out x);
}
}
}

6
src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.ComputationData.cs

@ -13,7 +13,7 @@ namespace ImageSharp.Formats.Jpg
internal unsafe partial struct JpegScanDecoder
{
/// <summary>
/// Holds the "large" data blocks needed for computations
/// Holds the "large" data blocks needed for computations.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ComputationData
@ -44,12 +44,12 @@ namespace ImageSharp.Formats.Jpg
public UnzigData Unzig;
/// <summary>
/// The no-idea-what's this data
/// The buffer storing the <see cref="ComponentScan"/>-s for each component
/// </summary>
public fixed byte ScanData[3 * JpegDecoderCore.MaxComponents];
/// <summary>
/// The DC component values
/// The DC values for each component
/// </summary>
public fixed int Dc[JpegDecoderCore.MaxComponents];

370
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.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp.Formats.Jpg
{
@ -10,20 +9,39 @@ namespace ImageSharp.Formats.Jpg
using System.Runtime.CompilerServices;
/// <summary>
/// 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 (<see cref="DecodeBlocks"/>) 2. JpegBlockProcessor for processing (<see cref="ProcessBlockColors"/>)
/// <see cref="zigStart"/> and <see cref="zigEnd"/> are the spectral selection bounds.
/// <see cref="ah"/> and <see cref="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.
/// </summary>
internal unsafe partial struct JpegScanDecoder
{
/// <summary>
/// The AC table index
/// </summary>
private const int AcTableIndex = 1;
public const int AcTableIndex = 1;
/// <summary>
/// The DC table index
/// </summary>
private const int DcTableIndex = 0;
public const int DcTableIndex = 0;
/// <summary>
/// The current component index
/// </summary>
public int ComponentIndex;
/// <summary>
/// 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
/// </summary>
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.
/// <summary>
/// Start index of the zig-zag selection bound
/// </summary>
@ -75,11 +78,6 @@ namespace ImageSharp.Formats.Jpg
/// </summary>
private int componentScanCount;
/// <summary>
/// The current component index
/// </summary>
private int componentIndex;
/// <summary>
/// Horizontal sampling factor at the current component index
/// </summary>
@ -88,36 +86,80 @@ namespace ImageSharp.Formats.Jpg
/// <summary>
/// End-of-Band run, specified in section G.1.2.2.
/// </summary>
private ushort eobRun;
private int eobRun;
/// <summary>
/// The <see cref="ComputationData"/> buffer
/// Pointers to elements of <see cref="data"/>
/// </summary>
private ComputationData data;
private DataPointers pointers;
/// <summary>
/// Pointers to elements of <see cref="data"/>
/// The <see cref="ComputationData"/> buffer
/// </summary>
private DataPointers pointers;
private ComputationData data;
/// <summary>
/// Initializes the default instance after creation.
/// Initializes a default-constructed <see cref="JpegScanDecoder"/> instance for reading data from <see cref="JpegDecoderCore"/>-s stream.
/// </summary>
/// <param name="p">Pointer to <see cref="JpegScanDecoder"/> on the stack</param>
/// <param name="decoder">The <see cref="JpegDecoderCore"/> instance</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
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);
}
/// <summary>
/// Initializes a default-constructed <see cref="JpegScanDecoder"/> instance, filling the data and setting the pointers.
/// </summary>
/// <param name="p">Pointer to <see cref="JpegScanDecoder"/> on the stack</param>
public static void Init(JpegScanDecoder* p)
{
p->data = ComputationData.Create();
p->pointers = new DataPointers(&p->data);
p->InitImpl(decoder, remaining);
}
/// <summary>
/// Reads the blocks from the <see cref="JpegDecoderCore"/>-s stream, and processes them into the corresponding <see cref="JpegPixelArea"/> instances.
/// Loads the data from the given <see cref="DecodedBlockMemento"/> into the block.
/// </summary>
/// <param name="memento">The <see cref="DecodedBlockMemento"/></param>
public void LoadMemento(ref DecodedBlockMemento memento)
{
this.bx = memento.Bx;
this.by = memento.By;
this.data.Block = memento.Block;
}
/// <summary>
/// Read Huffman data from Jpeg scans in <see cref="JpegDecoderCore.InputStream"/>,
/// and decode it as <see cref="Block8x8F"/> into <see cref="JpegDecoderCore.DecodedBlocks"/>.
///
/// 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 &gt; 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
/// </summary>
/// <param name="decoder">The <see cref="JpegDecoderCore"/> instance</param>
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
}
}
/// <summary>
/// Dequantize, perform the inverse DCT and store the block to the into the corresponding <see cref="JpegPixelArea"/> instances.
/// </summary>
/// <param name="decoder">The <see cref="JpegDecoderCore"/> instance</param>
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);
}
/// <summary>
/// The implementation part of <see cref="Init"/> as an instance method.
/// The implementation part of <see cref="InitStreamReading"/> as an instance method.
/// </summary>
/// <param name="decoder">The <see cref="JpegDecoderCore"/></param>
/// <param name="remaining">The remaining bytes</param>
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
}
/// <summary>
/// Process the current block at (<see cref="bx"/>, <see cref="by"/>)
/// Read the current the current block at (<see cref="bx"/>, <see cref="by"/>) from the decoders stream
/// </summary>
/// <param name="decoder">The decoder</param>
/// <param name="i">The index of the scan</param>
private void ProcessBlockImpl(JpegDecoderCore decoder, int i)
/// <param name="scanIndex">The index of the scan</param>
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;
}
/// <summary>
@ -417,10 +474,10 @@ namespace ImageSharp.Formats.Jpg
/// <returns>The index</returns>
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
/// <summary>
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
/// </summary>
/// <param name="decoder">The decoder instance</param>
/// <param name="bp">The <see cref="InputProcessor"/> instance</param>
/// <param name="h">The Huffman tree</param>
/// <param name="delta">The low transform offset</param>
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 <paramref name="nz" /> >= 0, the first <paramref name="nz" /> zero entries are skipped over.
/// </summary>
/// <param name="decoder">The decoder</param>
/// <param name="bp">The <see cref="InputProcessor"/></param>
/// <param name="zig">The zig-zag start index</param>
/// <param name="nz">The non-zero entry</param>
/// <param name="delta">The low transform offset</param>
/// <returns>The <see cref="int" /></returns>
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;

4
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`

17
src/ImageSharp.Formats.Jpeg/Components/Decoder/MissingFF00Exception.cs

@ -0,0 +1,17 @@
// <copyright file="MissingFF00Exception.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats.Jpg
{
using System;
/// <summary>
/// The missing ff00 exception.
/// </summary>
// ReSharper disable once InconsistentNaming
internal class MissingFF00Exception : Exception
{
}
}

5
src/ImageSharp.Formats.Jpeg/JpegConstants.cs

@ -86,6 +86,11 @@ namespace ImageSharp.Formats
/// </summary>
public const byte XFF = 0xff;
/// <summary>
/// Same as <see cref="XFF"/> but of type <see cref="int"/>
/// </summary>
public const int XFFInt = XFF;
/// <summary>
/// Start of Image
/// </summary>

599
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
/// </summary>
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
/// <summary>
/// Holds the unprocessed bits that have been taken from the byte-stream.
/// The maximum number of quantization tables
/// </summary>
public Bits Bits;
public const int MaxTq = 3;
/// <summary>
/// The byte buffer.
/// </summary>
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
/// <summary>
/// The maximum number of quantization tables
/// Encapsulates stream reading and processing data and operations for <see cref="JpegDecoderCore"/>.
/// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
/// </summary>
private const int MaxTq = 3;
public InputProcessor InputProcessor;
#pragma warning restore SA401
/// <summary>
/// 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();
}
/// <summary>
/// ReadByteStuffedByte was throwing exceptions on normal execution path (very inefficent)
/// It's better tho have an error code for this!
/// </summary>
internal enum ErrorCodes
{
/// <summary>
/// NoError
/// </summary>
NoError,
/// <summary>
/// MissingFF00
/// </summary>
// ReSharper disable once InconsistentNaming
MissingFF00
this.DecodedBlocks = new DecodedBlockMemento.Array[MaxComponents];
}
/// <summary>
@ -123,9 +102,9 @@ namespace ImageSharp.Formats
/// <summary>
/// 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)
/// </summary>
public Block8x8F[][] DecodedBlocks { get; }
public DecodedBlockMemento.Array[] DecodedBlocks { get; }
/// <summary>
/// Gets the quantization tables, in zigzag order.
@ -184,20 +163,118 @@ namespace ImageSharp.Formats
public int TotalMCUCount => this.MCUCountX * this.MCUCountY;
/// <summary>
/// Decodes the image from the specified this._stream and sets
/// Decodes the image from the specified <see cref="Stream"/> and sets
/// the data to image.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The image, where the data should be set to.</param>
/// <param name="stream">The stream, where the image should be.</param>
/// <param name="configOnly">Whether to decode metadata only.</param>
public void Decode<TColor>(Image<TColor> image, Stream stream, bool configOnly)
/// <param name="metadataOnly">Whether to decode metadata only.</param>
public void Decode<TColor>(Image<TColor> image, Stream stream, bool metadataOnly)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
this.ProcessStream(image, stream, metadataOnly);
if (!metadataOnly)
{
this.ProcessBlocksIntoJpegImageChannels<TColor>();
this.ConvertJpegPixelsToImagePixels(image);
}
}
/// <summary>
/// Dispose
/// </summary>
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();
}
/// <summary>
/// Gets the <see cref="JpegPixelArea"/> representing the channel at a given component index
/// </summary>
/// <param name="compIndex">The component index</param>
/// <returns>The <see cref="JpegPixelArea"/> of the channel</returns>
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");
}
}
}
/// <summary>
/// Optimized method to pack bytes to the image from the YCbCr color space.
/// This is faster than implicit casting as it avoids double packing.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PackYcbCr<TColor>(ref TColor packed, byte y, byte cb, byte cr)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
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);
}
/// <summary>
/// Read metadata from stream and read the blocks in the scans into <see cref="DecodedBlocks"/>.
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/></param>
/// <param name="stream">The stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param>
private void ProcessStream<TColor>(Image<TColor> image, Stream stream, bool metadataOnly)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
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;
}
}
}
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Missing SOF Marker
/// SOS has wrong length
/// </exception>
private void ProcessStartOfScan(int remaining)
{
JpegScanDecoder scan = default(JpegScanDecoder);
JpegScanDecoder.InitStreamReading(&scan, this, remaining);
this.InputProcessor.Bits = default(Bits);
this.MakeImage();
scan.DecodeBlocks(this);
}
/// <summary>
/// Process the blocks in <see cref="DecodedBlocks"/> into Jpeg image channels (<see cref="YCbCrImage"/> and <see cref="JpegPixelArea"/>)
/// 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 <see cref="JpegPixelArea"/>-s afterwards.
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
private void ProcessBlocksIntoJpegImageChannels<TColor>()
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
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);
}
});
}
/// <summary>
/// Convert the pixel data in <see cref="YCbCrImage"/> and/or <see cref="JpegPixelArea"/> into pixels of <see cref="Image{TColor}"/>
/// </summary>
/// <typeparam name="TColor">The pixel type</typeparam>
/// <param name="image">The destination image</param>
private void ConvertJpegPixelsToImagePixels<TColor>(Image<TColor> image)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
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
}
}
/// <summary>
/// Dispose
/// </summary>
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();
}
/// <summary>
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
/// </summary>
/// <returns>The <see cref="byte" /></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
return this.Bytes.ReadByte(this.InputStream);
}
/// <summary>
/// Decodes a single bit
/// </summary>
/// <returns>The <see cref="bool" /></returns>
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;
}
/// <summary>
/// Reads exactly length bytes into data. It does not care about byte stuffing.
/// </summary>
/// <param name="data">The data to write to.</param>
/// <param name="offset">The offset in the source buffer</param>
/// <param name="length">The number of bytes to read</param>
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);
}
}
}
/// <summary>
/// Decodes the given number of bits
/// </summary>
/// <param name="count">The number of bits to decode.</param>
/// <returns>The <see cref="uint" /></returns>
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;
}
/// <summary>
/// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
/// </summary>
/// <param name="huffmanTree">The huffman value</param>
/// <returns>The <see cref="byte" /></returns>
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");
}
/// <summary>
/// Gets the <see cref="JpegPixelArea"/> representing the channel at a given component index
/// </summary>
/// <param name="compIndex">The component index</param>
/// <returns>The <see cref="JpegPixelArea"/> of the channel</returns>
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");
}
}
}
/// <summary>
/// Optimized method to pack bytes to the image from the YCbCr color space.
/// This is faster than implicit casting as it avoids double packing.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="packed">The packed pixel.</param>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PackYcbCr<TColor>(ref TColor packed, byte y, byte cb, byte cr)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
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);
}
/// <summary>
/// Assigns the horizontal and vertical resolution to the image if it has a JFIF header.
/// </summary>
@ -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];
}
}
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Missing SOF Marker
/// SOS has wrong length
/// </exception>
private void ProcessStartOfScan(int remaining)
{
JpegScanDecoder scan = default(JpegScanDecoder);
JpegScanDecoder.Init(&scan, this, remaining);
this.Bits = default(Bits);
this.MakeImage();
scan.ProcessBlocks(this);
}
/// <summary>
/// Skips the next n bytes.
/// </summary>
/// <param name="count">The number of bytes to ignore.</param>
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);
}
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
/// <summary>
/// The EOF (End of File exception).
/// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker
/// </summary>
internal class EOFException : Exception
{
}
/// <summary>
/// The missing ff00 exception.
/// </summary>
// ReSharper disable once InconsistentNaming
internal class MissingFF00Exception : Exception
{
}
}
}
}

4
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();
}

124
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -6,7 +6,7 @@
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{96188137-5FA6-4924-AB6E-4EFF79C6E0BB}</ProjectGuid>
<OutputType>Library</OutputType>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ImageSharp</RootNamespace>
<AssemblyName>ImageSharp.Sandbox46</AssemblyName>
@ -24,6 +24,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -33,17 +34,127 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<StartupObject>ImageSharp.Sandbox46.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="BenchmarkDotNet, Version=0.10.1.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\..\packages\BenchmarkDotNet.0.10.2\lib\net45\BenchmarkDotNet.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="BenchmarkDotNet.Core, Version=0.10.1.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\..\packages\BenchmarkDotNet.Core.0.10.2\lib\net45\BenchmarkDotNet.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="BenchmarkDotNet.Diagnostics.Windows, Version=0.10.1.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.2\lib\net45\BenchmarkDotNet.Diagnostics.Windows.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="BenchmarkDotNet.Toolchains.Roslyn, Version=0.10.1.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.2\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Diagnostics.Tracing.TraceEvent, Version=1.0.41.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.AppContext, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.AppContext.4.1.0\lib\net46\System.AppContext.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Collections.Immutable, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Console, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Console.4.0.0\lib\net46\System.Console.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.FileVersionInfo, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.FileVersionInfo.4.0.0\lib\net46\System.Diagnostics.FileVersionInfo.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Diagnostics.StackTrace, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.StackTrace.4.0.1\lib\net46\System.Diagnostics.StackTrace.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.FileSystem, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IO.FileSystem.4.0.1\lib\net46\System.IO.FileSystem.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.FileSystem.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IO.FileSystem.Primitives.4.0.1\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Management" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Numerics.Vectors.4.1.1\lib\net46\System.Numerics.Vectors.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.Encoding, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.Primitives, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Text.Encoding.CodePages, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Text.Encoding.CodePages.4.0.1\lib\net46\System.Text.Encoding.CodePages.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.0.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Threading.Thread, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.XmlDocument, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Xml.XmlDocument.4.0.1\lib\net46\System.Xml.XmlDocument.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.XPath, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Xml.XPath.4.0.1\lib\net46\System.Xml.XPath.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.XPath.XDocument, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Xml.XPath.XDocument.4.0.1\lib\net46\System.Xml.XPath.XDocument.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
<Private>True</Private>
@ -199,22 +310,33 @@
<Compile Include="..\ImageSharp.Tests\TestUtilities\TestUtilityExtensions.cs">
<Link>Tests\TestUtilities\TestUtilityExtensions.cs</Link>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
<None Include="README.md" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Folder Include="Benchmarks\" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.2.0-beta4-build1194\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.2.0-beta4-build1194\build\net20\xunit.runner.visualstudio.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets'))" />
</Target>
<Import Project="..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets" Condition="Exists('..\..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

53
tests/ImageSharp.Sandbox46/Program.cs

@ -0,0 +1,53 @@
// <copyright file="Program.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Sandbox46
{
using System;
using System.Runtime.DesignerServices;
using ImageSharp.Tests;
using Xunit.Abstractions;
public class Program
{
private class ConsoleOutput : ITestOutputHelper
{
public void WriteLine(string message)
{
Console.WriteLine(message);
}
public void WriteLine(string format, params object[] args)
{
Console.WriteLine(format, args);
}
}
/// <summary>
/// The main entry point. Useful for executing benchmarks and performance unit tests manually,
/// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests.
/// </summary>
/// <param name="args">
/// The arguments to pass to the program.
/// </param>
public static void Main(string[] args)
{
RunDecodeJpegProfilingTests();
}
private static void RunDecodeJpegProfilingTests()
{
Console.WriteLine("RunDecodeJpegProfilingTests...");
JpegProfilingBenchmarks benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput());
foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData)
{
string fileName = (string)data[0];
benchmarks.DecodeJpeg(fileName);
}
}
}
}

15
tests/ImageSharp.Sandbox46/app.config

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.3.0.0" newVersion="1.3.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

50
tests/ImageSharp.Sandbox46/packages.config

@ -1,5 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BenchmarkDotNet" version="0.10.2" targetFramework="net461" />
<package id="BenchmarkDotNet.Core" version="0.10.2" targetFramework="net461" />
<package id="BenchmarkDotNet.Diagnostics.Windows" version="0.10.2" targetFramework="net461" />
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.2" targetFramework="net461" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net461" />
<package id="Microsoft.CodeAnalysis.Common" version="1.3.2" targetFramework="net461" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.3.2" targetFramework="net461" />
<package id="Microsoft.Diagnostics.Tracing.TraceEvent" version="1.0.41" targetFramework="net461" />
<package id="System.AppContext" version="4.1.0" targetFramework="net461" />
<package id="System.Collections" version="4.0.11" targetFramework="net461" />
<package id="System.Collections.Concurrent" version="4.0.12" targetFramework="net461" />
<package id="System.Collections.Immutable" version="1.2.0" targetFramework="net461" />
<package id="System.Console" version="4.0.0" targetFramework="net461" />
<package id="System.Diagnostics.Debug" version="4.0.11" targetFramework="net461" />
<package id="System.Diagnostics.FileVersionInfo" version="4.0.0" targetFramework="net461" />
<package id="System.Diagnostics.StackTrace" version="4.0.1" targetFramework="net461" />
<package id="System.Diagnostics.Tools" version="4.0.1" targetFramework="net461" />
<package id="System.Dynamic.Runtime" version="4.0.11" targetFramework="net461" />
<package id="System.Globalization" version="4.0.11" targetFramework="net461" />
<package id="System.IO.FileSystem" version="4.0.1" targetFramework="net461" />
<package id="System.IO.FileSystem.Primitives" version="4.0.1" targetFramework="net461" />
<package id="System.Linq" version="4.1.0" targetFramework="net461" />
<package id="System.Linq.Expressions" version="4.1.0" targetFramework="net461" />
<package id="System.Numerics.Vectors" version="4.1.1" targetFramework="net461" />
<package id="System.Reflection" version="4.1.0" targetFramework="net461" />
<package id="System.Reflection.Metadata" version="1.3.0" targetFramework="net461" />
<package id="System.Reflection.Primitives" version="4.0.1" targetFramework="net461" />
<package id="System.Resources.ResourceManager" version="4.0.1" targetFramework="net461" />
<package id="System.Runtime" version="4.1.0" targetFramework="net461" />
<package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net461" />
<package id="System.Runtime.Handles" version="4.0.1" targetFramework="net461" />
<package id="System.Runtime.InteropServices" version="4.1.0" targetFramework="net461" />
<package id="System.Runtime.Numerics" version="4.0.1" targetFramework="net461" />
<package id="System.Security.Cryptography.Algorithms" version="4.2.0" targetFramework="net461" />
<package id="System.Security.Cryptography.Encoding" version="4.0.0" targetFramework="net461" />
<package id="System.Security.Cryptography.Primitives" version="4.0.0" targetFramework="net461" />
<package id="System.Security.Cryptography.X509Certificates" version="4.1.0" targetFramework="net461" />
<package id="System.Text.Encoding" version="4.0.11" targetFramework="net461" />
<package id="System.Text.Encoding.CodePages" version="4.0.1" targetFramework="net461" />
<package id="System.Text.Encoding.Extensions" version="4.0.11" targetFramework="net461" />
<package id="System.Threading" version="4.0.11" targetFramework="net461" />
<package id="System.Threading.Tasks" version="4.0.11" targetFramework="net461" />
<package id="System.Threading.Tasks.Extensions" version="4.0.0" targetFramework="net461" />
<package id="System.Threading.Tasks.Parallel" version="4.0.1" targetFramework="net461" />
<package id="System.Threading.Thread" version="4.0.0" targetFramework="net461" />
<package id="System.Xml.ReaderWriter" version="4.0.11" targetFramework="net461" />
<package id="System.Xml.XDocument" version="4.0.11" targetFramework="net461" />
<package id="System.Xml.XmlDocument" version="4.0.1" targetFramework="net461" />
<package id="System.Xml.XPath" version="4.0.1" targetFramework="net461" />
<package id="System.Xml.XPath.XDocument" version="4.0.1" targetFramework="net461" />
<package id="xunit" version="2.2.0-beta4-build3444" targetFramework="net461" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
<package id="xunit.assert" version="2.2.0-beta4-build3444" targetFramework="net461" />

2
tests/ImageSharp.Tests/Formats/Jpg/BadEofJpegTests.cs

@ -36,7 +36,7 @@ namespace ImageSharp.Tests
provider.Utility.SaveTestOutputFile(image, "bmp");
}
// [Theory] // TODO: #18
[Theory] // TODO: #18
[WithFile(TestImages.Jpeg.Progressive.Bad.BadEOF, PixelTypes.Color)]
public void LoadProgressiveImage<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPackedPixel, IEquatable<TColor>

56
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -7,6 +7,9 @@
namespace ImageSharp.Tests
{
using System;
using System.IO;
using ImageSharp.Formats;
using Xunit;
@ -30,7 +33,7 @@ namespace ImageSharp.Tests
provider.Utility.SaveTestOutputFile(image, "bmp");
}
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)]
public void OpenProgressiveJpeg_SaveBmp<TColor>(TestImageProvider<TColor> provider)
@ -41,5 +44,56 @@ namespace ImageSharp.Tests
provider.Utility.SaveTestOutputFile(image, "bmp");
}
[Theory]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 75)]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 100)]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 75)]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)]
[WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)]
public void DecodeGenerated_SaveBmp<TColor>(
TestImageProvider<TColor> provider,
JpegSubsample subsample,
int quality)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
Image<TColor> image = provider.GetImage();
JpegEncoder encoder = new JpegEncoder() { Subsample = subsample, Quality = quality };
byte[] data = new byte[65536];
using (MemoryStream ms = new MemoryStream(data))
{
image.Save(ms, encoder);
}
// TODO: Automatic image comparers could help here a lot :P
Image<TColor> mirror = provider.Factory.CreateImage(data);
provider.Utility.TestName += $"_{subsample}_Q{quality}";
provider.Utility.SaveTestOutputFile(mirror, "bmp");
}
[Theory]
[WithSolidFilledImages(42, 88, 255, 0, 0, PixelTypes.StandardImageClass)]
public void DecodeGenerated_MetadataOnly<TColor>(
TestImageProvider<TColor> provider)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
Image<TColor> image = provider.GetImage();
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, new JpegEncoder());
ms.Seek(0, SeekOrigin.Begin);
Image<TColor> mirror = provider.Factory.CreateImage(1, 1);
using (JpegDecoderCore decoder = new JpegDecoderCore())
{
decoder.Decode(mirror, ms, true);
Assert.Equal(decoder.ImageWidth, image.Width);
Assert.Equal(decoder.ImageHeight, image.Height);
}
}
}
}
}

29
tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs

@ -8,6 +8,7 @@ namespace ImageSharp.Tests
using System;
using System.IO;
using System.Linq;
using System.Numerics;
using ImageSharp.Formats;
@ -21,20 +22,32 @@ namespace ImageSharp.Tests
{
}
public static readonly TheoryData<string> DecodeJpegData = new TheoryData<string>()
{
TestImages.Jpeg.Baseline.Cmyk,
TestImages.Jpeg.Baseline.Ycck,
TestImages.Jpeg.Baseline.Calliphora,
TestImages.Jpeg.Baseline.Jpeg400,
TestImages.Jpeg.Baseline.Jpeg420,
TestImages.Jpeg.Baseline.Jpeg444,
};
// [Theory] // Benchmark, enable manually
[InlineData(30, TestImages.Jpeg.Baseline.Cmyk)]
[InlineData(30, TestImages.Jpeg.Baseline.Ycck)]
[InlineData(30, TestImages.Jpeg.Baseline.Calliphora)]
[InlineData(30, TestImages.Jpeg.Baseline.Jpeg400)]
[InlineData(30, TestImages.Jpeg.Baseline.Jpeg420)]
[InlineData(30, TestImages.Jpeg.Baseline.Jpeg444)]
public void DecodeJpeg(int executionCount, string fileName)
[MemberData(nameof(DecodeJpegData))]
public void DecodeJpeg(string fileName)
{
const int ExecutionCount = 30;
if (!Vector.IsHardwareAccelerated)
{
throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)");
}
string path = TestFile.GetPath(fileName);
byte[] bytes = File.ReadAllBytes(path);
this.Measure(
100,
ExecutionCount,
() =>
{
Image img = new Image(bytes);

Loading…
Cancel
Save