diff --git a/src/ImageSharp/Formats/Jpg/Components/Block.cs b/src/ImageSharp/Formats/Jpg/Components/Block.cs
index 8bc5a861c..f41e615b5 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Block.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Block.cs
@@ -3,13 +3,19 @@
// Licensed under the Apache License, Version 2.0.
//
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+
namespace ImageSharp.Formats
{
///
/// Represents an 8x8 block of coefficients to transform and encode.
///
- internal class Block
+ public struct Block : IDisposable
{
+ private static ArrayPool IntArrayPool = ArrayPool.Create(BlockSize, 50);
+
///
/// Gets the size of the block.
///
@@ -18,16 +24,41 @@ namespace ImageSharp.Formats
///
/// The array of block data.
///
- private readonly int[] data;
+ public int[] Data;
///
/// Initializes a new instance of the class.
///
- public Block()
+ //public Block()
+ //{
+ // this.data = new int[BlockSize];
+ //}
+
+ public void Init()
+ {
+ //this.Data = new int[BlockSize];
+ this.Data = IntArrayPool.Rent(BlockSize);
+ }
+
+ public static Block Create()
{
- this.data = new int[BlockSize];
+ var block = new Block();
+ block.Init();
+ return block;
}
+ public static Block[] CreateArray(int size)
+ {
+ Block[] result = new Block[size];
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i].Init();
+ }
+ return result;
+ }
+
+ public bool IsInitialized => this.Data != null;
+
///
/// Gets the pixel data at the given block index.
///
@@ -37,8 +68,44 @@ namespace ImageSharp.Formats
///
public int this[int index]
{
- get { return this.data[index]; }
- set { this.data[index] = value; }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get { return this.Data[index]; }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set { this.Data[index] = value; }
+ }
+
+ // TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
+ public void Dispose()
+ {
+ if (Data != null)
+ {
+ IntArrayPool.Return(Data, true);
+ Data = null;
+ }
+ }
+
+ public static void DisposeAll(Block[] blocks)
+ {
+ for (int i = 0; i < blocks.Length; i++)
+ {
+ blocks[i].Dispose();
+ }
+ }
+
+
+ public void Clear()
+ {
+ for (int i = 0; i < Data.Length; i++)
+ {
+ Data[i] = 0;
+ }
+ }
+
+ public Block Clone()
+ {
+ Block clone = Create();
+ Array.Copy(Data, clone.Data, BlockSize);
+ return clone;
}
}
}
diff --git a/src/ImageSharp/Formats/Jpg/Components/Component.cs b/src/ImageSharp/Formats/Jpg/Components/Component.cs
index f56b6d513..f70dbff3f 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Component.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Component.cs
@@ -8,26 +8,26 @@ namespace ImageSharp.Formats
///
/// Represents a single color component
///
- internal class Component
+ internal struct Component
{
///
/// Gets or sets the horizontal sampling factor.
///
- public int HorizontalFactor { get; set; }
+ public int HorizontalFactor;
///
/// Gets or sets the vertical sampling factor.
///
- public int VerticalFactor { get; set; }
+ public int VerticalFactor;
///
/// Gets or sets the identifier
///
- public byte Identifier { get; set; }
+ public byte Identifier;
///
/// Gets or sets the quantization table destination selector.
///
- public byte Selector { get; set; }
+ public byte Selector;
}
}
diff --git a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs b/src/ImageSharp/Formats/Jpg/Components/FDCT.cs
index cd27b9e73..13ce80f55 100644
--- a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/FDCT.cs
@@ -45,7 +45,7 @@ namespace ImageSharp.Formats
/// Performs a forward DCT on an 8x8 block of coefficients, including a level shift.
///
/// The block of coefficients.
- public static void Transform(Block block)
+ public static void Transform(ref Block block)
{
// Pass 1: process rows.
for (int y = 0; y < 8; y++)
diff --git a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs b/src/ImageSharp/Formats/Jpg/Components/Huffman.cs
index 2c38cfd38..345b45e0e 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Huffman.cs
@@ -3,33 +3,47 @@
// Licensed under the Apache License, Version 2.0.
//
+using System;
+using System.Buffers;
+
namespace ImageSharp.Formats
{
///
/// Represents a Huffman tree
///
- internal class Huffman
+ internal struct Huffman : IDisposable
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The log-2 size of the Huffman decoder's look-up table.
- /// The maximum (inclusive) number of codes in a Huffman tree.
- /// The maximum (inclusive) number of bits in a Huffman code.
- public Huffman(int lutSize, int maxNCodes, int maxCodeLength)
+ private static ArrayPool UshortBuffer =
+ //ArrayPool.Shared;
+ ArrayPool.Create(1 << JpegDecoderCore.LutSize, 50);
+
+ private static ArrayPool ByteBuffer =
+ //ArrayPool.Shared;
+ ArrayPool.Create(JpegDecoderCore.MaxNCodes, 50);
+
+ private static readonly ArrayPool IntBuffer =
+ //ArrayPool.Shared;
+ ArrayPool.Create(JpegDecoderCore.MaxCodeLength, 50);
+
+ public void Init(int lutSize, int maxNCodes, int maxCodeLength)
{
- this.Lut = new ushort[1 << lutSize];
- this.Values = new byte[maxNCodes];
- this.MinCodes = new int[maxCodeLength];
- this.MaxCodes = new int[maxCodeLength];
- this.Indices = new int[maxCodeLength];
- this.Length = 0;
+ //this.Lut = new ushort[1 << lutSize];
+ //this.Values = new byte[maxNCodes];
+ //this.MinCodes = new int[maxCodeLength];
+ //this.MaxCodes = new int[maxCodeLength];
+ //this.Indices = new int[maxCodeLength];
+
+ 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); ;
}
///
/// Gets or sets the number of codes in the tree.
///
- public int Length { get; set; }
+ public int Length;
///
/// Gets the look-up table for the next LutSize bits in the bit-stream.
@@ -37,28 +51,39 @@ namespace ImageSharp.Formats
/// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits.
///
- public ushort[] Lut { get; }
+ public ushort[] Lut;
///
/// Gets the the decoded values, sorted by their encoding.
///
- public byte[] Values { get; }
+ public byte[] Values;
///
/// Gets the array of minimum codes.
/// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
///
- public int[] MinCodes { get; }
+ public int[] MinCodes;
///
/// Gets the array of maximum codes.
/// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
///
- public int[] MaxCodes { get; }
+ public int[] MaxCodes;
///
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
///
- public int[] Indices { get; }
+ public int[] Indices;
+
+ public void Dispose()
+ {
+ UshortBuffer.Return(Lut, true);
+ ByteBuffer.Return(Values, true);
+ IntBuffer.Return(MinCodes, true);
+ IntBuffer.Return(MaxCodes, true);
+ IntBuffer.Return(Indices, true);
+ }
}
+
+
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs b/src/ImageSharp/Formats/Jpg/Components/IDCT.cs
index bc145779a..88b493c4b 100644
--- a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/IDCT.cs
@@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0.
//
+using System.Numerics;
+
namespace ImageSharp.Formats
{
///
@@ -39,7 +41,7 @@ namespace ImageSharp.Formats
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
///
/// The source block of coefficients
- public static void Transform(Block src)
+ public static void Transform(ref Block src)
{
// Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++)
@@ -165,5 +167,6 @@ namespace ImageSharp.Formats
src[56 + x] = (y7 - y1) >> 14;
}
}
+
}
}
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpg/JpegDecoder.cs
index 666f1b35a..d3c64a9c0 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoder.cs
@@ -83,8 +83,10 @@ namespace ImageSharp.Formats
Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream");
- JpegDecoderCore decoder = new JpegDecoderCore();
- decoder.Decode(image, stream, false);
+ using (JpegDecoderCore decoder = new JpegDecoderCore())
+ {
+ decoder.Decode(image, stream, false);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
index 332dcec48..4a7726be4 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
@@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0.
//
+using System.Runtime.CompilerServices;
+
namespace ImageSharp.Formats
{
using System;
@@ -12,22 +14,22 @@ namespace ImageSharp.Formats
///
/// Performs the jpeg decoding operation.
///
- internal class JpegDecoderCore
+ internal class JpegDecoderCore : IDisposable
{
///
/// The maximum (inclusive) number of bits in a Huffman code.
///
- private const int MaxCodeLength = 16;
+ internal const int MaxCodeLength = 16;
///
/// The maximum (inclusive) number of codes in a Huffman tree.
///
- private const int MaxNCodes = 256;
+ internal const int MaxNCodes = 256;
///
/// The log-2 size of the Huffman decoder's look-up table.
///
- private const int LutSize = 8;
+ internal const int LutSize = 8;
///
/// The maximum number of color components
@@ -44,6 +46,8 @@ namespace ImageSharp.Formats
///
private const int MaxTh = 3;
+ private const int ThRowSize = MaxTh + 1;
+
///
/// The maximum number of quantization tables
///
@@ -85,7 +89,9 @@ namespace ImageSharp.Formats
///
/// The huffman trees
///
- private readonly Huffman[,] huffmanTrees;
+ //private readonly Huffman[,] huffmanTrees;
+
+ private readonly Huffman[] huffmanTrees;
///
/// Quantization tables, in zigzag order.
@@ -187,13 +193,17 @@ namespace ImageSharp.Formats
///
private short verticalResolution;
+ private int blockIndex;
+
///
/// Initializes a new instance of the class.
///
public JpegDecoderCore()
{
- this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
- this.quantizationTables = new Block[MaxTq + 1];
+ //this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
+ this.huffmanTrees = new Huffman[(MaxTc + 1)*(MaxTh + 1)];
+
+ this.quantizationTables = Block.CreateArray(MaxTq + 1);
this.temp = new byte[2 * Block.BlockSize];
this.componentArray = new Component[MaxComponents];
this.progCoeffs = new Block[MaxComponents][];
@@ -201,25 +211,29 @@ namespace ImageSharp.Formats
this.bytes = new Bytes();
// TODO: This looks like it could be static.
+
for (int i = 0; i < MaxTc + 1; i++)
{
for (int j = 0; j < MaxTh + 1; j++)
{
- this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength);
+ //this.huffmanTrees[i, j].Init(LutSize, MaxNCodes, MaxCodeLength);
+ this.huffmanTrees[i* ThRowSize + j].Init(LutSize, MaxNCodes, MaxCodeLength);
}
}
- for (int i = 0; i < this.quantizationTables.Length; i++)
- {
- this.quantizationTables[i] = new Block();
- }
+ //for (int i = 0; i < this.quantizationTables.Length; i++)
+ //{
+ // //this.quantizationTables[i] = new Block();
+ // this.quantizationTables[i].Init();
+ //}
- for (int i = 0; i < this.componentArray.Length; i++)
- {
- this.componentArray[i] = new Component();
- }
+ //for (int i = 0; i < this.componentArray.Length; i++)
+ //{
+ // this.componentArray[i] = new Component();
+ //}
}
+
///
/// Decodes the image from the specified this._stream and sets
/// the data to image.
@@ -501,92 +515,96 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Bad Th value");
}
- Huffman huffman = this.huffmanTrees[tc, th];
+ ProcessDefineHuffmanTablesMarkerLoop(ref this.huffmanTrees[tc* ThRowSize + th], ref remaining);
+ }
+ }
- // Read nCodes and huffman.Valuess (and derive h.Length).
- // nCodes[i] is the number of codes with code length i.
- // h.Length is the total number of codes.
- huffman.Length = 0;
+ private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining)
+ {
- int[] ncodes = new int[MaxCodeLength];
- for (int i = 0; i < ncodes.Length; i++)
- {
- ncodes[i] = this.temp[i + 1];
- huffman.Length += ncodes[i];
- }
+ // Read nCodes and huffman.Valuess (and derive h.Length).
+ // nCodes[i] is the number of codes with code length i.
+ // h.Length is the total number of codes.
+ huffman.Length = 0;
- if (huffman.Length == 0)
- {
- throw new ImageFormatException("Huffman table has zero length");
- }
+ int[] ncodes = new int[MaxCodeLength];
+ for (int i = 0; i < ncodes.Length; i++)
+ {
+ ncodes[i] = this.temp[i + 1];
+ huffman.Length += ncodes[i];
+ }
- if (huffman.Length > MaxNCodes)
- {
- throw new ImageFormatException("Huffman table has excessive length");
- }
+ if (huffman.Length == 0)
+ {
+ throw new ImageFormatException("Huffman table has zero length");
+ }
- remaining -= huffman.Length + 17;
- if (remaining < 0)
- {
- throw new ImageFormatException("DHT has wrong length");
- }
+ if (huffman.Length > MaxNCodes)
+ {
+ throw new ImageFormatException("Huffman table has excessive length");
+ }
+
+ remaining -= huffman.Length + 17;
+ if (remaining < 0)
+ {
+ throw new ImageFormatException("DHT has wrong length");
+ }
- this.ReadFull(huffman.Values, 0, huffman.Length);
+ this.ReadFull(huffman.Values, 0, huffman.Length);
- // Derive the look-up table.
- for (int i = 0; i < huffman.Lut.Length; i++)
- {
- huffman.Lut[i] = 0;
- }
+ // Derive the look-up table.
+ for (int i = 0; i < huffman.Lut.Length; i++)
+ {
+ huffman.Lut[i] = 0;
+ }
- uint x = 0, code = 0;
+ uint x = 0, code = 0;
+
+ for (int i = 0; i < LutSize; i++)
+ {
+ code <<= 1;
- for (int i = 0; i < LutSize; i++)
+ for (int j = 0; j < ncodes[i]; j++)
{
- code <<= 1;
+ // The codeLength is 1+i, so shift code by 8-(1+i) to
+ // calculate the high bits for every 8-bit sequence
+ // 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) ((huffman.Values[x] << 8) | (2 + i));
- for (int j = 0; j < ncodes[i]; j++)
+ for (int k = 0; k < 1 << (7 - i); k++)
{
- // The codeLength is 1+i, so shift code by 8-(1+i) to
- // calculate the high bits for every 8-bit sequence
- // 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)((huffman.Values[x] << 8) | (2 + i));
-
- for (int k = 0; k < 1 << (7 - i); k++)
- {
- huffman.Lut[base2 | k] = lutValue;
- }
-
- code++;
- x++;
+ huffman.Lut[base2 | k] = lutValue;
}
+
+ code++;
+ x++;
}
+ }
- // Derive minCodes, maxCodes, and indices.
- int c = 0, index = 0;
- for (int i = 0; i < ncodes.Length; i++)
+ // Derive minCodes, maxCodes, and indices.
+ int c = 0, index = 0;
+ for (int i = 0; i < ncodes.Length; i++)
+ {
+ int nc = ncodes[i];
+ if (nc == 0)
{
- int nc = ncodes[i];
- if (nc == 0)
- {
- huffman.MinCodes[i] = -1;
- huffman.MaxCodes[i] = -1;
- huffman.Indices[i] = -1;
- }
- else
- {
- huffman.MinCodes[i] = c;
- huffman.MaxCodes[i] = c + nc - 1;
- huffman.Indices[i] = index;
- c += nc;
- index += nc;
- }
-
- c <<= 1;
+ huffman.MinCodes[i] = -1;
+ huffman.MaxCodes[i] = -1;
+ huffman.Indices[i] = -1;
+ }
+ else
+ {
+ huffman.MinCodes[i] = c;
+ huffman.MaxCodes[i] = c + nc - 1;
+ huffman.Indices[i] = index;
+ c += nc;
+ index += nc;
}
+
+ c <<= 1;
}
}
@@ -595,7 +613,7 @@ namespace ImageSharp.Formats
///
/// The huffman value
/// The
- private byte DecodeHuffman(Huffman huffman)
+ private byte DecodeHuffman(ref Huffman huffman)
{
if (huffman.Length == 0)
{
@@ -1385,7 +1403,7 @@ namespace ImageSharp.Formats
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)];
TColor packed = default(TColor);
- this.PackYcbCr(ref packed, yy, cb, cr);
+ PackYcbCr(ref packed, yy, cb, cr);
pixels[x, y] = packed;
}
});
@@ -1453,6 +1471,8 @@ namespace ImageSharp.Formats
}
}
+ private Block scanWorkerBlock = Block.Create();
+
///
/// Processes the SOS (Start of scan marker).
///
@@ -1479,7 +1499,8 @@ namespace ImageSharp.Formats
this.ReadFull(this.temp, 0, remaining);
byte scanComponentCount = this.temp[0];
- if (remaining != 4 + (2 * scanComponentCount))
+ int scanComponentCountX2 = 2 * scanComponentCount;
+ if (remaining != 4 + scanComponentCountX2)
{
throw new ImageFormatException("SOS length inconsistent with number of components");
}
@@ -1489,51 +1510,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < scanComponentCount; i++)
{
- // Component selector.
- int cs = this.temp[1 + (2 * i)];
- int compIndex = -1;
- for (int j = 0; j < this.componentCount; j++)
- {
- Component compv = this.componentArray[j];
- if (cs == compv.Identifier)
- {
- compIndex = j;
- }
- }
-
- if (compIndex < 0)
- {
- throw new ImageFormatException("Unknown component selector");
- }
-
- scan[i].Index = (byte)compIndex;
-
- // Section B.2.3 states that "the value of Cs_j shall be different from
- // the values of Cs_1 through Cs_(j-1)". Since we have previously
- // verified that a frame's component identifiers (C_i values in section
- // B.2.2) are unique, it suffices to check that the implicit indexes
- // into comp are unique.
- for (int j = 0; j < i; j++)
- {
- if (scan[i].Index == scan[j].Index)
- {
- throw new ImageFormatException("Repeated component selector");
- }
- }
-
- totalHv += this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor;
-
- scan[i].DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4);
- if (scan[i].DcTableSelector > MaxTh)
- {
- throw new ImageFormatException("Bad DC table selector value");
- }
-
- scan[i].AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f);
- if (scan[i].AcTableSelector > MaxTh)
- {
- throw new ImageFormatException("Bad AC table selector value");
- }
+ ProcessScanImpl(i, ref scan[i], scan, ref totalHv);
}
// Section B.2.3 states that if there is more than one component then the
@@ -1564,10 +1541,10 @@ namespace ImageSharp.Formats
if (this.isProgressive)
{
- zigStart = this.temp[1 + (2 * scanComponentCount)];
- zigEnd = this.temp[2 + (2 * scanComponentCount)];
- ah = this.temp[3 + (2 * scanComponentCount)] >> 4;
- al = this.temp[3 + (2 * scanComponentCount)] & 0x0f;
+ zigStart = this.temp[1 + scanComponentCountX2];
+ zigEnd = this.temp[2 + scanComponentCountX2];
+ ah = this.temp[3 + scanComponentCountX2] >> 4;
+ al = this.temp[3 + scanComponentCountX2] & 0x0f;
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd)
{
@@ -1603,11 +1580,11 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index;
if (this.progCoeffs[compIndex] == null)
{
- this.progCoeffs[compIndex] = new Block[mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor];
+ this.progCoeffs[compIndex] = Block.CreateArray(mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor);
for (int j = 0; j < this.progCoeffs[compIndex].Length; j++)
{
- this.progCoeffs[compIndex][j] = new Block();
+ this.progCoeffs[compIndex][j].Init();
}
}
}
@@ -1619,7 +1596,7 @@ namespace ImageSharp.Formats
byte expectedRst = JpegConstants.Markers.RST0;
// b is the decoded coefficients block, in natural (not zig-zag) order.
- Block b;
+ //Block b;
int[] dc = new int[MaxComponents];
// bx and by are the location of the current block, in units of 8x8
@@ -1635,7 +1612,7 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index;
int hi = this.componentArray[compIndex].HorizontalFactor;
int vi = this.componentArray[compIndex].VerticalFactor;
- Block qt = this.quantizationTables[this.componentArray[compIndex].Selector];
+
for (int j = 0; j < hi * vi; j++)
{
@@ -1678,168 +1655,27 @@ namespace ImageSharp.Formats
}
}
- // Load the previous partially decoded coefficients, if applicable.
- b = this.isProgressive ? this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] : new Block();
-
- if (ah != 0)
+ var qtIndex = this.componentArray[compIndex].Selector;
+
+ if (this.isProgressive) // Load the previous partially decoded coefficients, if applicable.
{
- this.Refine(b, this.huffmanTrees[AcTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al);
+ blockIndex = ((@by * mxx) * hi) + bx;
+ ProcessBlockImpl(ah,
+ ref this.progCoeffs[compIndex][blockIndex],
+ scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi, bx,
+ ref this.quantizationTables[qtIndex]
+ );
}
else
{
- int zig = zigStart;
- if (zig == 0)
- {
- zig++;
-
- // Decode the DC coefficient, as specified in section F.2.2.1.
- byte value = this.DecodeHuffman(this.huffmanTrees[DcTable, scan[i].DcTableSelector]);
- if (value > 16)
- {
- throw new ImageFormatException("Excessive DC component");
- }
-
- int deltaDC = this.ReceiveExtend(value);
- dc[compIndex] += deltaDC;
- b[0] = dc[compIndex] << al;
- }
-
- if (zig <= zigEnd && this.eobRun > 0)
- {
- this.eobRun--;
- }
- else
- {
- // Decode the AC coefficients, as specified in section F.2.2.2.
- Huffman huffv = this.huffmanTrees[AcTable, scan[i].AcTableSelector];
- for (; zig <= zigEnd; zig++)
- {
- byte value = this.DecodeHuffman(huffv);
- byte val0 = (byte)(value >> 4);
- byte val1 = (byte)(value & 0x0f);
- if (val1 != 0)
- {
- zig += val0;
- if (zig > zigEnd)
- {
- break;
- }
-
- int ac = this.ReceiveExtend(val1);
- b[Unzig[zig]] = ac << al;
- }
- else
- {
- if (val0 != 0x0f)
- {
- this.eobRun = (ushort)(1 << val0);
- if (val0 != 0)
- {
- this.eobRun |= (ushort)this.DecodeBits(val0);
- }
-
- this.eobRun--;
- break;
- }
-
- zig += 0x0f;
- }
- }
- }
- }
-
- if (this.isProgressive)
- {
- if (zigEnd != Block.BlockSize - 1 || al != 0)
- {
- // We haven't completely decoded this 8x8 block. Save the coefficients.
- this.progCoeffs[compIndex][((by * mxx) * hi) + bx] = 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.
- continue;
- }
- }
-
- // Dequantize, perform the inverse DCT and store the block to the image.
- for (int zig = 0; zig < Block.BlockSize; zig++)
- {
- b[Unzig[zig]] *= qt[zig];
- }
-
- IDCT.Transform(b);
-
- byte[] dst;
- int offset;
- int stride;
-
- if (this.componentCount == 1)
- {
- dst = this.grayImage.Pixels;
- stride = this.grayImage.Stride;
- offset = this.grayImage.Offset + (8 * ((by * this.grayImage.Stride) + bx));
- }
- else
- {
- switch (compIndex)
- {
- case 0:
- dst = this.ycbcrImage.YChannel;
- stride = this.ycbcrImage.YStride;
- offset = this.ycbcrImage.YOffset + (8 * ((by * this.ycbcrImage.YStride) + bx));
- break;
-
- case 1:
- dst = this.ycbcrImage.CbChannel;
- stride = this.ycbcrImage.CStride;
- offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx));
- break;
-
- case 2:
- dst = this.ycbcrImage.CrChannel;
- stride = this.ycbcrImage.CStride;
- offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx));
- break;
-
- case 3:
-
- dst = this.blackPixels;
- stride = this.blackStride;
- offset = 8 * ((by * this.blackStride) + bx);
- break;
-
- default:
- throw new ImageFormatException("Too many components");
- }
- }
-
- // Level shift by +128, clip to [0, 255], and write to dst.
- for (int y = 0; y < 8; y++)
- {
- int y8 = y * 8;
- int yStride = y * stride;
-
- for (int x = 0; x < 8; x++)
- {
- int c = b[y8 + x];
- if (c < -128)
- {
- c = 0;
- }
- else if (c > 127)
- {
- c = 255;
- }
- else
- {
- c += 128;
- }
-
- dst[yStride + x + offset] = (byte)c;
- }
+ //var b = Block.Create();
+ scanWorkerBlock.Clear();
+
+ ProcessBlockImpl(ah, ref scanWorkerBlock, scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi,
+ bx, ref this.quantizationTables[qtIndex]
+ );
+
+ //b.Dispose();
}
}
@@ -1882,6 +1718,237 @@ namespace ImageSharp.Formats
// for my
}
+ private void ProcessBlockImpl(int ah, ref Block b, Scan[] scan, int i, int zigStart, int zigEnd, int al,
+ int[] dc, int compIndex, int @by, int mxx, int hi, int bx, ref Block qt)
+ {
+ if (ah != 0)
+ {
+ this.Refine(ref b, ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector], zigStart, zigEnd, 1 << al);
+ }
+ else
+ {
+ int zig = zigStart;
+ if (zig == 0)
+ {
+ zig++;
+
+ // Decode the DC coefficient, as specified in section F.2.2.1.
+ byte value = this.DecodeHuffman(ref this.huffmanTrees[DcTable * ThRowSize + scan[i].DcTableSelector]);
+ if (value > 16)
+ {
+ throw new ImageFormatException("Excessive DC component");
+ }
+
+ int deltaDC = this.ReceiveExtend(value);
+ dc[compIndex] += deltaDC;
+ b[0] = dc[compIndex] << al;
+ }
+
+ if (zig <= zigEnd && this.eobRun > 0)
+ {
+ this.eobRun--;
+ }
+ else
+ {
+ // Decode the AC coefficients, as specified in section F.2.2.2.
+ //Huffman huffv = ;
+ for (; zig <= zigEnd; zig++)
+ {
+ byte value = this.DecodeHuffman(ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector]);
+ byte val0 = (byte) (value >> 4);
+ byte val1 = (byte) (value & 0x0f);
+ if (val1 != 0)
+ {
+ zig += val0;
+ if (zig > zigEnd)
+ {
+ break;
+ }
+
+ int ac = this.ReceiveExtend(val1);
+ b[Unzig[zig]] = ac << al;
+ }
+ else
+ {
+ if (val0 != 0x0f)
+ {
+ this.eobRun = (ushort) (1 << val0);
+ if (val0 != 0)
+ {
+ this.eobRun |= (ushort) this.DecodeBits(val0);
+ }
+
+ this.eobRun--;
+ break;
+ }
+
+ zig += 0x0f;
+ }
+ }
+ }
+ }
+
+ if (this.isProgressive)
+ {
+ if (zigEnd != Block.BlockSize - 1 || al != 0)
+ {
+ // We haven't completely decoded this 8x8 block. Save the coefficients.
+
+ this.progCoeffs[compIndex][((@by*mxx)*hi) + bx] = b.Clone();
+
+ // 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;
+ }
+ }
+
+ // Dequantize, perform the inverse DCT and store the block to the image.
+ for (int zig = 0; zig < Block.BlockSize; zig++)
+ {
+ b[Unzig[zig]] *= qt[zig];
+ }
+
+ IDCT.Transform(ref b);
+
+ // ******* Other experimental variants: *************
+
+ // FluxJpeg:
+ // https://github.com/antonfirsov/ImageSharp/blob/master/src/ImageSharp46/Formats/Jpg/Components/FloatIDCT.cs
+ // FloatIDCT.Transform(ref b);
+
+ // SIMD-based:
+ // https://github.com/antonfirsov/ImageSharp/blob/master/src/ImageSharp46/Formats/Jpg/Components/MagicDCT.cs
+ // MagicDCT.IDCT(ref b);
+
+ byte[] dst;
+ int offset;
+ int stride;
+
+ if (this.componentCount == 1)
+ {
+ dst = this.grayImage.Pixels;
+ stride = this.grayImage.Stride;
+ offset = this.grayImage.Offset + (8*((@by*this.grayImage.Stride) + bx));
+ }
+ else
+ {
+ switch (compIndex)
+ {
+ case 0:
+ dst = this.ycbcrImage.YChannel;
+ stride = this.ycbcrImage.YStride;
+ offset = this.ycbcrImage.YOffset + (8*((@by*this.ycbcrImage.YStride) + bx));
+ break;
+
+ case 1:
+ dst = this.ycbcrImage.CbChannel;
+ stride = this.ycbcrImage.CStride;
+ offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx));
+ break;
+
+ case 2:
+ dst = this.ycbcrImage.CrChannel;
+ stride = this.ycbcrImage.CStride;
+ offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx));
+ break;
+
+ case 3:
+
+ dst = this.blackPixels;
+ stride = this.blackStride;
+ offset = 8*((@by*this.blackStride) + bx);
+ break;
+
+ default:
+ throw new ImageFormatException("Too many components");
+ }
+ }
+
+ // Level shift by +128, clip to [0, 255], and write to dst.
+ for (int y = 0; y < 8; y++)
+ {
+ int y8 = y*8;
+ int yStride = y*stride;
+
+ for (int x = 0; x < 8; x++)
+ {
+ int c = b[y8 + x];
+ if (c < -128)
+ {
+ c = 0;
+ }
+ else if (c > 127)
+ {
+ c = 255;
+ }
+ else
+ {
+ c += 128;
+ }
+
+ dst[yStride + x + offset] = (byte) c;
+ }
+ }
+ }
+
+ private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv)
+ {
+ // Component selector.
+ int cs = this.temp[1 + (2 * i)];
+ int compIndex = -1;
+ for (int j = 0; j < this.componentCount; j++)
+ {
+ //Component compv = ;
+ if (cs == this.componentArray[j].Identifier)
+ {
+ compIndex = j;
+ }
+ }
+
+ if (compIndex < 0)
+ {
+ throw new ImageFormatException("Unknown component selector");
+ }
+
+ currentScan.Index = (byte)compIndex;
+
+ ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]);
+ }
+
+ private void ProcessComponentImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv, ref Component currentComponent)
+ {
+ // Section B.2.3 states that "the value of Cs_j shall be different from
+ // the values of Cs_1 through Cs_(j-1)". Since we have previously
+ // verified that a frame's component identifiers (C_i values in section
+ // B.2.2) are unique, it suffices to check that the implicit indexes
+ // into comp are unique.
+ for (int j = 0; j < i; j++)
+ {
+ if (currentScan.Index == scan[j].Index)
+ {
+ throw new ImageFormatException("Repeated component selector");
+ }
+ }
+
+
+ totalHv += currentComponent.HorizontalFactor*currentComponent.VerticalFactor;
+
+ currentScan.DcTableSelector = (byte) (this.temp[2 + (2*i)] >> 4);
+ if (currentScan.DcTableSelector > MaxTh)
+ {
+ throw new ImageFormatException("Bad DC table selector value");
+ }
+
+ currentScan.AcTableSelector = (byte) (this.temp[2 + (2*i)] & 0x0f);
+ if (currentScan.AcTableSelector > MaxTh)
+ {
+ throw new ImageFormatException("Bad AC table selector value");
+ }
+ }
+
///
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
///
@@ -1890,7 +1957,7 @@ namespace ImageSharp.Formats
/// The zig-zag start index
/// The zig-zag end index
/// The low transform offset
- private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta)
+ private void Refine(ref Block b, ref Huffman h, int zigStart, int zigEnd, int delta)
{
// Refining a DC component is trivial.
if (zigStart == 0)
@@ -1917,7 +1984,7 @@ namespace ImageSharp.Formats
{
bool done = false;
int z = 0;
- byte val = this.DecodeHuffman(h);
+ byte val = this.DecodeHuffman(ref h);
int val0 = val >> 4;
int val1 = val & 0x0f;
@@ -2107,7 +2174,8 @@ namespace ImageSharp.Formats
/// The y luminance component.
/// The cb chroma component.
/// The cr chroma component.
- private void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void PackYcbCr(ref TColor packed, byte y, byte cb, byte cr)
where TColor : struct, IPackedPixel
where TPacked : struct
{
@@ -2199,5 +2267,24 @@ namespace ImageSharp.Formats
private class EOFException : Exception
{
}
+
+ public void Dispose()
+ {
+ scanWorkerBlock.Dispose();
+ Block.DisposeAll(this.quantizationTables);
+
+ foreach (Block[] blocks in progCoeffs)
+ {
+ if (blocks != null)
+ {
+ Block.DisposeAll(blocks);
+ }
+ }
+
+ for (int i = 0; i < huffmanTrees.Length; i++)
+ {
+ huffmanTrees[i].Dispose();
+ }
+ }
}
}
diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
index 69161eb02..75e00bc76 100644
--- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
@@ -246,6 +246,7 @@ namespace ImageSharp.Formats
///
/// The AC luminance huffman table index
///
+
LuminanceAC = 1,
// ReSharper restore UnusedMember.Local
@@ -487,9 +488,9 @@ namespace ImageSharp.Formats
/// The quantization table index.
/// The previous DC value.
/// The
- private int WriteBlock(Block block, QuantIndex index, int prevDC)
+ private int WriteBlock(ref Block block, QuantIndex index, int prevDC)
{
- FDCT.Transform(block);
+ FDCT.Transform(ref block);
// Emit the DC delta.
int dc = Round(block[0], 8 * this.quant[(int)index][0]);
@@ -540,7 +541,8 @@ namespace ImageSharp.Formats
/// The red chroma block.
/// The blue chroma block.
// ReSharper disable StyleCop.SA1305
- private void ToYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock)
+ private void ToYCbCr(PixelAccessor pixels, int x, int y,
+ ref Block yBlock, ref Block cbBlock, ref Block crBlock)
// ReSharper restore StyleCop.SA1305
where TColor : struct, IPackedPixel
where TPacked : struct
@@ -577,7 +579,7 @@ namespace ImageSharp.Formats
///
/// The destination block array
/// The source block array.
- private void Scale16X16To8X8(Block destination, Block[] source)
+ private void Scale16X16To8X8(ref Block destination, Block[] source)
{
for (int i = 0; i < 4; i++)
{
@@ -847,10 +849,9 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct
{
- Block b = new Block();
- Block cb = new Block();
- Block cr = new Block();
-
+ Block b = Block.Create();
+ Block cb = Block.Create();
+ Block cr = Block.Create();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@@ -858,12 +859,15 @@ namespace ImageSharp.Formats
{
for (int x = 0; x < pixels.Width; x += 8)
{
- this.ToYCbCr(pixels, x, y, b, cb, cr);
- prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY);
- prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb);
- prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr);
+ this.ToYCbCr(pixels, x, y, ref b, ref cb, ref cr);
+ prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
+ prevDCCb = this.WriteBlock(ref cb, QuantIndex.Chrominance, prevDCCb);
+ prevDCCr = this.WriteBlock(ref cr, QuantIndex.Chrominance, prevDCCr);
}
}
+ b.Dispose();
+ cb.Dispose();
+ cr.Dispose();
}
///
@@ -877,23 +881,12 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct
{
- Block b = new Block();
- Block[] cb = new Block[4];
- Block[] cr = new Block[4];
-
+ Block b = Block.Create();
+ Block[] cb = Block.CreateArray(4);
+ Block[] cr = Block.CreateArray(4);
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
-
- for (int i = 0; i < 4; i++)
- {
- cb[i] = new Block();
- }
-
- for (int i = 0; i < 4; i++)
- {
- cr[i] = new Block();
- }
-
+
for (int y = 0; y < pixels.Height; y += 16)
{
for (int x = 0; x < pixels.Width; x += 16)
@@ -903,16 +896,20 @@ namespace ImageSharp.Formats
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
- this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]);
- prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY);
+ this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]);
+ prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
}
- this.Scale16X16To8X8(b, cb);
- prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb);
- this.Scale16X16To8X8(b, cr);
- prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr);
+ this.Scale16X16To8X8(ref b, cb);
+ prevDCCb = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCb);
+ this.Scale16X16To8X8(ref b, cr);
+ prevDCCr = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCr);
}
}
+
+ b.Dispose();
+ Block.DisposeAll(cb);
+ Block.DisposeAll(cr);
}
///
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
index 549ac05ef..c91b0ad1b 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
@@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0.
//
+using ImageSharp.Formats;
+
namespace ImageSharp.Tests
{
using System.IO;
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
new file mode 100644
index 000000000..57bce1504
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using ImageSharp.Formats;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace ImageSharp.Tests.Formats.Jpg
+{
+ public class JpegTests
+ {
+
+ public const string TestOutputDirectory = "TestOutput/Jpeg";
+
+ private ITestOutputHelper Output { get; }
+
+ public JpegTests(ITestOutputHelper output)
+ {
+ Output = output;
+ }
+
+ protected string CreateTestOutputFile(string fileName)
+ {
+ if (!Directory.Exists(TestOutputDirectory))
+ {
+ Directory.CreateDirectory(TestOutputDirectory);
+ }
+
+ //string id = Guid.NewGuid().ToString().Substring(0, 4);
+
+ string ext = Path.GetExtension(fileName);
+ fileName = Path.GetFileNameWithoutExtension(fileName);
+
+ return $"{TestOutputDirectory}/{fileName}{ext}";
+ }
+
+ protected Stream CreateOutputStream(string fileName)
+ {
+ fileName = CreateTestOutputFile(fileName);
+ Output?.WriteLine("Opened for write: "+fileName);
+ return File.OpenWrite(fileName);
+ }
+
+ public static IEnumerable