diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs
new file mode 100644
index 000000000..6ef128ccb
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs
@@ -0,0 +1,74 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+
+ ///
+ /// Provides information about the Adobe marker segment
+ ///
+ internal struct Adobe : IEquatable
+ {
+ ///
+ /// The DCT Encode Version
+ ///
+ public short DCTEncodeVersion;
+
+ ///
+ /// 0x0 : (none)
+ /// Bit 15 : Encoded with Blend=1 downsampling
+ ///
+ public short APP14Flags0;
+
+ ///
+ /// 0x0 : (none)
+ ///
+ public short APP14Flags1;
+
+ ///
+ /// Determines the colorspace transform
+ /// 00 : Unknown (RGB or CMYK)
+ /// 01 : YCbCr
+ /// 02 : YCCK
+ ///
+ public byte ColorTransform;
+
+ ///
+ public bool Equals(Adobe other)
+ {
+ return this.DCTEncodeVersion == other.DCTEncodeVersion
+ && this.APP14Flags0 == other.APP14Flags0
+ && this.APP14Flags1 == other.APP14Flags1
+ && this.ColorTransform == other.ColorTransform;
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ return obj is Adobe && this.Equals((Adobe)obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ // TODO: Merge and use HashCodeHelpers
+ int hashCode = this.DCTEncodeVersion.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.APP14Flags0.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.APP14Flags1.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.ColorTransform.GetHashCode();
+ return hashCode;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs
new file mode 100644
index 000000000..a21cb6620
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+ using System.Numerics;
+ using ImageSharp.Memory;
+
+ ///
+ /// Represents a component block
+ ///
+ internal struct Component : IDisposable
+ {
+ ///
+ /// Gets or sets the output
+ ///
+ public Buffer Output;
+
+ ///
+ /// Gets or sets the scaling factors
+ ///
+ public Vector2 Scale;
+
+ ///
+ /// Gets or sets the number of blocks per line
+ ///
+ public int BlocksPerLine;
+
+ ///
+ /// Gets or sets the number of blocks per column
+ ///
+ public int BlocksPerColumn;
+
+ ///
+ public void Dispose()
+ {
+ this.Output?.Dispose();
+ this.Output = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs
new file mode 100644
index 000000000..a72835e75
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+
+ ///
+ /// Contains all the decoded component blocks
+ ///
+ internal sealed class ComponentBlocks : IDisposable
+ {
+ ///
+ /// Gets or sets the component blocks
+ ///
+ public Component[] Components { get; set; }
+
+ ///
+ public void Dispose()
+ {
+ if (this.Components != null)
+ {
+ for (int i = 0; i < this.Components.Length; i++)
+ {
+ this.Components[i].Dispose();
+ }
+
+ this.Components = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs
new file mode 100644
index 000000000..eaf3dafb9
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ ///
+ /// Represents a jpeg file marker
+ ///
+ internal struct FileMarker
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The marker
+ /// The position within the stream
+ public FileMarker(ushort marker, long position)
+ {
+ this.Marker = marker;
+ this.Position = position;
+ this.Invalid = false;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The marker
+ /// The position within the stream
+ /// Whether the current marker is invalid
+ public FileMarker(ushort marker, long position, bool invalid)
+ {
+ this.Marker = marker;
+ this.Position = position;
+ this.Invalid = invalid;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the current marker is invalid
+ ///
+ public bool Invalid { get; set; }
+
+ ///
+ /// Gets the position of the marker within a stream
+ ///
+ public ushort Marker { get; }
+
+ ///
+ /// Gets the position of the marker within a stream
+ ///
+ public long Position { get; }
+
+ ///
+ public override string ToString()
+ {
+ return this.Marker.ToString("X");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs
new file mode 100644
index 000000000..06b4bbc24
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+
+ ///
+ /// Represent a single jpeg frame
+ ///
+ internal sealed class Frame : IDisposable
+ {
+ ///
+ /// Gets or sets a value indicating whether the frame uses the extended specification
+ ///
+ public bool Extended { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the frame uses the progressive specification
+ ///
+ public bool Progressive { get; set; }
+
+ ///
+ /// Gets or sets the precision
+ ///
+ public byte Precision { get; set; }
+
+ ///
+ /// Gets or sets the number of scanlines within the frame
+ ///
+ public short Scanlines { get; set; }
+
+ ///
+ /// Gets or sets the number of samples per scanline
+ ///
+ public short SamplesPerLine { get; set; }
+
+ ///
+ /// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4
+ ///
+ public byte ComponentCount { get; set; }
+
+ ///
+ /// Gets or sets the component id collection
+ ///
+ public byte[] ComponentIds { get; set; }
+
+ ///
+ /// Gets or sets the frame component collection
+ ///
+ public FrameComponent[] Components { get; set; }
+
+ ///
+ /// Gets or sets the maximum horizontal sampling factor
+ ///
+ public int MaxHorizontalFactor { get; set; }
+
+ ///
+ /// Gets or sets the maximum vertical sampling factor
+ ///
+ public int MaxVerticalFactor { get; set; }
+
+ ///
+ /// Gets or sets the number of MCU's per line
+ ///
+ public int McusPerLine { get; set; }
+
+ ///
+ /// Gets or sets the number of MCU's per column
+ ///
+ public int McusPerColumn { get; set; }
+
+ ///
+ public void Dispose()
+ {
+ if (this.Components != null)
+ {
+ for (int i = 0; i < this.Components.Length; i++)
+ {
+ this.Components[i].Dispose();
+ }
+
+ this.Components = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs
new file mode 100644
index 000000000..b386a86f3
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs
@@ -0,0 +1,74 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+
+ using ImageSharp.Memory;
+
+ ///
+ /// Represents a single frame component
+ ///
+ internal struct FrameComponent : IDisposable
+ {
+ ///
+ /// Gets or sets the component Id
+ ///
+ public byte Id;
+
+ ///
+ /// TODO: What does pred stand for?
+ ///
+ public int Pred;
+
+ ///
+ /// Gets or sets the horizontal sampling factor.
+ ///
+ public int HorizontalFactor;
+
+ ///
+ /// Gets or sets the vertical sampling factor.
+ ///
+ public int VerticalFactor;
+
+ ///
+ /// Gets or sets the identifier
+ ///
+ public byte QuantizationIdentifier;
+
+ ///
+ /// Gets or sets the block data
+ ///
+ public Buffer BlockData;
+
+ ///
+ /// Gets or sets the number of blocks per line
+ ///
+ public int BlocksPerLine;
+
+ ///
+ /// Gets or sets the number of blocks per column
+ ///
+ public int BlocksPerColumn;
+
+ ///
+ /// Gets the index for the DC Huffman table
+ ///
+ public int DCHuffmanTableId;
+
+ ///
+ /// Gets the index for the AC Huffman table
+ ///
+ public int ACHuffmanTableId;
+
+ ///
+ public void Dispose()
+ {
+ this.BlockData?.Dispose();
+ this.BlockData = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs
new file mode 100644
index 000000000..4c475450b
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs
@@ -0,0 +1,212 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.Memory;
+
+ ///
+ /// Represents a Huffman Table
+ ///
+ internal struct HuffmanTable : IDisposable
+ {
+ private Buffer lookahead;
+ private Buffer valOffset;
+ private Buffer maxcode;
+ private Buffer huffval;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The code lengths
+ /// The huffman values
+ public HuffmanTable(byte[] lengths, byte[] values)
+ {
+ this.lookahead = Buffer.CreateClean(256);
+ this.valOffset = Buffer.CreateClean(18);
+ this.maxcode = Buffer.CreateClean(18);
+
+ using (var huffsize = Buffer.CreateClean(257))
+ using (var huffcode = Buffer.CreateClean(257))
+ {
+ GenerateSizeTable(lengths, huffsize);
+ GenerateCodeTable(huffsize, huffcode);
+ GenerateDecoderTables(lengths, huffcode, this.valOffset, this.maxcode);
+ GenerateLookaheadTables(lengths, values, this.lookahead);
+ }
+
+ this.huffval = Buffer.CreateClean(values.Length);
+ Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length);
+
+ this.MaxCode = this.maxcode.Array;
+ this.ValOffset = this.valOffset.Array;
+ this.HuffVal = this.huffval.Array;
+ this.Lookahead = this.lookahead.Array;
+ }
+
+ ///
+ /// Gets the max code array
+ ///
+ public long[] MaxCode
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Gets the value offset array
+ ///
+ public short[] ValOffset
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Gets the huffman value array
+ ///
+ public byte[] HuffVal
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Gets the lookahead array
+ ///
+ public short[] Lookahead
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.lookahead?.Dispose();
+ this.valOffset?.Dispose();
+ this.maxcode?.Dispose();
+ this.huffval?.Dispose();
+
+ this.lookahead = null;
+ this.valOffset = null;
+ this.maxcode = null;
+ this.huffval = null;
+ }
+
+ ///
+ /// Figure C.1: make table of Huffman code length for each symbol
+ ///
+ /// The code lengths
+ /// The huffman size span
+ private static void GenerateSizeTable(byte[] lengths, Span huffsize)
+ {
+ short index = 0;
+ for (short l = 1; l <= 16; l++)
+ {
+ byte i = lengths[l];
+ for (short j = 0; j < i; j++)
+ {
+ huffsize[index] = l;
+ index++;
+ }
+ }
+
+ huffsize[index] = 0;
+ }
+
+ ///
+ /// Figure C.2: generate the codes themselves
+ ///
+ /// The huffman size span
+ /// The huffman code span
+ private static void GenerateCodeTable(Span huffsize, Span huffcode)
+ {
+ short k = 0;
+ short si = huffsize[0];
+ short code = 0;
+ for (short i = 0; i < huffsize.Length; i++)
+ {
+ while (huffsize[k] == si)
+ {
+ huffcode[k] = code;
+ code++;
+ k++;
+ }
+
+ code <<= 1;
+ si++;
+ }
+ }
+
+ ///
+ /// Figure F.15: generate decoding tables for bit-sequential decoding
+ ///
+ /// The code lengths
+ /// The huffman code span
+ /// The value offset span
+ /// The max code span
+ private static void GenerateDecoderTables(byte[] lengths, Span huffcode, Span valOffset, Span maxcode)
+ {
+ short bitcount = 0;
+ for (int i = 1; i <= 16; i++)
+ {
+ if (lengths[i] != 0)
+ {
+ // valoffset[l] = huffval[] index of 1st symbol of code length i,
+ // minus the minimum code of length i
+ valOffset[i] = (short)(bitcount - huffcode[bitcount]);
+ bitcount += lengths[i];
+ maxcode[i] = huffcode[bitcount - 1]; // maximum code of length i
+ }
+ else
+ {
+ maxcode[i] = -1; // -1 if no codes of this length
+ }
+ }
+
+ valOffset[17] = 0;
+ maxcode[17] = 0xFFFFFL;
+ }
+
+ ///
+ /// Generates lookup tables to speed up decoding
+ ///
+ /// The code lengths
+ /// The huffman value array
+ /// The lookahead span
+ private static void GenerateLookaheadTables(byte[] lengths, byte[] huffval, Span lookahead)
+ {
+ int x = 0, code = 0;
+
+ for (int i = 0; i < 8; i++)
+ {
+ code <<= 1;
+
+ for (int j = 0; j < lengths[i + 1]; j++)
+ {
+ // 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));
+ short lutValue = (short)((short)(huffval[x] << 8) | (short)(2 + i));
+
+ for (int k = 0; k < 1 << (7 - i); k++)
+ {
+ lookahead[base2 | k] = lutValue;
+ }
+
+ code++;
+ x++;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs
new file mode 100644
index 000000000..d076c0b03
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Defines a pair of huffman tables
+ ///
+ internal sealed class HuffmanTables : IDisposable
+ {
+ private readonly HuffmanTable[] tables = new HuffmanTable[4];
+
+ ///
+ /// Gets or sets the table at the given index.
+ ///
+ /// The index
+ /// The
+ public ref HuffmanTable this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return ref this.tables[index];
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ for (int i = 0; i < this.tables.Length; i++)
+ {
+ this.tables[i].Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs
new file mode 100644
index 000000000..064b3bea3
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs
@@ -0,0 +1,511 @@
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.Memory;
+
+ ///
+ /// Performs the inverse Descrete Cosine Transform on each frame component.
+ ///
+ internal static class IDCT
+ {
+ ///
+ /// Precomputed values scaled up by 14 bits
+ ///
+ public static readonly short[] Aanscales =
+ {
+ 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 22725, 31521, 29692, 26722, 22725, 17855,
+ 12299, 6270, 21407, 29692, 27969, 25172, 21407, 16819, 11585,
+ 5906, 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315,
+ 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 12873,
+ 17855, 16819, 15137, 12873, 10114, 6967, 3552, 8867, 12299,
+ 11585, 10426, 8867, 6967, 4799, 2446, 4520, 6270, 5906, 5315,
+ 4520, 3552, 2446, 1247
+ };
+
+ private const int DctCos1 = 4017; // cos(pi/16)
+ private const int DctSin1 = 799; // sin(pi/16)
+ private const int DctCos3 = 3406; // cos(3*pi/16)
+ private const int DctSin3 = 2276; // sin(3*pi/16)
+ private const int DctCos6 = 1567; // cos(6*pi/16)
+ private const int DctSin6 = 3784; // sin(6*pi/16)
+ private const int DctSqrt2 = 5793; // sqrt(2)
+ private const int DctSqrt1D2 = 2896; // sqrt(2) / 2
+
+#pragma warning disable SA1310 // Field names must not contain underscore
+ private const int FIX_1_082392200 = 277; // FIX(1.082392200)
+ private const int FIX_1_414213562 = 362; // FIX(1.414213562)
+ private const int FIX_1_847759065 = 473; // FIX(1.847759065)
+ private const int FIX_2_613125930 = 669; // FIX(2.613125930)
+#pragma warning restore SA1310 // Field names must not contain underscore
+
+ private const int ConstBits = 8;
+ private const int Pass1Bits = 2; // Factional bits in scale factors
+ private const int MaxJSample = 255;
+ private const int CenterJSample = 128;
+ private const int RangeCenter = (MaxJSample * 2) + 2;
+
+ // First segment of range limit table: limit[x] = 0 for x < 0
+ // allow negative subscripts of simple table
+ private const int TableOffset = 2 * (MaxJSample + 1);
+ private const int LimitOffset = TableOffset - (RangeCenter - CenterJSample);
+
+ // Each IDCT routine is responsible for range-limiting its results and
+ // converting them to unsigned form (0..MaxJSample). The raw outputs could
+ // be quite far out of range if the input data is corrupt, so a bulletproof
+ // range-limiting step is required. We use a mask-and-table-lookup method
+ // to do the combined operations quickly, assuming that MaxJSample+1
+ // is a power of 2.
+ private const int RangeMask = (MaxJSample * 4) + 3; // 2 bits wider than legal samples
+
+ private static readonly byte[] Limit = new byte[5 * (MaxJSample + 1)];
+
+ static IDCT()
+ {
+ // Main part of range limit table: limit[x] = x
+ int i;
+ for (i = 0; i <= MaxJSample; i++)
+ {
+ Limit[TableOffset + i] = (byte)i;
+ }
+
+ // End of range limit table: Limit[x] = MaxJSample for x > MaxJSample
+ for (; i < 3 * (MaxJSample + 1); i++)
+ {
+ Limit[TableOffset + i] = MaxJSample;
+ }
+ }
+
+ ///
+ /// A port of Poppler's IDCT method which in turn is taken from:
+ /// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+ /// 'Practical Fast 1-D DCT Algorithms with 11 Multiplications',
+ /// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, 988-991.
+ ///
+ /// The fram component
+ /// The block buffer offset
+ /// The computational buffer for holding temp values
+ /// The quantization table
+ public static void QuantizeAndInverse(ref FrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span quantizationTable)
+ {
+ Span blockData = component.BlockData.Slice(blockBufferOffset);
+ int v0, v1, v2, v3, v4, v5, v6, v7;
+ int p0, p1, p2, p3, p4, p5, p6, p7;
+ int t;
+
+ // inverse DCT on rows
+ for (int row = 0; row < 64; row += 8)
+ {
+ // gather block data
+ p0 = blockData[row];
+ p1 = blockData[row + 1];
+ p2 = blockData[row + 2];
+ p3 = blockData[row + 3];
+ p4 = blockData[row + 4];
+ p5 = blockData[row + 5];
+ p6 = blockData[row + 6];
+ p7 = blockData[row + 7];
+
+ // dequant p0
+ p0 *= quantizationTable[row];
+
+ // check for all-zero AC coefficients
+ if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0)
+ {
+ t = ((DctSqrt2 * p0) + 512) >> 10;
+ short st = (short)t;
+ computationBuffer[row] = st;
+ computationBuffer[row + 1] = st;
+ computationBuffer[row + 2] = st;
+ computationBuffer[row + 3] = st;
+ computationBuffer[row + 4] = st;
+ computationBuffer[row + 5] = st;
+ computationBuffer[row + 6] = st;
+ computationBuffer[row + 7] = st;
+ continue;
+ }
+
+ // dequant p1 ... p7
+ p1 *= quantizationTable[row + 1];
+ p2 *= quantizationTable[row + 2];
+ p3 *= quantizationTable[row + 3];
+ p4 *= quantizationTable[row + 4];
+ p5 *= quantizationTable[row + 5];
+ p6 *= quantizationTable[row + 6];
+ p7 *= quantizationTable[row + 7];
+
+ // stage 4
+ v0 = ((DctSqrt2 * p0) + 128) >> 8;
+ v1 = ((DctSqrt2 * p4) + 128) >> 8;
+ v2 = p2;
+ v3 = p6;
+ v4 = ((DctSqrt1D2 * (p1 - p7)) + 128) >> 8;
+ v7 = ((DctSqrt1D2 * (p1 + p7)) + 128) >> 8;
+ v5 = p3 << 4;
+ v6 = p5 << 4;
+
+ // stage 3
+ v0 = (v0 + v1 + 1) >> 1;
+ v1 = v0 - v1;
+ t = ((v2 * DctSin6) + (v3 * DctCos6) + 128) >> 8;
+ v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 128) >> 8;
+ v3 = t;
+ v4 = (v4 + v6 + 1) >> 1;
+ v6 = v4 - v6;
+ v7 = (v7 + v5 + 1) >> 1;
+ v5 = v7 - v5;
+
+ // stage 2
+ v0 = (v0 + v3 + 1) >> 1;
+ v3 = v0 - v3;
+ v1 = (v1 + v2 + 1) >> 1;
+ v2 = v1 - v2;
+ t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12;
+ v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12;
+ v7 = t;
+ t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12;
+ v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12;
+ v6 = t;
+
+ // stage 1
+ computationBuffer[row] = (short)(v0 + v7);
+ computationBuffer[row + 7] = (short)(v0 - v7);
+ computationBuffer[row + 1] = (short)(v1 + v6);
+ computationBuffer[row + 6] = (short)(v1 - v6);
+ computationBuffer[row + 2] = (short)(v2 + v5);
+ computationBuffer[row + 5] = (short)(v2 - v5);
+ computationBuffer[row + 3] = (short)(v3 + v4);
+ computationBuffer[row + 4] = (short)(v3 - v4);
+ }
+
+ // inverse DCT on columns
+ for (int col = 0; col < 8; ++col)
+ {
+ p0 = computationBuffer[col];
+ p1 = computationBuffer[col + 8];
+ p2 = computationBuffer[col + 16];
+ p3 = computationBuffer[col + 24];
+ p4 = computationBuffer[col + 32];
+ p5 = computationBuffer[col + 40];
+ p6 = computationBuffer[col + 48];
+ p7 = computationBuffer[col + 56];
+
+ // check for all-zero AC coefficients
+ if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0)
+ {
+ t = ((DctSqrt2 * p0) + 8192) >> 14;
+
+ // convert to 8 bit
+ t = (t < -2040) ? 0 : (t >= 2024) ? MaxJSample : (t + 2056) >> 4;
+ short st = (short)t;
+
+ blockData[col] = st;
+ blockData[col + 8] = st;
+ blockData[col + 16] = st;
+ blockData[col + 24] = st;
+ blockData[col + 32] = st;
+ blockData[col + 40] = st;
+ blockData[col + 48] = st;
+ blockData[col + 56] = st;
+ continue;
+ }
+
+ // stage 4
+ v0 = ((DctSqrt2 * p0) + 2048) >> 12;
+ v1 = ((DctSqrt2 * p4) + 2048) >> 12;
+ v2 = p2;
+ v3 = p6;
+ v4 = ((DctSqrt1D2 * (p1 - p7)) + 2048) >> 12;
+ v7 = ((DctSqrt1D2 * (p1 + p7)) + 2048) >> 12;
+ v5 = p3;
+ v6 = p5;
+
+ // stage 3
+ // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when
+ // converting to UInt8 range later.
+ v0 = ((v0 + v1 + 1) >> 1) + 4112;
+ v1 = v0 - v1;
+ t = ((v2 * DctSin6) + (v3 * DctCos6) + 2048) >> 12;
+ v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 2048) >> 12;
+ v3 = t;
+ v4 = (v4 + v6 + 1) >> 1;
+ v6 = v4 - v6;
+ v7 = (v7 + v5 + 1) >> 1;
+ v5 = v7 - v5;
+
+ // stage 2
+ v0 = (v0 + v3 + 1) >> 1;
+ v3 = v0 - v3;
+ v1 = (v1 + v2 + 1) >> 1;
+ v2 = v1 - v2;
+ t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12;
+ v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12;
+ v7 = t;
+ t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12;
+ v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12;
+ v6 = t;
+
+ // stage 1
+ p0 = v0 + v7;
+ p7 = v0 - v7;
+ p1 = v1 + v6;
+ p6 = v1 - v6;
+ p2 = v2 + v5;
+ p5 = v2 - v5;
+ p3 = v3 + v4;
+ p4 = v3 - v4;
+
+ // convert to 8-bit integers
+ p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? MaxJSample : p0 >> 4;
+ p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? MaxJSample : p1 >> 4;
+ p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? MaxJSample : p2 >> 4;
+ p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? MaxJSample : p3 >> 4;
+ p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? MaxJSample : p4 >> 4;
+ p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? MaxJSample : p5 >> 4;
+ p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? MaxJSample : p6 >> 4;
+ p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? MaxJSample : p7 >> 4;
+
+ // store block data
+ blockData[col] = (short)p0;
+ blockData[col + 8] = (short)p1;
+ blockData[col + 16] = (short)p2;
+ blockData[col + 24] = (short)p3;
+ blockData[col + 32] = (short)p4;
+ blockData[col + 40] = (short)p5;
+ blockData[col + 48] = (short)p6;
+ blockData[col + 56] = (short)p7;
+ }
+ }
+
+ ///
+ /// A port of
+ /// A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT
+ /// on each row(or vice versa, but it's more convenient to emit a row at
+ /// a time). Direct algorithms are also available, but they are much more
+ /// complex and seem not to be any faster when reduced to code.
+ ///
+ /// This implementation is based on Arai, Agui, and Nakajima's algorithm for
+ /// scaled DCT.Their original paper (Trans.IEICE E-71(11):1095) is in
+ /// Japanese, but the algorithm is described in the Pennebaker & Mitchell
+ /// JPEG textbook(see REFERENCES section in file README.ijg). The following
+ /// code is based directly on figure 4-8 in P&M.
+ /// While an 8-point DCT cannot be done in less than 11 multiplies, it is
+ /// possible to arrange the computation so that many of the multiplies are
+ /// simple scalings of the final outputs.These multiplies can then be
+ /// folded into the multiplications or divisions by the JPEG quantization
+ /// table entries. The AA&N method leaves only 5 multiplies and 29 adds
+ /// to be done in the DCT itself.
+ /// The primary disadvantage of this method is that with fixed-point math,
+ /// accuracy is lost due to imprecise representation of the scaled
+ /// quantization values.The smaller the quantization table entry, the less
+ /// precise the scaled value, so this implementation does worse with high -
+ /// quality - setting files than with low - quality ones.
+ ///
+ /// The frame component
+ /// The block buffer offset
+ /// The computational buffer for holding temp values
+ /// The multiplier table
+ public static void QuantizeAndInverseFast(ref FrameComponent component, int blockBufferOffset, ref Span computationBuffer, ref Span multiplierTable)
+ {
+ Span blockData = component.BlockData.Slice(blockBufferOffset);
+ int p0, p1, p2, p3, p4, p5, p6, p7;
+
+ for (int col = 0; col < 8; col++)
+ {
+ // Gather block data
+ p0 = blockData[col];
+ p1 = blockData[col + 8];
+ p2 = blockData[col + 16];
+ p3 = blockData[col + 24];
+ p4 = blockData[col + 32];
+ p5 = blockData[col + 40];
+ p6 = blockData[col + 48];
+ p7 = blockData[col + 56];
+
+ int tmp0 = p0 * multiplierTable[col];
+
+ // Due to quantization, we will usually find that many of the input
+ // coefficients are zero, especially the AC terms. We can exploit this
+ // by short-circuiting the IDCT calculation for any column in which all
+ // the AC terms are zero. In that case each output is equal to the
+ // DC coefficient (with scale factor as needed).
+ // With typical images and quantization tables, half or more of the
+ // column DCT calculations can be simplified this way.
+ if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0)
+ {
+ short dcval = (short)tmp0;
+
+ computationBuffer[col] = dcval;
+ computationBuffer[col + 8] = dcval;
+ computationBuffer[col + 16] = dcval;
+ computationBuffer[col + 24] = dcval;
+ computationBuffer[col + 32] = dcval;
+ computationBuffer[col + 40] = dcval;
+ computationBuffer[col + 48] = dcval;
+ computationBuffer[col + 56] = dcval;
+
+ continue;
+ }
+
+ // Even part
+ int tmp1 = p2 * multiplierTable[col + 16];
+ int tmp2 = p4 * multiplierTable[col + 32];
+ int tmp3 = p6 * multiplierTable[col + 48];
+
+ int tmp10 = tmp0 + tmp2; // Phase 3
+ int tmp11 = tmp0 - tmp2;
+
+ int tmp13 = tmp1 + tmp3; // Phases 5-3
+ int tmp12 = Multiply(tmp1 - tmp3, FIX_1_414213562) - tmp13; // 2*c4
+
+ tmp0 = tmp10 + tmp13; // Phase 2
+ tmp3 = tmp10 - tmp13;
+ tmp1 = tmp11 + tmp12;
+ tmp2 = tmp11 - tmp12;
+
+ // Odd Part
+ int tmp4 = p1 * multiplierTable[col + 8];
+ int tmp5 = p3 * multiplierTable[col + 24];
+ int tmp6 = p5 * multiplierTable[col + 40];
+ int tmp7 = p7 * multiplierTable[col + 56];
+
+ int z13 = tmp6 + tmp5; // Phase 6
+ int z10 = tmp6 - tmp5;
+ int z11 = tmp4 + tmp7;
+ int z12 = tmp4 - tmp7;
+
+ tmp7 = z11 + z13; // Phase 5
+ tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4
+
+ int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2
+ tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6)
+ tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6)
+
+ tmp6 = tmp12 - tmp7; // Phase 2
+ tmp5 = tmp11 - tmp6;
+ tmp4 = tmp10 - tmp5;
+
+ computationBuffer[col] = (short)(tmp0 + tmp7);
+ computationBuffer[col + 56] = (short)(tmp0 - tmp7);
+ computationBuffer[col + 8] = (short)(tmp1 + tmp6);
+ computationBuffer[col + 48] = (short)(tmp1 - tmp6);
+ computationBuffer[col + 16] = (short)(tmp2 + tmp5);
+ computationBuffer[col + 40] = (short)(tmp2 - tmp5);
+ computationBuffer[col + 24] = (short)(tmp3 + tmp4);
+ computationBuffer[col + 32] = (short)(tmp3 - tmp4);
+ }
+
+ // Pass 2: process rows from work array, store into output array.
+ // Note that we must descale the results by a factor of 8 == 2**3,
+ // and also undo the pass 1 bits scaling.
+ for (int row = 0; row < 64; row += 8)
+ {
+ p1 = computationBuffer[row + 1];
+ p2 = computationBuffer[row + 2];
+ p3 = computationBuffer[row + 3];
+ p4 = computationBuffer[row + 4];
+ p5 = computationBuffer[row + 5];
+ p6 = computationBuffer[row + 6];
+ p7 = computationBuffer[row + 7];
+
+ // Add range center and fudge factor for final descale and range-limit.
+ int z5 = computationBuffer[row] + (RangeCenter << (Pass1Bits + 3)) + (1 << (Pass1Bits + 2));
+
+ // Check for all-zero AC coefficients
+ if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0)
+ {
+ byte dcval = Limit[LimitOffset + (RightShift(z5, Pass1Bits + 3) & RangeMask)];
+
+ blockData[row] = dcval;
+ blockData[row + 1] = dcval;
+ blockData[row + 2] = dcval;
+ blockData[row + 3] = dcval;
+ blockData[row + 4] = dcval;
+ blockData[row + 5] = dcval;
+ blockData[row + 6] = dcval;
+ blockData[row + 7] = dcval;
+
+ continue;
+ }
+
+ // Even part
+ int tmp10 = z5 + p4;
+ int tmp11 = z5 - p4;
+
+ int tmp13 = p2 + p6;
+ int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; // 2*c4
+
+ int tmp0 = tmp10 + tmp13;
+ int tmp3 = tmp10 - tmp13;
+ int tmp1 = tmp11 + tmp12;
+ int tmp2 = tmp11 - tmp12;
+
+ // Odd part
+ int z13 = p5 + p3;
+ int z10 = p5 - p3;
+ int z11 = p1 + p7;
+ int z12 = p1 - p7;
+
+ int tmp7 = z11 + z13; // Phase 5
+ tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4
+
+ z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2
+ tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6)
+ tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6)
+
+ int tmp6 = tmp12 - tmp7; // Phase 2
+ int tmp5 = tmp11 - tmp6;
+ int tmp4 = tmp10 - tmp5;
+
+ // Final output stage: scale down by a factor of 8, offset, and range-limit
+ blockData[row] = Limit[LimitOffset + (RightShift(tmp0 + tmp7, Pass1Bits + 3) & RangeMask)];
+ blockData[row + 7] = Limit[LimitOffset + (RightShift(tmp0 - tmp7, Pass1Bits + 3) & RangeMask)];
+ blockData[row + 1] = Limit[LimitOffset + (RightShift(tmp1 + tmp6, Pass1Bits + 3) & RangeMask)];
+ blockData[row + 6] = Limit[LimitOffset + (RightShift(tmp1 - tmp6, Pass1Bits + 3) & RangeMask)];
+ blockData[row + 2] = Limit[LimitOffset + (RightShift(tmp2 + tmp5, Pass1Bits + 3) & RangeMask)];
+ blockData[row + 5] = Limit[LimitOffset + (RightShift(tmp2 - tmp5, Pass1Bits + 3) & RangeMask)];
+ blockData[row + 3] = Limit[LimitOffset + (RightShift(tmp3 + tmp4, Pass1Bits + 3) & RangeMask)];
+ blockData[row + 4] = Limit[LimitOffset + (RightShift(tmp3 - tmp4, Pass1Bits + 3) & RangeMask)];
+ }
+ }
+
+ ///
+ /// Descale and correctly round an int value that's scaled by bits.
+ /// We assume rounds towards minus infinity, so adding
+ /// the fudge factor is correct for either sign of .
+ ///
+ /// The value
+ /// The number of bits
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Descale(int value, int n)
+ {
+ return RightShift(value + (1 << (n - 1)), n);
+ }
+
+ ///
+ /// Multiply a variable by an int constant, and immediately descale.
+ ///
+ /// The value
+ /// The multiplier
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Multiply(int val, int c)
+ {
+ return Descale(val * c, ConstBits);
+ }
+
+ ///
+ /// Right-shifts the value by the given amount
+ ///
+ /// The value
+ /// The amount to shift by
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int RightShift(int value, int shift)
+ {
+ return value >> shift;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs
new file mode 100644
index 000000000..6baecdf68
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+
+ ///
+ /// Provides information about the JFIF marker segment
+ /// TODO: Thumbnail?
+ ///
+ internal struct JFif : IEquatable
+ {
+ ///
+ /// The major version
+ ///
+ public byte MajorVersion;
+
+ ///
+ /// The minor version
+ ///
+ public byte MinorVersion;
+
+ ///
+ /// Units for the following pixel density fields
+ /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity
+ /// 01 : Pixels per inch (2.54 cm)
+ /// 02 : Pixels per centimeter
+ ///
+ public byte DensityUnits;
+
+ ///
+ /// Horizontal pixel density. Must not be zero.
+ ///
+ public short XDensity;
+
+ ///
+ /// Vertical pixel density. Must not be zero.
+ ///
+ public short YDensity;
+
+ ///
+ public bool Equals(JFif other)
+ {
+ return this.MajorVersion == other.MajorVersion
+ && this.MinorVersion == other.MinorVersion
+ && this.DensityUnits == other.DensityUnits
+ && this.XDensity == other.XDensity
+ && this.YDensity == other.YDensity;
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ return obj is JFif && this.Equals((JFif)obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.MajorVersion.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.MinorVersion.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.DensityUnits.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.XDensity.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.YDensity.GetHashCode();
+ return hashCode;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs
new file mode 100644
index 000000000..e88e39617
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs
@@ -0,0 +1,147 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+ using System.Diagnostics;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+ using ImageSharp.Memory;
+
+ ///
+ /// Represents a section of the jpeg component data laid out in pixel order.
+ ///
+ internal struct JpegPixelArea : IDisposable
+ {
+ private readonly int imageWidth;
+
+ private readonly int imageHeight;
+
+ private Buffer componentData;
+
+ private int rowStride;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The image width
+ /// The image height
+ /// The number of components
+ public JpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents)
+ {
+ this.imageWidth = imageWidth;
+ this.imageHeight = imageHeight;
+ this.Width = 0;
+ this.Height = 0;
+ this.NumberOfComponents = numberOfComponents;
+ this.componentData = null;
+ this.rowStride = 0;
+ }
+
+ ///
+ /// Gets the number of components
+ ///
+ public int NumberOfComponents { get; }
+
+ ///
+ /// Gets the width
+ ///
+ public int Width { get; private set; }
+
+ ///
+ /// Gets the height
+ ///
+ public int Height { get; private set; }
+
+ ///
+ /// Organsizes the decoded jpeg components into a linear array ordered by component.
+ /// This must be called before attempting to retrieve the data.
+ ///
+ /// The jpeg component blocks
+ /// The pixel area width
+ /// The pixel area height
+ public void LinearizeBlockData(ComponentBlocks components, int width, int height)
+ {
+ this.Width = width;
+ this.Height = height;
+ int numberOfComponents = this.NumberOfComponents;
+ this.rowStride = width * numberOfComponents;
+ var scale = new Vector2(this.imageWidth / (float)width, this.imageHeight / (float)height);
+
+ this.componentData = new Buffer(width * height * numberOfComponents);
+ Span componentDataSpan = this.componentData;
+ const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs
+
+ using (var xScaleBlockOffset = new Buffer(width))
+ {
+ Span xScaleBlockOffsetSpan = xScaleBlockOffset;
+ for (int i = 0; i < numberOfComponents; i++)
+ {
+ ref Component component = ref components.Components[i];
+ Vector2 componentScale = component.Scale * scale;
+ int offset = i;
+ Span output = component.Output;
+ int blocksPerScanline = (component.BlocksPerLine + 1) << 3;
+
+ // Precalculate the xScaleBlockOffset
+ int j;
+ for (int x = 0; x < width; x++)
+ {
+ j = (int)(x * componentScale.X);
+ xScaleBlockOffsetSpan[x] = (int)((j & Mask3Lsb) << 3) | (j & 7);
+ }
+
+ // Linearize the blocks of the component
+ for (int y = 0; y < height; y++)
+ {
+ j = (int)(y * componentScale.Y);
+ int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3);
+ for (int x = 0; x < width; x++)
+ {
+ componentDataSpan[offset] = (byte)output[index + xScaleBlockOffsetSpan[x]];
+ offset += numberOfComponents;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets a representing the row 'y' beginning from the the first byte on that row.
+ ///
+ /// The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the pixel area.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetRowSpan(int y)
+ {
+ this.CheckCoordinates(y);
+ return this.componentData.Slice(y * this.rowStride, this.rowStride);
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.componentData?.Dispose();
+ this.componentData = null;
+ }
+
+ ///
+ /// Checks the coordinates to ensure they are within bounds.
+ ///
+ /// The y-coordinate of the row. Must be greater than zero and less than the height of the area.
+ ///
+ /// Thrown if the coordinates are not within the bounds of the image.
+ ///
+ [Conditional("DEBUG")]
+ private void CheckCoordinates(int y)
+ {
+ if (y < 0 || y >= this.Height)
+ {
+ throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the area bounds.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs
new file mode 100644
index 000000000..352dc43f2
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs
@@ -0,0 +1,67 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.Memory;
+
+ ///
+ /// Contains the quantization tables.
+ ///
+ internal sealed class QuantizationTables : IDisposable
+ {
+ ///
+ /// Gets the ZigZag scan table
+ ///
+ public static byte[] DctZigZag
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ =
+ {
+ 0,
+ 1, 8,
+ 16, 9, 2,
+ 3, 10, 17, 24,
+ 32, 25, 18, 11, 4,
+ 5, 12, 19, 26, 33, 40,
+ 48, 41, 34, 27, 20, 13, 6,
+ 7, 14, 21, 28, 35, 42, 49, 56,
+ 57, 50, 43, 36, 29, 22, 15,
+ 23, 30, 37, 44, 51, 58,
+ 59, 52, 45, 38, 31,
+ 39, 46, 53, 60,
+ 61, 54, 47,
+ 55, 62,
+ 63
+ };
+
+ ///
+ /// Gets or sets the quantization tables.
+ ///
+ public Buffer2D Tables
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get; set;
+ }
+
+ = new Buffer2D(64, 4);
+
+ ///
+ public void Dispose()
+ {
+ if (this.Tables != null)
+ {
+ this.Tables.Dispose();
+ this.Tables = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs
new file mode 100644
index 000000000..8b711eaa8
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs
@@ -0,0 +1,947 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System;
+#if DEBUG
+ using System.Diagnostics;
+#endif
+ using System.IO;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Provides the means to decode a spectral scan
+ ///
+ internal struct ScanDecoder
+ {
+ private byte[] markerBuffer;
+
+ private int bitsData;
+
+ private int bitsCount;
+
+#pragma warning disable 414
+ private int bitsUnRead;
+
+ private int accumulator;
+#pragma warning restore 414
+
+ private int specStart;
+
+ private int specEnd;
+
+ private int eobrun;
+
+ private int compIndex;
+
+ private int successiveState;
+
+ private int successiveACState;
+
+ private int successiveACNextValue;
+
+ private bool endOfStreamReached;
+
+ private bool unexpectedMarkerReached;
+
+ ///
+ /// Decodes the spectral scan
+ ///
+ /// The image frame
+ /// The input stream
+ /// The DC Huffman tables
+ /// The AC Huffman tables
+ /// The scan components
+ /// The component index within the array
+ /// The length of the components. Different to the array length
+ /// The reset interval
+ /// The spectral selection start
+ /// The spectral selection end
+ /// The successive approximation bit high end
+ /// The successive approximation bit low end
+ public void DecodeScan(
+ Frame frame,
+ Stream stream,
+ HuffmanTables dcHuffmanTables,
+ HuffmanTables acHuffmanTables,
+ FrameComponent[] components,
+ int componentIndex,
+ int componentsLength,
+ ushort resetInterval,
+ int spectralStart,
+ int spectralEnd,
+ int successivePrev,
+ int successive)
+ {
+ this.markerBuffer = new byte[2];
+ this.compIndex = componentIndex;
+ this.specStart = spectralStart;
+ this.specEnd = spectralEnd;
+ this.successiveState = successive;
+ this.endOfStreamReached = false;
+ this.unexpectedMarkerReached = false;
+
+ bool progressive = frame.Progressive;
+ int mcusPerLine = frame.McusPerLine;
+
+ int mcu = 0;
+ int mcuExpected;
+ if (componentsLength == 1)
+ {
+ mcuExpected = components[this.compIndex].BlocksPerLine * components[this.compIndex].BlocksPerColumn;
+ }
+ else
+ {
+ mcuExpected = mcusPerLine * frame.McusPerColumn;
+ }
+
+ FileMarker fileMarker;
+ while (mcu < mcuExpected)
+ {
+ // Reset interval stuff
+ int mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected;
+ for (int i = 0; i < components.Length; i++)
+ {
+ ref FrameComponent c = ref components[i];
+ c.Pred = 0;
+ }
+
+ this.eobrun = 0;
+
+ if (!progressive)
+ {
+ this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
+ }
+ else
+ {
+ if (this.specStart == 0)
+ {
+ if (successivePrev == 0)
+ {
+ this.DecodeScanDCFirst(dcHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
+ }
+ else
+ {
+ this.DecodeScanDCSuccessive(components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
+ }
+ }
+ else
+ {
+ if (successivePrev == 0)
+ {
+ this.DecodeScanACFirst(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
+ }
+ else
+ {
+ this.DecodeScanACSuccessive(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
+ }
+ }
+ }
+
+ // Find marker
+ this.bitsCount = 0;
+ this.accumulator = 0;
+ this.bitsUnRead = 0;
+ fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
+
+ // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past
+ // those to attempt to find a valid marker (fixes issue4090.pdf) in original code.
+ if (fileMarker.Invalid)
+ {
+#if DEBUG
+ Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
+#endif
+ }
+
+ ushort marker = fileMarker.Marker;
+
+ // RSTn - We've alread read the bytes and altered the position so no need to skip
+ if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
+ {
+ continue;
+ }
+
+ if (!fileMarker.Invalid)
+ {
+ // We've found a valid marker.
+ // Rewind the stream to the position of the marker and break
+ stream.Position = fileMarker.Position;
+ break;
+ }
+ }
+
+ fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
+
+ // Some images include more Scan blocks than expected, skip past those and
+ // attempt to find the next valid marker (fixes issue8182.pdf) in original code.
+ if (fileMarker.Invalid)
+ {
+#if DEBUG
+ Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
+#endif
+ }
+ else
+ {
+ // We've found a valid marker.
+ // Rewind the stream to the position of the marker
+ stream.Position = fileMarker.Position;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int GetBlockBufferOffset(FrameComponent component, int row, int col)
+ {
+ return 64 * (((component.BlocksPerLine + 1) * row) + col);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeScanBaseline(
+ HuffmanTables dcHuffmanTables,
+ HuffmanTables acHuffmanTables,
+ FrameComponent[] components,
+ int componentsLength,
+ int mcusPerLine,
+ int mcuToRead,
+ ref int mcu,
+ Stream stream)
+ {
+ if (componentsLength == 1)
+ {
+ ref FrameComponent component = ref components[this.compIndex];
+ ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
+
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, ref component, mcu, stream);
+ mcu++;
+ }
+ }
+ else
+ {
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ for (int i = 0; i < componentsLength; i++)
+ {
+ ref FrameComponent component = ref components[i];
+ ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
+ int h = component.HorizontalFactor;
+ int v = component.VerticalFactor;
+
+ for (int j = 0; j < v; j++)
+ {
+ for (int k = 0; k < h; k++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream);
+ }
+ }
+ }
+
+ mcu++;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeScanDCFirst(
+ HuffmanTables dcHuffmanTables,
+ FrameComponent[] components,
+ int componentsLength,
+ int mcusPerLine,
+ int mcuToRead,
+ ref int mcu,
+ Stream stream)
+ {
+ if (componentsLength == 1)
+ {
+ ref FrameComponent component = ref components[this.compIndex];
+ ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
+
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeBlockDCFirst(ref dcHuffmanTable, ref component, mcu, stream);
+ mcu++;
+ }
+ }
+ else
+ {
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ for (int i = 0; i < componentsLength; i++)
+ {
+ ref FrameComponent component = ref components[i];
+ ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
+ int h = component.HorizontalFactor;
+ int v = component.VerticalFactor;
+
+ for (int j = 0; j < v; j++)
+ {
+ for (int k = 0; k < h; k++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeMcuDCFirst(ref dcHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream);
+ }
+ }
+ }
+
+ mcu++;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeScanDCSuccessive(
+ FrameComponent[] components,
+ int componentsLength,
+ int mcusPerLine,
+ int mcuToRead,
+ ref int mcu,
+ Stream stream)
+ {
+ if (componentsLength == 1)
+ {
+ ref FrameComponent component = ref components[this.compIndex];
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeBlockDCSuccessive(ref component, mcu, stream);
+ mcu++;
+ }
+ }
+ else
+ {
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ for (int i = 0; i < componentsLength; i++)
+ {
+ ref FrameComponent component = ref components[i];
+ int h = component.HorizontalFactor;
+ int v = component.VerticalFactor;
+ for (int j = 0; j < v; j++)
+ {
+ for (int k = 0; k < h; k++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeMcuDCSuccessive(ref component, mcusPerLine, mcu, j, k, stream);
+ }
+ }
+ }
+
+ mcu++;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeScanACFirst(
+ HuffmanTables acHuffmanTables,
+ FrameComponent[] components,
+ int componentsLength,
+ int mcusPerLine,
+ int mcuToRead,
+ ref int mcu,
+ Stream stream)
+ {
+ if (componentsLength == 1)
+ {
+ ref FrameComponent component = ref components[this.compIndex];
+ ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
+
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeBlockACFirst(ref acHuffmanTable, ref component, mcu, stream);
+ mcu++;
+ }
+ }
+ else
+ {
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ for (int i = 0; i < componentsLength; i++)
+ {
+ ref FrameComponent component = ref components[i];
+ ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
+ int h = component.HorizontalFactor;
+ int v = component.VerticalFactor;
+
+ for (int j = 0; j < v; j++)
+ {
+ for (int k = 0; k < h; k++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeMcuACFirst(ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream);
+ }
+ }
+ }
+
+ mcu++;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeScanACSuccessive(
+ HuffmanTables acHuffmanTables,
+ FrameComponent[] components,
+ int componentsLength,
+ int mcusPerLine,
+ int mcuToRead,
+ ref int mcu,
+ Stream stream)
+ {
+ if (componentsLength == 1)
+ {
+ ref FrameComponent component = ref components[this.compIndex];
+ ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
+
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeBlockACSuccessive(ref acHuffmanTable, ref component, mcu, stream);
+ mcu++;
+ }
+ }
+ else
+ {
+ for (int n = 0; n < mcuToRead; n++)
+ {
+ for (int i = 0; i < componentsLength; i++)
+ {
+ ref FrameComponent component = ref components[i];
+ ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
+ int h = component.HorizontalFactor;
+ int v = component.VerticalFactor;
+
+ for (int j = 0; j < v; j++)
+ {
+ for (int k = 0; k < h; k++)
+ {
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ continue;
+ }
+
+ this.DecodeMcuACSuccessive(ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream);
+ }
+ }
+ }
+
+ mcu++;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeBlockBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream)
+ {
+ int blockRow = mcu / component.BlocksPerLine;
+ int blockCol = mcu % component.BlocksPerLine;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeBaseline(ref component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeMcuBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ {
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ int blockRow = (mcuRow * component.VerticalFactor) + row;
+ int blockCol = (mcuCol * component.HorizontalFactor) + col;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeBaseline(ref component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeBlockDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcu, Stream stream)
+ {
+ int blockRow = mcu / component.BlocksPerLine;
+ int blockCol = mcu % component.BlocksPerLine;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeDCFirst(ref component, offset, ref dcHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeMcuDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ {
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ int blockRow = (mcuRow * component.VerticalFactor) + row;
+ int blockCol = (mcuCol * component.HorizontalFactor) + col;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeDCFirst(ref component, offset, ref dcHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeBlockDCSuccessive(ref FrameComponent component, int mcu, Stream stream)
+ {
+ int blockRow = mcu / component.BlocksPerLine;
+ int blockCol = mcu % component.BlocksPerLine;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeDCSuccessive(ref component, offset, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeMcuDCSuccessive(ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ {
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ int blockRow = (mcuRow * component.VerticalFactor) + row;
+ int blockCol = (mcuCol * component.HorizontalFactor) + col;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeDCSuccessive(ref component, offset, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeBlockACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream)
+ {
+ int blockRow = mcu / component.BlocksPerLine;
+ int blockCol = mcu % component.BlocksPerLine;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeACFirst(ref component, offset, ref acHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeMcuACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ {
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ int blockRow = (mcuRow * component.VerticalFactor) + row;
+ int blockCol = (mcuCol * component.HorizontalFactor) + col;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeACFirst(ref component, offset, ref acHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeBlockACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream)
+ {
+ int blockRow = mcu / component.BlocksPerLine;
+ int blockCol = mcu % component.BlocksPerLine;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeACSuccessive(ref component, offset, ref acHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeMcuACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream)
+ {
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ int blockRow = (mcuRow * component.VerticalFactor) + row;
+ int blockCol = (mcuCol * component.HorizontalFactor) + col;
+ int offset = GetBlockBufferOffset(component, blockRow, blockCol);
+ this.DecodeACSuccessive(ref component, offset, ref acHuffmanTable, stream);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int ReadBit(Stream stream)
+ {
+ // TODO: I wonder if we can do this two bytes at a time; libjpeg turbo seems to do that?
+ if (this.bitsCount > 0)
+ {
+ this.bitsCount--;
+ return (this.bitsData >> this.bitsCount) & 1;
+ }
+
+ this.bitsData = stream.ReadByte();
+
+ if (this.bitsData == -0x1)
+ {
+ // We've encountered the end of the file stream which means there's no EOI marker in the image
+ this.endOfStreamReached = true;
+ }
+
+ if (this.bitsData == JpegConstants.Markers.Prefix)
+ {
+ int nextByte = stream.ReadByte();
+ if (nextByte != 0)
+ {
+#if DEBUG
+ Debug.WriteLine($"DecodeScan - Unexpected marker {(this.bitsData << 8) | nextByte:X} at {stream.Position}");
+#endif
+
+ // We've encountered an unexpected marker. Reverse the stream and exit.
+ this.unexpectedMarkerReached = true;
+ stream.Position -= 2;
+ }
+
+ // Unstuff 0
+ }
+
+ this.bitsCount = 7;
+
+ return this.bitsData >> 7;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private short DecodeHuffman(ref HuffmanTable tree, Stream stream)
+ {
+ short code = -1;
+
+ // TODO: Adding this code introduces error into the decoder.
+ // NOTES # During investigation of the libjpeg implementation it appears that they pull 32bits at a time and operate on those bits
+ // using 3 methods: FillBits, PeekBits, and ReadBits. We should attempt to do the same.
+ // It doesn't appear to speed anything up either.
+ // if (this.bitsUnRead < 8)
+ // {
+ // if (this.bitsCount <= 0)
+ // {
+ // code = (short)this.ReadBit(stream);
+ // if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ // {
+ // return -1;
+ // }
+ //
+ // this.bitsUnRead += 8;
+ // }
+ //
+ // this.accumulator = (this.accumulator << 8) | this.bitsData;
+ // int lutIndex = (this.accumulator >> (8 - this.bitsUnRead)) & 0xFF;
+ // int v = tree.Lookahead[lutIndex];
+ // if (v != 0)
+ // {
+ // int nb = (v & 0xFF) - 1;
+ // this.bitsCount -= nb - 1;
+ // this.bitsUnRead -= nb;
+ // v = v >> 8;
+ // return (short)v;
+ // }
+ // }
+ if (code == -1)
+ {
+ code = (short)this.ReadBit(stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return -1;
+ }
+ }
+
+ // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
+ int i = 1;
+
+ while (code > tree.MaxCode[i])
+ {
+ code <<= 1;
+ code |= (short)this.ReadBit(stream);
+
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return -1;
+ }
+
+ i++;
+ }
+
+ int j = tree.ValOffset[i];
+ return tree.HuffVal[(j + code) & 0xFF];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int Receive(int length, Stream stream)
+ {
+ int n = 0;
+ while (length > 0)
+ {
+ int bit = this.ReadBit(stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return -1;
+ }
+
+ n = (n << 1) | bit;
+ length--;
+ }
+
+ return n;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int ReceiveAndExtend(int length, Stream stream)
+ {
+ if (length == 1)
+ {
+ return this.ReadBit(stream) == 1 ? 1 : -1;
+ }
+
+ int n = this.Receive(length, stream);
+ if (n >= 1 << (length - 1))
+ {
+ return n;
+ }
+
+ return n + (-1 << length) + 1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeBaseline(ref FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, Stream stream)
+ {
+ int t = this.DecodeHuffman(ref dcHuffmanTable, stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream);
+ component.BlockData[offset] = (short)(component.Pred += diff);
+
+ int k = 1;
+ while (k < 64)
+ {
+ int rs = this.DecodeHuffman(ref acHuffmanTable, stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ int s = rs & 15;
+ int r = rs >> 4;
+
+ if (s == 0)
+ {
+ if (r < 15)
+ {
+ break;
+ }
+
+ k += 16;
+ continue;
+ }
+
+ k += r;
+
+ if (k > 63)
+ {
+ break;
+ }
+
+ byte z = QuantizationTables.DctZigZag[k];
+ short re = (short)this.ReceiveAndExtend(s, stream);
+ component.BlockData[offset + z] = re;
+ k++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeDCFirst(ref FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, Stream stream)
+ {
+ int t = this.DecodeHuffman(ref dcHuffmanTable, stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState;
+ component.BlockData[offset] = (short)(component.Pred += diff);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeDCSuccessive(ref FrameComponent component, int offset, Stream stream)
+ {
+ int bit = this.ReadBit(stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ component.BlockData[offset] |= (short)(bit << this.successiveState);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeACFirst(ref FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream)
+ {
+ if (this.eobrun > 0)
+ {
+ this.eobrun--;
+ return;
+ }
+
+ Span componentBlockDataSpan = component.BlockData.Span;
+ int k = this.specStart;
+ int e = this.specEnd;
+ while (k <= e)
+ {
+ short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ int s = rs & 15;
+ int r = rs >> 4;
+
+ if (s == 0)
+ {
+ if (r < 15)
+ {
+ this.eobrun = this.Receive(r, stream) + (1 << r) - 1;
+ break;
+ }
+
+ k += 16;
+ continue;
+ }
+
+ k += r;
+ byte z = QuantizationTables.DctZigZag[k];
+ componentBlockDataSpan[offset + z] = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState));
+ k++;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecodeACSuccessive(ref FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream)
+ {
+ int k = this.specStart;
+ int e = this.specEnd;
+ int r = 0;
+ Span componentBlockDataSpan = component.BlockData.Span;
+ while (k <= e)
+ {
+ byte z = QuantizationTables.DctZigZag[k];
+ switch (this.successiveACState)
+ {
+ case 0: // Initial state
+ short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ int s = rs & 15;
+ r = rs >> 4;
+ if (s == 0)
+ {
+ if (r < 15)
+ {
+ this.eobrun = this.Receive(r, stream) + (1 << r);
+ this.successiveACState = 4;
+ }
+ else
+ {
+ r = 16;
+ this.successiveACState = 1;
+ }
+ }
+ else
+ {
+ if (s != 1)
+ {
+ throw new ImageFormatException("Invalid ACn encoding");
+ }
+
+ this.successiveACNextValue = this.ReceiveAndExtend(s, stream);
+ this.successiveACState = r > 0 ? 2 : 3;
+ }
+
+ continue;
+ case 1: // Skipping r zero items
+ case 2:
+ if (componentBlockDataSpan[offset + z] != 0)
+ {
+ int bit = this.ReadBit(stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState);
+ }
+ else
+ {
+ r--;
+ if (r == 0)
+ {
+ this.successiveACState = this.successiveACState == 2 ? 3 : 0;
+ }
+ }
+
+ break;
+ case 3: // Set value for a zero item
+ if (componentBlockDataSpan[offset + z] != 0)
+ {
+ int bit = this.ReadBit(stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState);
+ }
+ else
+ {
+ componentBlockDataSpan[offset + z] = (short)(this.successiveACNextValue << this.successiveState);
+ this.successiveACState = 0;
+ }
+
+ break;
+ case 4: // Eob
+ if (componentBlockDataSpan[offset + z] != 0)
+ {
+ int bit = this.ReadBit(stream);
+ if (this.endOfStreamReached || this.unexpectedMarkerReached)
+ {
+ return;
+ }
+
+ componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState);
+ }
+
+ break;
+ }
+
+ k++;
+ }
+
+ if (this.successiveACState == 4)
+ {
+ this.eobrun--;
+ if (this.eobrun == 0)
+ {
+ this.successiveACState = 0;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs
new file mode 100644
index 000000000..02397e1d7
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs
@@ -0,0 +1,128 @@
+namespace ImageSharp.Formats.Jpeg.Port.Components
+{
+ using System.Runtime.CompilerServices;
+ using ImageSharp.PixelFormats;
+
+ ///
+ /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace.
+ /// Methods to build the tables are based on libjpeg implementation.
+ ///
+ internal struct YCbCrToRgbTables
+ {
+ ///
+ /// The red red-chrominance table
+ ///
+ public static int[] CrRTable = new int[256];
+
+ ///
+ /// The blue blue-chrominance table
+ ///
+ public static int[] CbBTable = new int[256];
+
+ ///
+ /// The green red-chrominance table
+ ///
+ public static int[] CrGTable = new int[256];
+
+ ///
+ /// The green blue-chrominance table
+ ///
+ public static int[] CbGTable = new int[256];
+
+ // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
+ private const int ScaleBits = 16;
+
+ private const int Half = 1 << (ScaleBits - 1);
+
+ private const int MinSample = 0;
+
+ private const int HalfSample = 128;
+
+ private const int MaxSample = 255;
+
+ ///
+ /// Initializes the YCbCr tables
+ ///
+ public static void Create()
+ {
+ for (int i = 0, x = -128; i <= 255; i++, x++)
+ {
+ // i is the actual input pixel value, in the range 0..255
+ // The Cb or Cr value we are thinking of is x = i - 128
+ // Cr=>R value is nearest int to 1.402 * x
+ CrRTable[i] = RightShift((Fix(1.402F) * x) + Half);
+
+ // Cb=>B value is nearest int to 1.772 * x
+ CbBTable[i] = RightShift((Fix(1.772F) * x) + Half);
+
+ // Cr=>G value is scaled-up -0.714136286
+ CrGTable[i] = (-Fix(0.714136286F)) * x;
+
+ // Cb => G value is scaled - up - 0.344136286 * x
+ // We also add in Half so that need not do it in inner loop
+ CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half;
+ }
+ }
+
+ ///
+ /// Optimized method to pack bytes to the image from the YCbCr color space.
+ ///
+ /// The pixel format.
+ /// The packed pixel.
+ /// The y luminance component.
+ /// The cb chroma component.
+ /// The cr chroma component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void PackYCbCr(ref TPixel packed, byte y, byte cb, byte cr)
+ where TPixel : struct, IPixel
+ {
+ byte r = (byte)(y + CrRTable[cr]).Clamp(0, 255);
+
+ // The values for the G calculation are left scaled up, since we must add them together before rounding.
+ byte g = (byte)(y + RightShift(CbGTable[cb] + CrGTable[cr])).Clamp(0, 255);
+
+ byte b = (byte)(y + CbBTable[cb]).Clamp(0, 255);
+
+ packed.PackFromRgba32(new Rgba32(r, g, b, 255));
+ }
+
+ ///
+ /// Optimized method to pack bytes to the image from the YccK color space.
+ ///
+ /// The pixel format.
+ /// The packed pixel.
+ /// The y luminance component.
+ /// The cb chroma component.
+ /// The cr chroma component.
+ /// The keyline component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void PackYccK(ref TPixel packed, byte y, byte cb, byte cr, byte k)
+ where TPixel : struct, IPixel
+ {
+ int c = (MaxSample - (y + CrRTable[cr])).Clamp(0, 255);
+
+ // The values for the G calculation are left scaled up, since we must add them together before rounding.
+ int m = (MaxSample - (y + RightShift(CbGTable[cb] + CrGTable[cr]))).Clamp(0, 255);
+
+ int cy = (MaxSample - (y + CbBTable[cb])).Clamp(0, 255);
+
+ byte r = (byte)((c * k) / MaxSample);
+ byte g = (byte)((m * k) / MaxSample);
+ byte b = (byte)((cy * k) / MaxSample);
+
+ packed.PackFromRgba32(new Rgba32(r, g, b, MaxSample));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Fix(float x)
+ {
+ return (int)((x * (1L << ScaleBits)) + 0.5F);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int RightShift(int x)
+ {
+ return x >> ScaleBits;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs
new file mode 100644
index 000000000..a02e05591
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs
@@ -0,0 +1,359 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.Formats.Jpeg.Port
+{
+ ///
+ /// Contains jpeg constant values
+ ///
+ internal static class JpegConstants
+ {
+ ///
+ /// Contains marker specific constants
+ ///
+ public static class Markers
+ {
+ ///
+ /// The prefix used for all markers.
+ ///
+ public const byte Prefix = 0xFF;
+
+ ///
+ /// The Start of Image marker
+ ///
+ public const ushort SOI = 0xFFD8;
+
+ ///
+ /// The End of Image marker
+ ///
+ public const ushort EOI = 0xFFD9;
+
+ ///
+ /// Application specific marker for marking the jpeg format.
+ ///
+ ///
+ public const ushort APP0 = 0xFFE0;
+
+ ///
+ /// Application specific marker for marking where to store metadata.
+ ///
+ public const ushort APP1 = 0xFFE1;
+
+ ///
+ /// Application specific marker for marking where to store ICC profile information.
+ ///
+ public const ushort APP2 = 0xFFE2;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP3 = 0xFFE3;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP4 = 0xFFE4;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP5 = 0xFFE5;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP6 = 0xFFE6;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP7 = 0xFFE7;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP8 = 0xFFE8;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP9 = 0xFFE9;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP10 = 0xFFEA;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP11 = 0xFFEB;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP12 = 0xFFEC;
+
+ ///
+ /// Application specific marker.
+ ///
+ public const ushort APP13 = 0xFFED;
+
+ ///
+ /// Application specific marker used by Adobe for storing encoding information for DCT filters.
+ ///
+ public const ushort APP14 = 0xFFEE;
+
+ ///
+ /// Application specific marker used by GraphicConverter to store JPEG quality.
+ ///
+ public const ushort APP15 = 0xFFEF;
+
+ ///
+ /// The text comment marker
+ ///
+ public const ushort COM = 0xFFFE;
+
+ ///
+ /// Define Quantization Table(s) marker
+ ///
+ /// Specifies one or more quantization tables.
+ ///
+ ///
+ public const ushort DQT = 0xFFDB;
+
+ ///
+ /// Start of Frame (baseline DCT)
+ ///
+ /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components,
+ /// and component subsampling (e.g., 4:2:0).
+ ///
+ ///
+ public const ushort SOF0 = 0xFFC0;
+
+ ///
+ /// Start Of Frame (Extended Sequential DCT)
+ ///
+ /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
+ /// and component subsampling (e.g., 4:2:0).
+ ///
+ ///
+ public const ushort SOF1 = 0xFFC1;
+
+ ///
+ /// Start Of Frame (progressive DCT)
+ ///
+ /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
+ /// and component subsampling (e.g., 4:2:0).
+ ///
+ ///
+ public const ushort SOF2 = 0xFFC2;
+
+ ///
+ /// Define Huffman Table(s)
+ ///
+ /// Specifies one or more Huffman tables.
+ ///
+ ///
+ public const ushort DHT = 0xFFC4;
+
+ ///
+ /// Define Restart Interval
+ ///
+ /// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.
+ ///
+ ///
+ public const ushort DRI = 0xFFDD;
+
+ ///
+ /// Start of Scan
+ ///
+ /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan.
+ /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it
+ /// will contain, and is immediately followed by entropy-coded data.
+ ///
+ ///
+ public const ushort SOS = 0xFFDA;
+
+ ///
+ /// Define First Restart
+ ///
+ /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
+ /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
+ ///
+ ///
+ public const ushort RST0 = 0xFFD0;
+
+ ///
+ /// Define Eigth Restart
+ ///
+ /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
+ /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
+ ///
+ ///
+ public const ushort RST7 = 0xFFD7;
+
+ ///
+ /// Contains JFIF specific markers
+ ///
+ public static class JFif
+ {
+ ///
+ /// Represents J in ASCII
+ ///
+ public const byte J = 0x4A;
+
+ ///
+ /// Represents F in ASCII
+ ///
+ public const byte F = 0x46;
+
+ ///
+ /// Represents I in ASCII
+ ///
+ public const byte I = 0x49;
+
+ ///
+ /// Represents the null "0" marker
+ ///
+ public const byte Null = 0x0;
+ }
+
+ ///
+ /// Contains Adobe specific markers
+ ///
+ public static class Adobe
+ {
+ ///
+ /// Represents A in ASCII
+ ///
+ public const byte A = 0x41;
+
+ ///
+ /// Represents d in ASCII
+ ///
+ public const byte D = 0x64;
+
+ ///
+ /// Represents b in ASCII
+ ///
+ public const byte O = 0x6F;
+
+ ///
+ /// Represents b in ASCII
+ ///
+ public const byte B = 0x62;
+
+ ///
+ /// Represents e in ASCII
+ ///
+ public const byte E = 0x65;
+
+ ///
+ /// The color transform is unknown.(RGB or CMYK)
+ ///
+ public const byte ColorTransformUnknown = 0;
+
+ ///
+ /// The color transform is YCbCr (luminance, red chroma, blue chroma)
+ ///
+ public const byte ColorTransformYCbCr = 1;
+
+ ///
+ /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline)
+ ///
+ public const byte ColorTransformYcck = 2;
+ }
+
+ ///
+ /// Contains EXIF specific markers
+ ///
+ public static class Exif
+ {
+ ///
+ /// Represents E in ASCII
+ ///
+ public const byte E = 0x45;
+
+ ///
+ /// Represents x in ASCII
+ ///
+ public const byte X = 0x78;
+
+ ///
+ /// Represents i in ASCII
+ ///
+ public const byte I = 0x69;
+
+ ///
+ /// Represents f in ASCII
+ ///
+ public const byte F = 0x66;
+
+ ///
+ /// Represents the null "0" marker
+ ///
+ public const byte Null = 0x0;
+ }
+
+ ///
+ /// Contains ICC specific markers
+ ///
+ public static class ICC
+ {
+ ///
+ /// Represents I in ASCII
+ ///
+ public const byte I = 0x49;
+
+ ///
+ /// Represents C in ASCII
+ ///
+ public const byte C = 0x43;
+
+ ///
+ /// Represents _ in ASCII
+ ///
+ public const byte UnderScore = 0x5F;
+
+ ///
+ /// Represents P in ASCII
+ ///
+ public const byte P = 0x50;
+
+ ///
+ /// Represents R in ASCII
+ ///
+ public const byte R = 0x52;
+
+ ///
+ /// Represents O in ASCII
+ ///
+ public const byte O = 0x4F;
+
+ ///
+ /// Represents F in ASCII
+ ///
+ public const byte F = 0x46;
+
+ ///
+ /// Represents L in ASCII
+ ///
+ public const byte L = 0x4C;
+
+ ///
+ /// Represents E in ASCII
+ ///
+ public const byte E = 0x45;
+
+ ///
+ /// Represents the null "0" marker
+ ///
+ public const byte Null = 0x0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs
new file mode 100644
index 000000000..6b499a5fd
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs
@@ -0,0 +1,966 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpeg.Port
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Runtime.CompilerServices;
+ using ImageSharp.Formats.Jpeg.Port.Components;
+ using ImageSharp.Memory;
+ using ImageSharp.PixelFormats;
+
+ ///
+ /// Performs the jpeg decoding operation.
+ /// Ported from with additional fixes to handle common encoding errors
+ ///
+ internal sealed class JpegDecoderCore : IDisposable
+ {
+ ///
+ /// The global configuration
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Gets the temporary buffer used to store bytes read from the stream.
+ ///
+ private readonly byte[] temp = new byte[2 * 16 * 4];
+
+ private readonly byte[] markerBuffer = new byte[2];
+
+ private QuantizationTables quantizationTables;
+
+ private HuffmanTables dcHuffmanTables;
+
+ private HuffmanTables acHuffmanTables;
+
+ private Frame frame;
+
+ private ComponentBlocks components;
+
+ private JpegPixelArea pixelArea;
+
+ private ushort resetInterval;
+
+ private int imageWidth;
+
+ private int imageHeight;
+
+ private int numberOfComponents;
+
+ ///
+ /// Whether the image has a EXIF header
+ ///
+ private bool isExif;
+
+ ///
+ /// Contains information about the JFIF marker
+ ///
+ private JFif jFif;
+
+ ///
+ /// Contains information about the Adobe marker
+ ///
+ private Adobe adobe;
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static JpegDecoderCore()
+ {
+ YCbCrToRgbTables.Create();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The options.
+ public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
+ {
+ this.configuration = configuration ?? Configuration.Default;
+ this.IgnoreMetadata = options.IgnoreMetadata;
+ }
+
+ ///
+ /// Gets the input stream.
+ ///
+ public Stream InputStream { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ public bool IgnoreMetadata { get; }
+
+ ///
+ /// Finds the next file marker within the byte stream.
+ ///
+ /// The buffer to read file markers to
+ /// The input stream
+ /// The
+ public static FileMarker FindNextFileMarker(byte[] marker, Stream stream)
+ {
+ int value = stream.Read(marker, 0, 2);
+
+ if (value == 0)
+ {
+ return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2);
+ }
+
+ if (marker[0] == JpegConstants.Markers.Prefix)
+ {
+ // According to Section B.1.1.2:
+ // "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF."
+ while (marker[1] == JpegConstants.Markers.Prefix)
+ {
+ int suffix = stream.ReadByte();
+ if (suffix == -1)
+ {
+ return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2);
+ }
+
+ marker[1] = (byte)value;
+ }
+
+ return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2));
+ }
+
+ return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2), true);
+ }
+
+ ///
+ /// Decodes the image from the specified and sets the data to image.
+ ///
+ /// The pixel format.
+ /// The stream, where the image should be.
+ /// The decoded image.
+ public Image Decode(Stream stream)
+ where TPixel : struct, IPixel
+ {
+ this.InputStream = stream;
+
+ var metadata = new ImageMetaData();
+ this.ParseStream(metadata, false);
+
+ var image = new Image(this.configuration, this.imageWidth, this.imageHeight, metadata);
+ this.FillPixelData(image);
+ this.AssignResolution(image);
+ return image;
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.frame?.Dispose();
+ this.components?.Dispose();
+ this.quantizationTables?.Dispose();
+ this.dcHuffmanTables?.Dispose();
+ this.acHuffmanTables?.Dispose();
+ this.pixelArea.Dispose();
+
+ // Set large fields to null.
+ this.frame = null;
+ this.components = null;
+ this.quantizationTables = null;
+ this.dcHuffmanTables = null;
+ this.acHuffmanTables = null;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int GetBlockBufferOffset(ref Component component, int row, int col)
+ {
+ return 64 * (((component.BlocksPerLine + 1) * row) + col);
+ }
+
+ ///
+ /// Parses the input stream for file markers
+ ///
+ /// Contains the metadata for an image
+ /// Whether to decode metadata only.
+ private void ParseStream(ImageMetaData metaData, bool metadataOnly)
+ {
+ // TODO: metadata only logic
+ // Check for the Start Of Image marker.
+ var fileMarker = new FileMarker(this.ReadUint16(), 0);
+ if (fileMarker.Marker != JpegConstants.Markers.SOI)
+ {
+ throw new ImageFormatException("Missing SOI marker.");
+ }
+
+ ushort marker = this.ReadUint16();
+ fileMarker = new FileMarker(marker, (int)this.InputStream.Position - 2);
+
+ this.quantizationTables = new QuantizationTables();
+ this.dcHuffmanTables = new HuffmanTables();
+ this.acHuffmanTables = new HuffmanTables();
+
+ while (fileMarker.Marker != JpegConstants.Markers.EOI)
+ {
+ // Get the marker length
+ int remaining = this.ReadUint16() - 2;
+
+ switch (fileMarker.Marker)
+ {
+ case JpegConstants.Markers.APP0:
+ this.ProcessApplicationHeaderMarker(remaining);
+ break;
+
+ case JpegConstants.Markers.APP1:
+ this.ProcessApp1Marker(remaining, metaData);
+ break;
+
+ case JpegConstants.Markers.APP2:
+ this.ProcessApp2Marker(remaining, metaData);
+ break;
+ case JpegConstants.Markers.APP3:
+ case JpegConstants.Markers.APP4:
+ case JpegConstants.Markers.APP5:
+ case JpegConstants.Markers.APP6:
+ case JpegConstants.Markers.APP7:
+ case JpegConstants.Markers.APP8:
+ case JpegConstants.Markers.APP9:
+ case JpegConstants.Markers.APP10:
+ case JpegConstants.Markers.APP11:
+ case JpegConstants.Markers.APP12:
+ case JpegConstants.Markers.APP13:
+ this.InputStream.Skip(remaining);
+ break;
+
+ case JpegConstants.Markers.APP14:
+ this.ProcessApp14Marker(remaining);
+ break;
+
+ case JpegConstants.Markers.APP15:
+ case JpegConstants.Markers.COM:
+ this.InputStream.Skip(remaining);
+ break;
+
+ case JpegConstants.Markers.DQT:
+ this.ProcessDefineQuantizationTablesMarker(remaining);
+ break;
+
+ case JpegConstants.Markers.SOF0:
+ case JpegConstants.Markers.SOF1:
+ case JpegConstants.Markers.SOF2:
+ this.ProcessStartOfFrameMarker(remaining, fileMarker);
+ break;
+
+ case JpegConstants.Markers.DHT:
+ this.ProcessDefineHuffmanTablesMarker(remaining);
+ break;
+
+ case JpegConstants.Markers.DRI:
+ this.ProcessDefineRestartIntervalMarker(remaining);
+ break;
+
+ case JpegConstants.Markers.SOS:
+ this.ProcessStartOfScanMarker();
+ break;
+ }
+
+ // Read on.
+ fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream);
+ }
+
+ this.imageWidth = this.frame.SamplesPerLine;
+ this.imageHeight = this.frame.Scanlines;
+ this.components = new ComponentBlocks { Components = new Component[this.frame.ComponentCount] };
+
+ for (int i = 0; i < this.components.Components.Length; i++)
+ {
+ ref var frameComponent = ref this.frame.Components[i];
+ var component = new Component
+ {
+ Scale = new System.Numerics.Vector2(
+ frameComponent.HorizontalFactor / (float)this.frame.MaxHorizontalFactor,
+ frameComponent.VerticalFactor / (float)this.frame.MaxVerticalFactor),
+ BlocksPerLine = frameComponent.BlocksPerLine,
+ BlocksPerColumn = frameComponent.BlocksPerColumn
+ };
+
+ this.BuildComponentData(ref component, ref frameComponent);
+ this.components.Components[i] = component;
+ }
+
+ this.numberOfComponents = this.components.Components.Length;
+ }
+
+ ///
+ /// Fills the given image with the color data
+ ///
+ /// The pixel format.
+ /// The image
+ private void FillPixelData(Image image)
+ where TPixel : struct, IPixel
+ {
+ if (this.numberOfComponents > 4)
+ {
+ throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.numberOfComponents}");
+ }
+
+ this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.numberOfComponents);
+ this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height);
+
+ if (this.numberOfComponents == 1)
+ {
+ this.FillGrayScaleImage(image);
+ return;
+ }
+
+ if (this.numberOfComponents == 3)
+ {
+ if (this.adobe.Equals(default(Adobe)) || this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYCbCr)
+ {
+ this.FillYCbCrImage(image);
+ }
+ else if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformUnknown)
+ {
+ this.FillRgbImage(image);
+ }
+ }
+
+ if (this.numberOfComponents == 4)
+ {
+ if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYcck)
+ {
+ this.FillYcckImage(image);
+ }
+ else
+ {
+ this.FillCmykImage(image);
+ }
+ }
+ }
+
+ ///
+ /// Assigns the horizontal and vertical resolution to the image if it has a JFIF header or EXIF metadata.
+ ///
+ /// The pixel format.
+ /// The image to assign the resolution to.
+ private void AssignResolution(Image image)
+ where TPixel : struct, IPixel
+ {
+ if (this.isExif)
+ {
+ ExifValue horizontal = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution);
+ ExifValue vertical = image.MetaData.ExifProfile.GetValue(ExifTag.YResolution);
+ double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0;
+ double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0;
+
+ if (horizontalValue > 0 && verticalValue > 0)
+ {
+ image.MetaData.HorizontalResolution = horizontalValue;
+ image.MetaData.VerticalResolution = verticalValue;
+ }
+ }
+ else if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0)
+ {
+ image.MetaData.HorizontalResolution = this.jFif.XDensity;
+ image.MetaData.VerticalResolution = this.jFif.YDensity;
+ }
+ }
+
+ ///
+ /// Processes the application header containing the JFIF identifier plus extra data.
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessApplicationHeaderMarker(int remaining)
+ {
+ if (remaining < 5)
+ {
+ // Skip the application header length
+ this.InputStream.Skip(remaining);
+ return;
+ }
+
+ this.InputStream.Read(this.temp, 0, 13);
+ remaining -= 13;
+
+ bool isJfif = this.temp[0] == JpegConstants.Markers.JFif.J &&
+ this.temp[1] == JpegConstants.Markers.JFif.F &&
+ this.temp[2] == JpegConstants.Markers.JFif.I &&
+ this.temp[3] == JpegConstants.Markers.JFif.F &&
+ this.temp[4] == JpegConstants.Markers.JFif.Null;
+
+ if (isJfif)
+ {
+ this.jFif = new JFif
+ {
+ MajorVersion = this.temp[5],
+ MinorVersion = this.temp[6],
+ DensityUnits = this.temp[7],
+ XDensity = (short)((this.temp[8] << 8) | this.temp[9]),
+ YDensity = (short)((this.temp[10] << 8) | this.temp[11])
+ };
+ }
+
+ // TODO: thumbnail
+ if (remaining > 0)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ }
+
+ ///
+ /// Processes the App1 marker retrieving any stored metadata
+ ///
+ /// The remaining bytes in the segment block.
+ /// The image.
+ private void ProcessApp1Marker(int remaining, ImageMetaData metadata)
+ {
+ if (remaining < 6 || this.IgnoreMetadata)
+ {
+ // Skip the application header length
+ this.InputStream.Skip(remaining);
+ return;
+ }
+
+ byte[] profile = new byte[remaining];
+ this.InputStream.Read(profile, 0, remaining);
+
+ if (profile[0] == JpegConstants.Markers.Exif.E &&
+ profile[1] == JpegConstants.Markers.Exif.X &&
+ profile[2] == JpegConstants.Markers.Exif.I &&
+ profile[3] == JpegConstants.Markers.Exif.F &&
+ profile[4] == JpegConstants.Markers.Exif.Null &&
+ profile[5] == JpegConstants.Markers.Exif.Null)
+ {
+ this.isExif = true;
+ metadata.ExifProfile = new ExifProfile(profile);
+ }
+ }
+
+ ///
+ /// Processes the App2 marker retrieving any stored ICC profile information
+ ///
+ /// The remaining bytes in the segment block.
+ /// The image.
+ private void ProcessApp2Marker(int remaining, ImageMetaData metadata)
+ {
+ // Length is 14 though we only need to check 12.
+ const int Icclength = 14;
+ if (remaining < Icclength || this.IgnoreMetadata)
+ {
+ this.InputStream.Skip(remaining);
+ return;
+ }
+
+ byte[] identifier = new byte[Icclength];
+ this.InputStream.Read(identifier, 0, Icclength);
+ remaining -= Icclength; // We have read it by this point
+
+ if (identifier[0] == JpegConstants.Markers.ICC.I &&
+ identifier[1] == JpegConstants.Markers.ICC.C &&
+ identifier[2] == JpegConstants.Markers.ICC.C &&
+ identifier[3] == JpegConstants.Markers.ICC.UnderScore &&
+ identifier[4] == JpegConstants.Markers.ICC.P &&
+ identifier[5] == JpegConstants.Markers.ICC.R &&
+ identifier[6] == JpegConstants.Markers.ICC.O &&
+ identifier[7] == JpegConstants.Markers.ICC.F &&
+ identifier[8] == JpegConstants.Markers.ICC.I &&
+ identifier[9] == JpegConstants.Markers.ICC.L &&
+ identifier[10] == JpegConstants.Markers.ICC.E &&
+ identifier[11] == JpegConstants.Markers.ICC.Null)
+ {
+ byte[] profile = new byte[remaining];
+ this.InputStream.Read(profile, 0, remaining);
+
+ if (metadata.IccProfile == null)
+ {
+ metadata.IccProfile = new IccProfile(profile);
+ }
+ else
+ {
+ metadata.IccProfile.Extend(profile);
+ }
+ }
+ else
+ {
+ // Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this.
+ this.InputStream.Skip(remaining);
+ }
+ }
+
+ ///
+ /// Processes the application header containing the Adobe identifier
+ /// which stores image encoding information for DCT filters.
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessApp14Marker(int remaining)
+ {
+ if (remaining < 12)
+ {
+ // Skip the application header length
+ this.InputStream.Skip(remaining);
+ return;
+ }
+
+ this.InputStream.Read(this.temp, 0, 12);
+ remaining -= 12;
+
+ bool isAdobe = this.temp[0] == JpegConstants.Markers.Adobe.A &&
+ this.temp[1] == JpegConstants.Markers.Adobe.D &&
+ this.temp[2] == JpegConstants.Markers.Adobe.O &&
+ this.temp[3] == JpegConstants.Markers.Adobe.B &&
+ this.temp[4] == JpegConstants.Markers.Adobe.E;
+
+ if (isAdobe)
+ {
+ this.adobe = new Adobe
+ {
+ DCTEncodeVersion = (short)((this.temp[5] << 8) | this.temp[6]),
+ APP14Flags0 = (short)((this.temp[7] << 8) | this.temp[8]),
+ APP14Flags1 = (short)((this.temp[9] << 8) | this.temp[10]),
+ ColorTransform = this.temp[11]
+ };
+ }
+
+ if (remaining > 0)
+ {
+ this.InputStream.Skip(remaining);
+ }
+ }
+
+ ///
+ /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
+ ///
+ /// The remaining bytes in the segment block.
+ ///
+ /// Thrown if the tables do not match the header
+ ///
+ private void ProcessDefineQuantizationTablesMarker(int remaining)
+ {
+ while (remaining > 0)
+ {
+ bool done = false;
+ remaining--;
+ int quantizationTableSpec = this.InputStream.ReadByte();
+
+ switch (quantizationTableSpec >> 4)
+ {
+ case 0:
+ {
+ // 8 bit values
+ if (remaining < 64)
+ {
+ done = true;
+ break;
+ }
+
+ this.InputStream.Read(this.temp, 0, 64);
+ remaining -= 64;
+
+ Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15);
+ for (int j = 0; j < 64; j++)
+ {
+ tableSpan[QuantizationTables.DctZigZag[j]] = this.temp[j];
+ }
+ }
+
+ break;
+ case 1:
+ {
+ // 16 bit values
+ if (remaining < 128)
+ {
+ done = true;
+ break;
+ }
+
+ this.InputStream.Read(this.temp, 0, 128);
+ remaining -= 128;
+
+ Span tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15);
+ for (int j = 0; j < 64; j++)
+ {
+ tableSpan[QuantizationTables.DctZigZag[j]] = (short)((this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]);
+ }
+ }
+
+ break;
+ default:
+ throw new ImageFormatException("Bad Tq index value");
+ }
+
+ if (done)
+ {
+ break;
+ }
+ }
+
+ if (remaining != 0)
+ {
+ throw new ImageFormatException("DQT has wrong length");
+ }
+ }
+
+ ///
+ /// Processes the Start of Frame marker. Specified in section B.2.2.
+ ///
+ /// The remaining bytes in the segment block.
+ /// The current frame marker.
+ private void ProcessStartOfFrameMarker(int remaining, FileMarker frameMarker)
+ {
+ if (this.frame != null)
+ {
+ throw new ImageFormatException("Multiple SOF markers. Only single frame jpegs supported.");
+ }
+
+ this.InputStream.Read(this.temp, 0, remaining);
+
+ this.frame = new Frame
+ {
+ Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
+ Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
+ Precision = this.temp[0],
+ Scanlines = (short)((this.temp[1] << 8) | this.temp[2]),
+ SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]),
+ ComponentCount = this.temp[5]
+ };
+
+ int maxH = 0;
+ int maxV = 0;
+ int index = 6;
+
+ // No need to pool this. They max out at 4
+ this.frame.ComponentIds = new byte[this.frame.ComponentCount];
+ this.frame.Components = new FrameComponent[this.frame.ComponentCount];
+
+ for (int i = 0; i < this.frame.Components.Length; i++)
+ {
+ int h = this.temp[index + 1] >> 4;
+ int v = this.temp[index + 1] & 15;
+
+ if (maxH < h)
+ {
+ maxH = h;
+ }
+
+ if (maxV < v)
+ {
+ maxV = v;
+ }
+
+ ref var component = ref this.frame.Components[i];
+ component.Id = this.temp[index];
+ component.HorizontalFactor = h;
+ component.VerticalFactor = v;
+ component.QuantizationIdentifier = this.temp[index + 2];
+
+ this.frame.ComponentIds[i] = component.Id;
+
+ index += 3;
+ }
+
+ this.frame.MaxHorizontalFactor = maxH;
+ this.frame.MaxVerticalFactor = maxV;
+ this.PrepareComponents();
+ }
+
+ ///
+ /// Processes a Define Huffman Table marker, and initializes a huffman
+ /// struct from its contents. Specified in section B.2.4.2.
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessDefineHuffmanTablesMarker(int remaining)
+ {
+ if (remaining < 17)
+ {
+ throw new ImageFormatException($"DHT has wrong length: {remaining}");
+ }
+
+ using (var huffmanData = Buffer.CreateClean(256))
+ {
+ for (int i = 2; i < remaining;)
+ {
+ byte huffmanTableSpec = (byte)this.InputStream.ReadByte();
+ this.InputStream.Read(huffmanData.Array, 0, 16);
+
+ using (var codeLengths = Buffer.CreateClean(17))
+ {
+ int codeLengthSum = 0;
+
+ for (int j = 1; j < 17; j++)
+ {
+ codeLengthSum += codeLengths[j] = huffmanData[j - 1];
+ }
+
+ using (var huffmanValues = Buffer.CreateClean(256))
+ {
+ this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum);
+
+ i += 17 + codeLengthSum;
+
+ this.BuildHuffmanTable(
+ huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables,
+ huffmanTableSpec & 15,
+ codeLengths.Array,
+ huffmanValues.Array);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
+ /// macroblocks
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessDefineRestartIntervalMarker(int remaining)
+ {
+ if (remaining != 2)
+ {
+ throw new ImageFormatException($"DRI has wrong length: {remaining}");
+ }
+
+ this.resetInterval = this.ReadUint16();
+ }
+
+ ///
+ /// Processes the SOS (Start of scan marker).
+ ///
+ private void ProcessStartOfScanMarker()
+ {
+ int selectorsCount = this.InputStream.ReadByte();
+ int componentIndex = -1;
+ for (int i = 0; i < selectorsCount; i++)
+ {
+ componentIndex = -1;
+ int selector = this.InputStream.ReadByte();
+
+ for (int j = 0; j < this.frame.ComponentIds.Length; j++)
+ {
+ byte id = this.frame.ComponentIds[j];
+ if (selector == id)
+ {
+ componentIndex = j;
+ }
+ }
+
+ if (componentIndex < 0)
+ {
+ throw new ImageFormatException("Unknown component selector");
+ }
+
+ ref FrameComponent component = ref this.frame.Components[componentIndex];
+ int tableSpec = this.InputStream.ReadByte();
+ component.DCHuffmanTableId = tableSpec >> 4;
+ component.ACHuffmanTableId = tableSpec & 15;
+ }
+
+ this.InputStream.Read(this.temp, 0, 3);
+
+ int spectralStart = this.temp[0];
+ int spectralEnd = this.temp[1];
+ int successiveApproximation = this.temp[2];
+ var scanDecoder = default(ScanDecoder);
+
+ scanDecoder.DecodeScan(
+ this.frame,
+ this.InputStream,
+ this.dcHuffmanTables,
+ this.acHuffmanTables,
+ this.frame.Components,
+ componentIndex,
+ selectorsCount,
+ this.resetInterval,
+ spectralStart,
+ spectralEnd,
+ successiveApproximation >> 4,
+ successiveApproximation & 15);
+ }
+
+ ///
+ /// Build the data for the given component
+ ///
+ /// The component
+ /// The frame component
+ private void BuildComponentData(ref Component component, ref FrameComponent frameComponent)
+ {
+ int blocksPerLine = component.BlocksPerLine;
+ int blocksPerColumn = component.BlocksPerColumn;
+ using (var computationBuffer = Buffer.CreateClean(64))
+ using (var multiplicationBuffer = Buffer.CreateClean(64))
+ {
+ Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationIdentifier);
+ Span computationBufferSpan = computationBuffer;
+
+ // For AA&N IDCT method, multiplier are equal to quantization
+ // coefficients scaled by scalefactor[row]*scalefactor[col], where
+ // scalefactor[0] = 1
+ // scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
+ // For integer operation, the multiplier table is to be scaled by 12.
+ Span multiplierSpan = multiplicationBuffer;
+ for (int i = 0; i < 64; i++)
+ {
+ multiplierSpan[i] = (short)IDCT.Descale(quantizationTable[i] * IDCT.Aanscales[i], 12);
+ }
+
+ for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++)
+ {
+ for (int blockCol = 0; blockCol < blocksPerLine; blockCol++)
+ {
+ int offset = GetBlockBufferOffset(ref component, blockRow, blockCol);
+ IDCT.QuantizeAndInverseFast(ref frameComponent, offset, ref computationBufferSpan, ref multiplierSpan);
+ }
+ }
+ }
+
+ component.Output = frameComponent.BlockData;
+ }
+
+ ///
+ /// Builds the huffman tables
+ ///
+ /// The tables
+ /// The table index
+ /// The codelengths
+ /// The values
+ private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, byte[] values)
+ {
+ tables[index] = new HuffmanTable(codeLengths, values);
+ }
+
+ ///
+ /// Allocates the frame component blocks
+ ///
+ private void PrepareComponents()
+ {
+ int mcusPerLine = (int)MathF.Ceiling(this.frame.SamplesPerLine / 8F / this.frame.MaxHorizontalFactor);
+ int mcusPerColumn = (int)MathF.Ceiling(this.frame.Scanlines / 8F / this.frame.MaxVerticalFactor);
+
+ for (int i = 0; i < this.frame.ComponentCount; i++)
+ {
+ ref var component = ref this.frame.Components[i];
+ int blocksPerLine = (int)MathF.Ceiling(MathF.Ceiling(this.frame.SamplesPerLine / 8F) * component.HorizontalFactor / this.frame.MaxHorizontalFactor);
+ int blocksPerColumn = (int)MathF.Ceiling(MathF.Ceiling(this.frame.Scanlines / 8F) * component.VerticalFactor / this.frame.MaxVerticalFactor);
+ int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor;
+ int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor;
+
+ int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
+
+ // Pooled. Disposed via frame disposal
+ component.BlockData = Buffer.CreateClean(blocksBufferSize);
+ component.BlocksPerLine = blocksPerLine;
+ component.BlocksPerColumn = blocksPerColumn;
+ }
+
+ this.frame.McusPerLine = mcusPerLine;
+ this.frame.McusPerColumn = mcusPerColumn;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void FillGrayScaleImage(Image image)
+ where TPixel : struct, IPixel
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span imageRowSpan = image.GetRowSpan(y);
+ Span areaRowSpan = this.pixelArea.GetRowSpan(y);
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ ref byte luminance = ref areaRowSpan[x];
+ ref TPixel pixel = ref imageRowSpan[x];
+ var rgba = new Rgba32(luminance, luminance, luminance);
+ pixel.PackFromRgba32(rgba);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void FillYCbCrImage(Image image)
+ where TPixel : struct, IPixel
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span imageRowSpan = image.GetRowSpan(y);
+ Span areaRowSpan = this.pixelArea.GetRowSpan(y);
+ for (int x = 0, o = 0; x < image.Width; x++, o += 3)
+ {
+ ref byte yy = ref areaRowSpan[o];
+ ref byte cb = ref areaRowSpan[o + 1];
+ ref byte cr = ref areaRowSpan[o + 2];
+ ref TPixel pixel = ref imageRowSpan[x];
+ YCbCrToRgbTables.PackYCbCr(ref pixel, yy, cb, cr);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void FillYcckImage(Image image)
+ where TPixel : struct, IPixel
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span imageRowSpan = image.GetRowSpan(y);
+ Span areaRowSpan = this.pixelArea.GetRowSpan(y);
+ for (int x = 0, o = 0; x < image.Width; x++, o += 4)
+ {
+ ref byte yy = ref areaRowSpan[o];
+ ref byte cb = ref areaRowSpan[o + 1];
+ ref byte cr = ref areaRowSpan[o + 2];
+ ref byte k = ref areaRowSpan[o + 3];
+
+ ref TPixel pixel = ref imageRowSpan[x];
+ YCbCrToRgbTables.PackYccK(ref pixel, yy, cb, cr, k);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void FillCmykImage(Image image)
+ where TPixel : struct, IPixel
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span imageRowSpan = image.GetRowSpan(y);
+ Span areaRowSpan = this.pixelArea.GetRowSpan(y);
+ for (int x = 0, o = 0; x < image.Width; x++, o += 4)
+ {
+ ref byte c = ref areaRowSpan[o];
+ ref byte m = ref areaRowSpan[o + 1];
+ ref byte cy = ref areaRowSpan[o + 2];
+ ref byte k = ref areaRowSpan[o + 3];
+
+ byte r = (byte)((c * k) / 255);
+ byte g = (byte)((m * k) / 255);
+ byte b = (byte)((cy * k) / 255);
+
+ ref TPixel pixel = ref imageRowSpan[x];
+ var rgba = new Rgba32(r, g, b);
+ pixel.PackFromRgba32(rgba);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void FillRgbImage(Image image)
+ where TPixel : struct, IPixel
+ {
+ for (int y = 0; y < image.Height; y++)
+ {
+ Span imageRowSpan = image.GetRowSpan(y);
+ Span areaRowSpan = this.pixelArea.GetRowSpan(y);
+
+ PixelOperations.Instance.PackFromRgb24Bytes(areaRowSpan, imageRowSpan, image.Width);
+ }
+ }
+
+ ///
+ /// Reads a from the stream advancing it by two bytes
+ ///
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ushort ReadUint16()
+ {
+ this.InputStream.Read(this.markerBuffer, 0, 2);
+ return (ushort)((this.markerBuffer[0] << 8) | this.markerBuffer[1]);
+ }
+ }
+}
\ No newline at end of file