diff --git a/ImageSharp.sln b/ImageSharp.sln
index a584c5686..aedfcc345 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -49,9 +49,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}"
EndProject
Global
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
diff --git a/src/ImageSharp/Common/Extensions/ListExtensions.cs b/src/ImageSharp/Common/Extensions/ListExtensions.cs
new file mode 100644
index 000000000..752f7ef21
--- /dev/null
+++ b/src/ImageSharp/Common/Extensions/ListExtensions.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Common.Extensions
+{
+ using System.Collections.Generic;
+
+ ///
+ /// Encapsulates a series of time saving extension methods to the class.
+ ///
+ internal static class ListExtensions
+ {
+ ///
+ /// Inserts an item at the given index automatically expanding the capacity if required.
+ ///
+ /// The type of object within the list
+ /// The list
+ /// The index
+ /// The item to insert
+ public static void SafeInsert(this List list, int index, T item)
+ {
+ if (index >= list.Count)
+ {
+ list.Add(item);
+ }
+ else
+ {
+ list[index] = item;
+ }
+ }
+
+ ///
+ /// Removes the last element from a list and returns that element. This method changes the length of the list.
+ ///
+ /// The type of object within the list
+ /// The list
+ /// The last element in the specified sequence.
+ public static T Pop(this List list)
+ {
+ int last = list.Count - 1;
+ T item = list[last];
+ list.RemoveAt(last);
+ return item;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
index 6de94dd22..6b474b92e 100644
--- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
@@ -27,7 +27,7 @@ namespace ImageSharp
if (stream.CanSeek)
{
- stream.Position += count;
+ stream.Seek(count, SeekOrigin.Current); // Position += count;
}
else
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
index b3caddeca..38160c148 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
@@ -5,8 +5,6 @@
namespace ImageSharp.Formats
{
- using System;
- using System.Collections.Generic;
using System.IO;
using ImageSharp.PixelFormats;
@@ -27,10 +25,11 @@ namespace ImageSharp.Formats
{
Guard.NotNull(stream, "stream");
- using (JpegDecoderCore decoder = new JpegDecoderCore(configuration, this))
+ // using (var decoder = new JpegDecoderCore(configuration, this))
+ using (var decoder = new Jpeg.Port.JpegDecoderCore(configuration, this))
{
return decoder.Decode(stream);
}
}
}
-}
+}
\ No newline at end of file
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..3a6e14f59
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs
@@ -0,0 +1,968 @@
+//
+// 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.Common.Extensions;
+ 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.QuantizeAndInverse(ref frameComponent, offset, ref computationBufferSpan, ref quantizationTable);
+ }
+ }
+ }
+
+ 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
diff --git a/src/ImageSharp/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Memory/Fast2DArray{T}.cs
index 260c829e2..ee8a60983 100644
--- a/src/ImageSharp/Memory/Fast2DArray{T}.cs
+++ b/src/ImageSharp/Memory/Fast2DArray{T}.cs
@@ -106,11 +106,23 @@ namespace ImageSharp.Memory
return new Fast2DArray(data);
}
+ ///
+ /// Gets a representing the row beginning from the the first item on that row.
+ ///
+ /// The y-coordinate of the row. Must be greater than or equal to zero and less than the height of the 2D array.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetRowSpan(int row)
+ {
+ this.CheckCoordinates(row);
+ return new Span(this.Data, row * this.Width, this.Width);
+ }
+
///
/// Checks the coordinates to ensure they are within bounds.
///
- /// The row-coordinate of the item. Must be greater than zero and smaller than the height of the array.
- /// The column-coordinate of the item. Must be greater than zero and smaller than the width of the array.
+ /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the array.
+ /// The x-coordinate of the item. Must be greater than zero and smaller than the width of the array.
///
/// Thrown if the coordinates are not within the bounds of the array.
///
@@ -127,5 +139,21 @@ namespace ImageSharp.Memory
throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the array bounds.");
}
}
+
+ ///
+ /// Checks the coordinates to ensure they are within bounds.
+ ///
+ /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the array.
+ ///
+ /// Thrown if the coordinates are not within the bounds of the image.
+ ///
+ [Conditional("DEBUG")]
+ private void CheckCoordinates(int row)
+ {
+ if (row < 0 || row >= this.Height)
+ {
+ throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the array bounds.");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
index a7fd8fd6a..be4201c72 100644
--- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
+++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
@@ -5,6 +5,7 @@
namespace ImageSharp
{
+ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
@@ -82,9 +83,11 @@ namespace ImageSharp
this.values.Add(new ExifValue(value));
}
}
- else
+
+ if (other.data != null)
{
- this.data = other.data;
+ this.data = new byte[other.data.Length];
+ Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length);
}
}
@@ -131,7 +134,7 @@ namespace ImageSharp
return null;
}
- if (this.data.Length < (this.thumbnailOffset + this.thumbnailLength))
+ if (this.data == null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength))
{
return null;
}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
index 978d5bc24..1393afa62 100644
--- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
@@ -59,7 +59,11 @@ namespace ImageSharp
Guard.NotNull(other, nameof(other));
// TODO: Do we need to copy anything else?
- this.data = other.data;
+ if (other.data != null)
+ {
+ this.data = new byte[other.data.Length];
+ Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length);
+ }
}
///
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs
deleted file mode 100644
index caadaaa34..000000000
--- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-namespace ImageSharp.Benchmarks.General.Vectorization
-{
- using System.Numerics;
-
- using BenchmarkDotNet.Attributes;
-
- public class DivFloat
- {
- private float[] input;
-
- private float[] result;
-
- [Params(32)]
- public int InputSize { get; set; }
-
- private float testValue;
-
- [GlobalSetup]
- public void Setup()
- {
- this.input = new float[this.InputSize];
- this.result = new float[this.InputSize];
- this.testValue = 42;
-
- for (int i = 0; i < this.InputSize; i++)
- {
- this.input[i] = (uint)i;
- }
- }
-
- [Benchmark(Baseline = true)]
- public void Standard()
- {
- float v = this.testValue;
- for (int i = 0; i < this.input.Length; i++)
- {
- this.result[i] = this.input[i] / v;
- }
- }
-
- [Benchmark]
- public void Simd()
- {
- Vector v = new Vector(this.testValue);
-
- for (int i = 0; i < this.input.Length; i += Vector.Count)
- {
- Vector a = new Vector(this.input, i);
- a = a / v;
- a.CopyTo(this.result, i);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs
deleted file mode 100644
index 6b3edb192..000000000
--- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-namespace ImageSharp.Benchmarks.General.Vectorization
-{
- using System.Numerics;
-
- using BenchmarkDotNet.Attributes;
-
- public class DivUInt32
- {
- private uint[] input;
-
- private uint[] result;
-
- [Params(32)]
- public int InputSize { get; set; }
-
- private uint testValue;
-
- [GlobalSetup]
- public void Setup()
- {
- this.input = new uint[this.InputSize];
- this.result = new uint[this.InputSize];
- this.testValue = 42;
-
- for (int i = 0; i < this.InputSize; i++)
- {
- this.input[i] = (uint)i;
- }
- }
-
- [Benchmark(Baseline = true)]
- public void Standard()
- {
- uint v = this.testValue;
- for (int i = 0; i < this.input.Length; i++)
- {
- this.result[i] = this.input[i] / v;
- }
- }
-
- [Benchmark]
- public void Simd()
- {
- Vector v = new Vector(this.testValue);
-
- for (int i = 0; i < this.input.Length; i += Vector.Count)
- {
- Vector a = new Vector(this.input, i);
- a = a / v;
- a.CopyTo(this.result, i);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs
new file mode 100644
index 000000000..b38429557
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs
@@ -0,0 +1,69 @@
+namespace ImageSharp.Benchmarks.General.Vectorization
+{
+ using System;
+ using System.Numerics;
+
+ using BenchmarkDotNet.Attributes;
+
+ public class DivFloat : SIMDBenchmarkBase.Divide
+ {
+ protected override float GetTestValue() => 42;
+
+ [Benchmark(Baseline = true)]
+ public void Standard()
+ {
+ float v = this.testValue;
+ for (int i = 0; i < this.input.Length; i++)
+ {
+ this.result[i] = this.input[i] / v;
+ }
+ }
+ }
+
+ public class Divide : SIMDBenchmarkBase.Divide
+ {
+ protected override uint GetTestValue() => 42;
+
+ [Benchmark(Baseline = true)]
+ public void Standard()
+ {
+ uint v = this.testValue;
+ for (int i = 0; i < this.input.Length; i++)
+ {
+ this.result[i] = this.input[i] / v;
+ }
+ }
+ }
+
+ public class DivInt32 : SIMDBenchmarkBase.Divide
+ {
+ protected override int GetTestValue() => 42;
+
+ [Benchmark(Baseline = true)]
+ public void Standard()
+ {
+ int v = this.testValue;
+ for (int i = 0; i < this.input.Length; i++)
+ {
+ this.result[i] = this.input[i] / v;
+ }
+ }
+ }
+
+ public class DivInt16 : SIMDBenchmarkBase.Divide
+ {
+ protected override short GetTestValue() => 42;
+
+ protected override Vector GetTestVector() => new Vector(new short[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17});
+
+ [Benchmark(Baseline = true)]
+ public void Standard()
+ {
+ short v = this.testValue;
+ for (int i = 0; i < this.input.Length; i++)
+ {
+ this.result[i] = (short)(this.input[i] / v);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs
deleted file mode 100644
index e5ae49b2a..000000000
--- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-namespace ImageSharp.Benchmarks.General.Vectorization
-{
- using System.Numerics;
-
- using BenchmarkDotNet.Attributes;
-
- public class MulFloat
- {
- private float[] input;
-
- private float[] result;
-
- [Params(32)]
- public int InputSize { get; set; }
-
- private float testValue;
-
- [GlobalSetup]
- public void Setup()
- {
- this.input = new float[this.InputSize];
- this.result = new float[this.InputSize];
- this.testValue = 42;
-
- for (int i = 0; i < this.InputSize; i++)
- {
- this.input[i] = i;
- }
- }
-
- [Benchmark(Baseline = true)]
- public void Standard()
- {
- float v = this.testValue;
- for (int i = 0; i < this.input.Length; i++)
- {
- this.result[i] = this.input[i] * v;
- }
- }
-
- [Benchmark]
- public void SimdMultiplyByVector()
- {
- Vector v = new Vector(this.testValue);
-
- for (int i = 0; i < this.input.Length; i += Vector.Count)
- {
- Vector a = new Vector(this.input, i);
- a = a * v;
- a.CopyTo(this.result, i);
- }
- }
-
- [Benchmark]
- public void SimdMultiplyByScalar()
- {
- float v = this.testValue;
-
- for (int i = 0; i < this.input.Length; i += Vector.Count)
- {
- Vector a = new Vector(this.input, i);
- a = a * v;
- a.CopyTo(this.result, i);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs
deleted file mode 100644
index 532acc02d..000000000
--- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-namespace ImageSharp.Benchmarks.General.Vectorization
-{
- using System.Numerics;
-
- using BenchmarkDotNet.Attributes;
-
- public class MulUInt32
- {
- private uint[] input;
-
- private uint[] result;
-
- [Params(32)]
- public int InputSize { get; set; }
-
- private uint testValue;
-
- [GlobalSetup]
- public void Setup()
- {
- this.input = new uint[this.InputSize];
- this.result = new uint[this.InputSize];
- this.testValue = 42;
-
- for (int i = 0; i < this.InputSize; i++)
- {
- this.input[i] = (uint)i;
- }
- }
-
- [Benchmark(Baseline = true)]
- public void Standard()
- {
- uint v = this.testValue;
- for (int i = 0; i < this.input.Length; i++)
- {
- this.result[i] = this.input[i] * v;
- }
- }
-
- [Benchmark]
- public void Simd()
- {
- Vector v = new Vector(this.testValue);
-
- for (int i = 0; i < this.input.Length; i += Vector.Count)
- {
- Vector a = new Vector(this.input, i);
- a = a * v;
- a.CopyTo(this.result, i);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs
new file mode 100644
index 000000000..d1b70f21b
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs
@@ -0,0 +1,50 @@
+namespace ImageSharp.Benchmarks.General.Vectorization
+{
+ using System.Numerics;
+ using BenchmarkDotNet.Attributes;
+
+ public class MulUInt32 : SIMDBenchmarkBase.Multiply
+ {
+ protected override uint GetTestValue() => 42u;
+
+ [Benchmark(Baseline = true)]
+ public void Standard()
+ {
+ uint v = this.testValue;
+ for (int i = 0; i < this.input.Length; i++)
+ {
+ this.result[i] = this.input[i] * v;
+ }
+ }
+ }
+
+ public class MulInt32 : SIMDBenchmarkBase.Multiply
+ {
+ [Benchmark(Baseline = true)]
+ public void Standard()
+ {
+ int v = this.testValue;
+ for (int i = 0; i < this.input.Length; i++)
+ {
+ this.result[i] = this.input[i] * v;
+ }
+ }
+ }
+
+ public class MulInt16 : SIMDBenchmarkBase.Multiply
+ {
+ protected override short GetTestValue() => 42;
+
+ protected override Vector GetTestVector() => new Vector(new short[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 });
+
+ [Benchmark(Baseline = true)]
+ public void Standard()
+ {
+ short v = this.testValue;
+ for (int i = 0; i < this.input.Length; i++)
+ {
+ this.result[i] = (short)(this.input[i] * v);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs
new file mode 100644
index 000000000..76987dbd2
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs
@@ -0,0 +1,70 @@
+namespace ImageSharp.Benchmarks.General.Vectorization
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ using BenchmarkDotNet.Attributes;
+
+ public abstract class SIMDBenchmarkBase
+ where T : struct
+ {
+ protected T[] input;
+
+ protected T[] result;
+
+ protected T testValue;
+
+ protected Vector testVector;
+
+ protected virtual T GetTestValue() => default(T);
+
+ protected virtual Vector GetTestVector() => new Vector(this.GetTestValue());
+
+
+ [Params(32)]
+ public int InputSize { get; set; }
+
+ [GlobalSetup]
+ public virtual void Setup()
+ {
+ this.input = new T[this.InputSize];
+ this.result = new T[this.InputSize];
+ this.testValue = this.GetTestValue();
+ this.testVector = this.GetTestVector();
+ }
+
+ public abstract class Multiply : SIMDBenchmarkBase
+ {
+ [Benchmark]
+ public void Simd()
+ {
+ Vector v = this.testVector;
+
+ for (int i = 0; i < this.input.Length; i += Vector.Count)
+ {
+ Vector a = Unsafe.As>(ref this.input[i]);
+ a = a * v;
+ Unsafe.As>(ref this.result[i]) = a;
+ }
+ }
+ }
+
+ public abstract class Divide : SIMDBenchmarkBase
+ {
+ [Benchmark]
+ public void Simd()
+ {
+ Vector v = this.testVector;
+
+ for (int i = 0; i < this.input.Length; i += Vector.Count)
+ {
+ Vector a = Unsafe.As>(ref this.input[i]);
+ a = a / v;
+ Unsafe.As>(ref this.result[i]) = a;
+ }
+ }
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs
index ece93f912..aaf44c692 100644
--- a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs
+++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs
@@ -14,6 +14,7 @@ namespace ImageSharp.Benchmarks.Image
using CoreSize = SixLabors.Primitives.Size;
+ [Config(typeof(Config))]
public class DecodeJpeg : BenchmarkBase
{
private byte[] jpegBytes;
@@ -23,21 +24,21 @@ namespace ImageSharp.Benchmarks.Image
{
if (this.jpegBytes == null)
{
- this.jpegBytes = File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Jpg/Baseline/Calliphora.jpg");
+ this.jpegBytes = File.ReadAllBytes("../../../../../../../../ImageSharp.Tests/TestImages/Formats/Jpg/Baseline/Calliphora.jpg");
}
}
- [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
- public Size JpegSystemDrawing()
- {
- using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes))
- {
- using (Image image = Image.FromStream(memoryStream))
- {
- return image.Size;
- }
- }
- }
+ //[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
+ //public Size JpegSystemDrawing()
+ //{
+ // using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes))
+ // {
+ // using (Image image = Image.FromStream(memoryStream))
+ // {
+ // return image.Size;
+ // }
+ // }
+ //}
[Benchmark(Description = "ImageSharp Jpeg")]
public CoreSize JpegCore()
diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs
index 44c90d253..ebcdf972a 100644
--- a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs
+++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs
@@ -6,8 +6,13 @@
namespace ImageSharp.Benchmarks.Image
{
using System.Collections.Generic;
+ using System.IO;
+
using BenchmarkDotNet.Attributes;
+ using ImageSharp.Formats;
+ using ImageSharp.PixelFormats;
+
using CoreImage = ImageSharp.Image;
[Config(typeof(Config.Short))]
@@ -20,14 +25,23 @@ namespace ImageSharp.Benchmarks.Image
protected override IEnumerable SearchPatterns => new[] { "*.jpg" };
- [Benchmark(Description = "DecodeJpegMultiple - ImageSharp")]
- public void DecodeJpegImageSharp()
+ [Benchmark(Description = "DecodeJpegMultiple - ImageSharp NEW")]
+ public void DecodeJpegImageSharpNwq()
{
this.ForEachStream(
ms => CoreImage.Load(ms)
);
}
+
+ [Benchmark(Description = "DecodeJpegMultiple - ImageSharp Original")]
+ public void DecodeJpegImageSharpOriginal()
+ {
+ this.ForEachStream(
+ ms => CoreImage.Load(ms, new OriginalJpegDecoder())
+ );
+ }
+
[Benchmark(Baseline = true, Description = "DecodeJpegMultiple - System.Drawing")]
public void DecodeJpegSystemDrawing()
{
@@ -36,5 +50,25 @@ namespace ImageSharp.Benchmarks.Image
);
}
+
+ public sealed class OriginalJpegDecoder : IImageDecoder, IJpegDecoderOptions
+ {
+ ///
+ /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ public bool IgnoreMetadata { get; set; }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ Guard.NotNull(stream, "stream");
+
+ using (var decoder = new JpegDecoderCore(configuration, this))
+ {
+ return decoder.Decode(stream);
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
index e01a5951e..fcd72a9ba 100644
--- a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
+++ b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
@@ -39,7 +39,7 @@ namespace ImageSharp.Benchmarks.Image
[Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)]
public virtual InputImageCategory InputCategory { get; set; }
- protected virtual string BaseFolder => "../ImageSharp.Tests/TestImages/Formats/";
+ protected virtual string BaseFolder => "../../../../../../../../ImageSharp.Tests/TestImages/Formats/";
protected virtual IEnumerable SearchPatterns => new[] { "*.*" };
diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs
index b536af71f..8b221f75b 100644
--- a/tests/ImageSharp.Sandbox46/Program.cs
+++ b/tests/ImageSharp.Sandbox46/Program.cs
@@ -40,10 +40,10 @@ namespace ImageSharp.Sandbox46
///
public static void Main(string[] args)
{
- // RunDecodeJpegProfilingTests();
+ RunDecodeJpegProfilingTests();
// RunToVector4ProfilingTest();
- RunResizeProfilingTest();
+ //RunResizeProfilingTest();
Console.ReadLine();
}
diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs
index 68ccfe06b..dea780cd7 100644
--- a/tests/ImageSharp.Tests/FileTestBase.cs
+++ b/tests/ImageSharp.Tests/FileTestBase.cs
@@ -75,7 +75,9 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only
+ // TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.Bad.MissingEOF), // Perf: Enable for local testing only
+ // TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg
new file mode 100644
index 000000000..2fe8f0a61
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/baseline/ycck - Copy.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0
+size 611572
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm
new file mode 100644
index 000000000..72a5e448b
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpeg.htm
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js
new file mode 100644
index 000000000..6ebf71a69
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestImages/Formats/Jpg/jpg.js
@@ -0,0 +1,1205 @@
+/* Copyright 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* eslint-disable no-multi-spaces */
+
+// import { error, warn } from '../shared/util';
+
+/**
+ * This code was forked from https://github.com/notmasteryet/jpgjs.
+ * The original version was created by GitHub user notmasteryet.
+ *
+ * - The JPEG specification can be found in the ITU CCITT Recommendation T.81
+ * (www.w3.org/Graphics/JPEG/itu-t81.pdf)
+ * - The JFIF specification can be found in the JPEG File Interchange Format
+ * (www.w3.org/Graphics/JPEG/jfif3.pdf)
+ * - The Adobe Application-Specific JPEG markers in the
+ * Supporting the DCT Filters in PostScript Level 2, Technical Note #5116
+ * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
+ */
+
+var error = function(val){
+ console.log(val);
+}
+
+var warn = function(val){
+ console.log(val);
+}
+
+var JpegImage = (function JpegImageClosure() {
+ var dctZigZag = new Uint8Array([
+ 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
+ ]);
+
+ var dctCos1 = 4017; // cos(pi/16)
+ var dctSin1 = 799; // sin(pi/16)
+ var dctCos3 = 3406; // cos(3*pi/16)
+ var dctSin3 = 2276; // sin(3*pi/16)
+ var dctCos6 = 1567; // cos(6*pi/16)
+ var dctSin6 = 3784; // sin(6*pi/16)
+ var dctSqrt2 = 5793; // sqrt(2)
+ var dctSqrt1d2 = 2896; // sqrt(2) / 2
+
+ function JpegImage() {
+ this.decodeTransform = null;
+ this.colorTransform = -1;
+ }
+
+ function buildHuffmanTable(codeLengths, values) {
+ console.log(codeLengths);
+ console.log(values);
+
+ var k = 0, code = [], i, j, length = 16;
+ while (length > 0 && !codeLengths[length - 1]) {
+ length--;
+ }
+
+ code.push({ children: [], index: 0, });
+ var p = code[0], q;
+ for (i = 0; i < length; i++) {
+ for (j = 0; j < codeLengths[i]; j++) {
+ p = code.pop();
+ p.children[p.index] = values[k];
+
+ while (p.index > 0) {
+ p = code.pop();
+ }
+ p.index++;
+ code.push(p);
+ while (code.length <= i) {
+ code.push(q = { children: [], index: 0, });
+ p.children[p.index] = q.children;
+ p = q;
+ }
+ k++;
+ }
+ if (i + 1 < length) {
+ // p here points to last code
+ code.push(q = { children: [], index: 0, });
+ p.children[p.index] = q.children;
+ p = q;
+ }
+ }
+ console.log(code[0].children);
+ console.log(k);
+ return code[0].children;
+ }
+
+ function getBlockBufferOffset(component, row, col) {
+ return 64 * ((component.blocksPerLine + 1) * row + col);
+ }
+
+ function decodeScan(data, offset, frame, components, resetInterval,
+ spectralStart, spectralEnd, successivePrev, successive) {
+ var mcusPerLine = frame.mcusPerLine;
+ var progressive = frame.progressive;
+ var startOffset = offset, bitsData = 0, bitsCount = 0;
+
+ function readBit() {
+ if (bitsCount > 0) {
+ bitsCount--;
+ return (bitsData >> bitsCount) & 1;
+ }
+ bitsData = data[offset++];
+ if (bitsData === 0xFF) {
+ var nextByte = data[offset++];
+ if (nextByte) {
+ error('JPEG error: unexpected marker ' +
+ ((bitsData << 8) | nextByte).toString(16));
+ }
+ // unstuff 0
+ }
+ bitsCount = 7;
+ return bitsData >>> 7;
+ }
+
+ function decodeHuffman(tree) {
+ var node = tree;
+ while (true) {
+ node = node[readBit()];
+ if (typeof node === 'number') {
+ return node;
+ }
+ if (typeof node !== 'object') {
+ error('JPEG error: invalid huffman sequence');
+ }
+ }
+ }
+
+ function receive(length) {
+ var n = 0;
+ while (length > 0) {
+ n = (n << 1) | readBit();
+ length--;
+ }
+ return n;
+ }
+
+ function receiveAndExtend(length) {
+ if (length === 1) {
+ return readBit() === 1 ? 1 : -1;
+ }
+ var n = receive(length);
+ if (n >= 1 << (length - 1)) {
+ return n;
+ }
+ return n + (-1 << length) + 1;
+ }
+
+ function decodeBaseline(component, offset) {
+ var t = decodeHuffman(component.huffmanTableDC);
+ var diff = t === 0 ? 0 : receiveAndExtend(t);
+ component.blockData[offset] = (component.pred += diff);
+ // console.log("component");
+ // console.log(component);
+
+ if(offset === 0){
+ console.log("component at 0");
+ console.log(component.blockData[offset])
+ }
+
+ var k = 1;
+ while (k < 64) {
+ var rs = decodeHuffman(component.huffmanTableAC);
+ var s = rs & 15, r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ break;
+ }
+ k += 16;
+ continue;
+ }
+ k += r;
+ var z = dctZigZag[k];
+ component.blockData[offset + z] = receiveAndExtend(s);
+ k++;
+ }
+ }
+
+ function decodeDCFirst(component, offset) {
+ var t = decodeHuffman(component.huffmanTableDC);
+ var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
+ component.blockData[offset] = (component.pred += diff);
+ }
+
+ function decodeDCSuccessive(component, offset) {
+ component.blockData[offset] |= readBit() << successive;
+ }
+
+ var eobrun = 0;
+ function decodeACFirst(component, offset) {
+ if (eobrun > 0) {
+ eobrun--;
+ return;
+ }
+ var k = spectralStart, e = spectralEnd;
+ while (k <= e) {
+ var rs = decodeHuffman(component.huffmanTableAC);
+ var s = rs & 15, r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r) - 1;
+ break;
+ }
+ k += 16;
+ continue;
+ }
+ k += r;
+ var z = dctZigZag[k];
+ component.blockData[offset + z] =
+ receiveAndExtend(s) * (1 << successive);
+ k++;
+ }
+ }
+
+ var successiveACState = 0, successiveACNextValue;
+ function decodeACSuccessive(component, offset) {
+ var k = spectralStart;
+ var e = spectralEnd;
+ var r = 0;
+ var s;
+ var rs;
+ while (k <= e) {
+ var z = dctZigZag[k];
+ switch (successiveACState) {
+ case 0: // initial state
+ rs = decodeHuffman(component.huffmanTableAC);
+ s = rs & 15;
+ r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r);
+ successiveACState = 4;
+ } else {
+ r = 16;
+ successiveACState = 1;
+ }
+ } else {
+ if (s !== 1) {
+ error('JPEG error: invalid ACn encoding');
+ }
+ successiveACNextValue = receiveAndExtend(s);
+ successiveACState = r ? 2 : 3;
+ }
+ continue;
+ case 1: // skipping r zero items
+ case 2:
+ if (component.blockData[offset + z]) {
+ component.blockData[offset + z] += (readBit() << successive);
+ } else {
+ r--;
+ if (r === 0) {
+ successiveACState = successiveACState === 2 ? 3 : 0;
+ }
+ }
+ break;
+ case 3: // set value for a zero item
+ if (component.blockData[offset + z]) {
+ component.blockData[offset + z] += (readBit() << successive);
+ } else {
+ component.blockData[offset + z] =
+ successiveACNextValue << successive;
+ successiveACState = 0;
+ }
+ break;
+ case 4: // eob
+ if (component.blockData[offset + z]) {
+ component.blockData[offset + z] += (readBit() << successive);
+ }
+ break;
+ }
+ k++;
+ }
+ if (successiveACState === 4) {
+ eobrun--;
+ if (eobrun === 0) {
+ successiveACState = 0;
+ }
+ }
+ }
+
+ function decodeMcu(component, decode, mcu, row, col) {
+ var mcuRow = (mcu / mcusPerLine) | 0;
+ var mcuCol = mcu % mcusPerLine;
+ var blockRow = mcuRow * component.v + row;
+ var blockCol = mcuCol * component.h + col;
+ var offset = getBlockBufferOffset(component, blockRow, blockCol);
+
+ // console.log("MCU Offset: " + offset);
+ decode(component, offset);
+ }
+
+ function decodeBlock(component, decode, mcu) {
+ var blockRow = (mcu / component.blocksPerLine) | 0;
+ var blockCol = mcu % component.blocksPerLine;
+ var offset = getBlockBufferOffset(component, blockRow, blockCol);
+ decode(component, offset);
+ }
+
+ var componentsLength = components.length;
+ var component, i, j, k, n;
+ var decodeFn;
+ if (progressive) {
+ if (spectralStart === 0) {
+ decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
+ console.log(successivePrev === 0 ? "decodeDCFirst" : "decodeDCSuccessive");
+ } else {
+ decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
+ console.log(successivePrev === 0 ? "decodeACFirst" : "decodeACSuccessive");
+ }
+ } else {
+ decodeFn = decodeBaseline;
+ }
+
+ var mcu = 0, fileMarker;
+ var mcuExpected;
+ if (componentsLength === 1) {
+ mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
+ } else {
+ mcuExpected = mcusPerLine * frame.mcusPerColumn;
+ }
+
+ console.log("mcuExpected = "+ mcuExpected);
+
+ var h, v;
+ while (mcu < mcuExpected) {
+ // reset interval stuff
+ var mcuToRead = resetInterval ?
+ Math.min(mcuExpected - mcu, resetInterval) : mcuExpected;
+ for (i = 0; i < componentsLength; i++) {
+ components[i].pred = 0;
+ }
+ eobrun = 0;
+
+ if (componentsLength === 1) {
+ component = components[0];
+
+ for (n = 0; n < mcuToRead; n++) {
+ decodeBlock(component, decodeFn, mcu);
+ mcu++;
+ }
+ } else {
+ for (n = 0; n < mcuToRead; n++) {
+ for (i = 0; i < componentsLength; i++) {
+ component = components[i];
+ h = component.h;
+ v = component.v;
+ for (j = 0; j < v; j++) {
+ for (k = 0; k < h; k++) {
+ decodeMcu(component, decodeFn, mcu, j, k);
+ }
+ }
+ }
+ mcu++;
+ }
+ }
+
+ // find marker
+ bitsCount = 0;
+ fileMarker = findNextFileMarker(data, offset);
+ // 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).
+ if (fileMarker && fileMarker.invalid) {
+ warn('decodeScan - unexpected MCU data, next marker is: ' +
+ fileMarker.invalid);
+ offset = fileMarker.offset;
+ }
+ var marker = fileMarker && fileMarker.marker;
+ if (!marker || marker <= 0xFF00) {
+ error('JPEG error: marker was not found');
+ }
+
+ if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
+ offset += 2;
+ } else {
+ break;
+ }
+ }
+
+ fileMarker = findNextFileMarker(data, offset);
+ // Some images include more Scan blocks than expected, skip past those and
+ // attempt to find the next valid marker (fixes issue8182.pdf).
+ if (fileMarker && fileMarker.invalid) {
+ warn('decodeScan - unexpected Scan data, next marker is: ' +
+ fileMarker.invalid);
+ offset = fileMarker.offset;
+ }
+
+ return offset - startOffset;
+ }
+
+ // 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.
+ function quantizeAndInverse(component, blockBufferOffset, p) {
+ var qt = component.quantizationTable, blockData = component.blockData;
+ var v0, v1, v2, v3, v4, v5, v6, v7;
+ var p0, p1, p2, p3, p4, p5, p6, p7;
+ var t;
+
+ if (!qt) {
+ error('JPEG error: missing required Quantization Table.');
+ }
+
+ // inverse DCT on rows
+ for (var row = 0; row < 64; row += 8) {
+ // gather block data
+ p0 = blockData[blockBufferOffset + row];
+ p1 = blockData[blockBufferOffset + row + 1];
+ p2 = blockData[blockBufferOffset + row + 2];
+ p3 = blockData[blockBufferOffset + row + 3];
+ p4 = blockData[blockBufferOffset + row + 4];
+ p5 = blockData[blockBufferOffset + row + 5];
+ p6 = blockData[blockBufferOffset + row + 6];
+ p7 = blockData[blockBufferOffset + row + 7];
+
+ // dequant p0
+ p0 *= qt[row];
+
+ // check for all-zero AC coefficients
+ if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
+ t = (dctSqrt2 * p0 + 512) >> 10;
+ p[row] = t;
+ p[row + 1] = t;
+ p[row + 2] = t;
+ p[row + 3] = t;
+ p[row + 4] = t;
+ p[row + 5] = t;
+ p[row + 6] = t;
+ p[row + 7] = t;
+ continue;
+ }
+ // dequant p1 ... p7
+ p1 *= qt[row + 1];
+ p2 *= qt[row + 2];
+ p3 *= qt[row + 3];
+ p4 *= qt[row + 4];
+ p5 *= qt[row + 5];
+ p6 *= qt[row + 6];
+ p7 *= qt[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
+ p[row] = v0 + v7;
+ p[row + 7] = v0 - v7;
+ p[row + 1] = v1 + v6;
+ p[row + 6] = v1 - v6;
+ p[row + 2] = v2 + v5;
+ p[row + 5] = v2 - v5;
+ p[row + 3] = v3 + v4;
+ p[row + 4] = v3 - v4;
+ }
+
+ // inverse DCT on columns
+ for (var col = 0; col < 8; ++col) {
+ p0 = p[col];
+ p1 = p[col + 8];
+ p2 = p[col + 16];
+ p3 = p[col + 24];
+ p4 = p[col + 32];
+ p5 = p[col + 40];
+ p6 = p[col + 48];
+ p7 = p[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) ? 255 : (t + 2056) >> 4;
+ blockData[blockBufferOffset + col] = t;
+ blockData[blockBufferOffset + col + 8] = t;
+ blockData[blockBufferOffset + col + 16] = t;
+ blockData[blockBufferOffset + col + 24] = t;
+ blockData[blockBufferOffset + col + 32] = t;
+ blockData[blockBufferOffset + col + 40] = t;
+ blockData[blockBufferOffset + col + 48] = t;
+ blockData[blockBufferOffset + col + 56] = t;
+ 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) ? 255 : p0 >> 4;
+ p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? 255 : p1 >> 4;
+ p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? 255 : p2 >> 4;
+ p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? 255 : p3 >> 4;
+ p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? 255 : p4 >> 4;
+ p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? 255 : p5 >> 4;
+ p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? 255 : p6 >> 4;
+ p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? 255 : p7 >> 4;
+
+ // store block data
+ blockData[blockBufferOffset + col] = p0;
+ blockData[blockBufferOffset + col + 8] = p1;
+ blockData[blockBufferOffset + col + 16] = p2;
+ blockData[blockBufferOffset + col + 24] = p3;
+ blockData[blockBufferOffset + col + 32] = p4;
+ blockData[blockBufferOffset + col + 40] = p5;
+ blockData[blockBufferOffset + col + 48] = p6;
+ blockData[blockBufferOffset + col + 56] = p7;
+ }
+ }
+
+ function buildComponentData(frame, component) {
+ var blocksPerLine = component.blocksPerLine;
+ var blocksPerColumn = component.blocksPerColumn;
+ var computationBuffer = new Int16Array(64);
+ console.log("qt");
+ console.log(component.quantizationTable);
+ for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+ for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+ var offset = getBlockBufferOffset(component, blockRow, blockCol);
+ quantizeAndInverse(component, offset, computationBuffer);
+ }
+ }
+
+ console.log("component.blockData");
+ console.log(component.blockData);
+ return component.blockData;
+ }
+
+ function clamp0to255(a) {
+ return a <= 0 ? 0 : a >= 255 ? 255 : a;
+ }
+
+ function findNextFileMarker(data, currentPos, startPos) {
+ function peekUint16(pos) {
+ return (data[pos] << 8) | data[pos + 1];
+ }
+
+ var maxPos = data.length - 1;
+ var newPos = startPos < currentPos ? startPos : currentPos;
+
+ if (currentPos >= maxPos) {
+ return null; // Don't attempt to read non-existent data and just return.
+ }
+ var currentMarker = peekUint16(currentPos);
+ if (currentMarker >= 0xFFC0 && currentMarker <= 0xFFFE) {
+ return {
+ invalid: null,
+ marker: currentMarker,
+ offset: currentPos,
+ };
+ }
+ var newMarker = peekUint16(newPos);
+ while (!(newMarker >= 0xFFC0 && newMarker <= 0xFFFE)) {
+ if (++newPos >= maxPos) {
+ return null; // Don't attempt to read non-existent data and just return.
+ }
+ newMarker = peekUint16(newPos);
+ }
+ return {
+ invalid: currentMarker.toString(16),
+ marker: newMarker,
+ offset: newPos,
+ };
+ }
+
+ JpegImage.prototype = {
+ parse: function parse(data) {
+
+ function readUint16() {
+ var value = (data[offset] << 8) | data[offset + 1];
+ offset += 2;
+ return value;
+ }
+
+ function readDataBlock() {
+ var length = readUint16();
+ var endOffset = offset + length - 2;
+
+ var fileMarker = findNextFileMarker(data, endOffset, offset);
+ if (fileMarker && fileMarker.invalid) {
+ warn('readDataBlock - incorrect length, next marker is: ' +
+ fileMarker.invalid);
+ endOffset = fileMarker.offset;
+ }
+
+ var array = data.subarray(offset, endOffset);
+ offset += array.length;
+ return array;
+ }
+
+ function prepareComponents(frame) {
+ var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
+ var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
+ for (var i = 0; i < frame.components.length; i++) {
+ component = frame.components[i];
+ var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) *
+ component.h / frame.maxH);
+ var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) *
+ component.v / frame.maxV);
+ var blocksPerLineForMcu = mcusPerLine * component.h;
+ var blocksPerColumnForMcu = mcusPerColumn * component.v;
+
+ var blocksBufferSize = 64 * blocksPerColumnForMcu *
+ (blocksPerLineForMcu + 1);
+ component.blockData = new Int16Array(blocksBufferSize);
+ component.blocksPerLine = blocksPerLine;
+ component.blocksPerColumn = blocksPerColumn;
+ }
+ frame.mcusPerLine = mcusPerLine;
+ frame.mcusPerColumn = mcusPerColumn;
+ }
+
+ var offset = 0;
+ var jfif = null;
+ var adobe = null;
+ var frame, resetInterval;
+ var quantizationTables = [];
+ var huffmanTablesAC = [], huffmanTablesDC = [];
+ var fileMarker = readUint16();
+ if (fileMarker !== 0xFFD8) { // SOI (Start of Image)
+ error('JPEG error: SOI not found');
+ }
+
+ fileMarker = readUint16();
+ while (fileMarker !== 0xFFD9) { // EOI (End of image)
+ var i, j, l;
+ switch (fileMarker) {
+ case 0xFFE0: // APP0 (Application Specific)
+ case 0xFFE1: // APP1
+ case 0xFFE2: // APP2
+ case 0xFFE3: // APP3
+ case 0xFFE4: // APP4
+ case 0xFFE5: // APP5
+ case 0xFFE6: // APP6
+ case 0xFFE7: // APP7
+ case 0xFFE8: // APP8
+ case 0xFFE9: // APP9
+ case 0xFFEA: // APP10
+ case 0xFFEB: // APP11
+ case 0xFFEC: // APP12
+ case 0xFFED: // APP13
+ case 0xFFEE: // APP14
+ case 0xFFEF: // APP15
+ case 0xFFFE: // COM (Comment)
+ var appData = readDataBlock();
+
+ if (fileMarker === 0xFFE0) {
+ if (appData[0] === 0x4A && appData[1] === 0x46 &&
+ appData[2] === 0x49 && appData[3] === 0x46 &&
+ appData[4] === 0) { // 'JFIF\x00'
+ jfif = {
+ version: { major: appData[5], minor: appData[6], },
+ densityUnits: appData[7],
+ xDensity: (appData[8] << 8) | appData[9],
+ yDensity: (appData[10] << 8) | appData[11],
+ thumbWidth: appData[12],
+ thumbHeight: appData[13],
+ thumbData: appData.subarray(14, 14 +
+ 3 * appData[12] * appData[13]),
+ };
+ }
+ }
+ // TODO APP1 - Exif
+ if (fileMarker === 0xFFEE) {
+ if (appData[0] === 0x41 && appData[1] === 0x64 &&
+ appData[2] === 0x6F && appData[3] === 0x62 &&
+ appData[4] === 0x65) { // 'Adobe'
+ adobe = {
+ version: (appData[5] << 8) | appData[6],
+ flags0: (appData[7] << 8) | appData[8],
+ flags1: (appData[9] << 8) | appData[10],
+ transformCode: appData[11],
+ };
+ }
+ }
+ break;
+
+ case 0xFFDB: // DQT (Define Quantization Tables)
+ var quantizationTablesLength = readUint16();
+ var quantizationTablesEnd = quantizationTablesLength + offset - 2;
+ var z;
+ while (offset < quantizationTablesEnd) {
+ var quantizationTableSpec = data[offset++];
+ var tableData = new Uint16Array(64);
+ if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
+ for (j = 0; j < 64; j++) {
+ z = dctZigZag[j];
+ tableData[z] = data[offset++];
+ }
+ } else if ((quantizationTableSpec >> 4) === 1) { // 16 bit values
+ for (j = 0; j < 64; j++) {
+ z = dctZigZag[j];
+ tableData[z] = readUint16();
+ }
+ } else {
+ error('JPEG error: DQT - invalid table spec');
+ }
+ quantizationTables[quantizationTableSpec & 15] = tableData;
+ }
+ break;
+
+ case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
+ case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
+ case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
+ if (frame) {
+ error('JPEG error: Only single frame JPEGs supported');
+ }
+ console.log("filemarker");
+ console.log(fileMarker);
+ console.log(offset);
+ readUint16(); // skip data length
+ frame = {};
+ frame.extended = (fileMarker === 0xFFC1);
+ frame.progressive = (fileMarker === 0xFFC2);
+ frame.precision = data[offset++];
+ frame.scanLines = readUint16();
+ frame.samplesPerLine = readUint16();
+ frame.components = [];
+ frame.componentIds = {};
+ var componentsCount = data[offset++], componentId;
+ var maxH = 0, maxV = 0;
+ for (i = 0; i < componentsCount; i++) {
+ componentId = data[offset];
+ var h = data[offset + 1] >> 4;
+ var v = data[offset + 1] & 15;
+ if (maxH < h) {
+ maxH = h;
+ }
+ if (maxV < v) {
+ maxV = v;
+ }
+ var qId = data[offset + 2];
+ l = frame.components.push({
+ h,
+ v,
+ quantizationId: qId,
+ quantizationTable: null, // See comment below.
+ });
+ frame.componentIds[componentId] = l - 1;
+ offset += 3;
+ }
+
+ frame.maxH = maxH;
+ frame.maxV = maxV;
+ prepareComponents(frame);
+ break;
+
+ case 0xFFC4: // DHT (Define Huffman Tables)
+ var huffmanLength = readUint16();
+ for (i = 2; i < huffmanLength;) {
+ console.log("offset= " + offset);
+ var huffmanTableSpec = data[offset++];
+ console.log("huffmanTableSpec= " + huffmanTableSpec);
+
+ var codeLengths = new Uint8Array(16);
+ var codeLengthSum = 0;
+ for (j = 0; j < 16; j++, offset++) {
+ codeLengthSum += (codeLengths[j] = data[offset]);
+ }
+ console.log("codelengthsum = " + codeLengthSum);
+ console.log("offset = " + offset);
+ var huffmanValues = new Uint8Array(codeLengthSum);
+ for (j = 0; j < codeLengthSum; j++, offset++) {
+ huffmanValues[j] = data[offset];
+ }
+ i += 17 + codeLengthSum;
+
+
+ console.log((huffmanTableSpec >> 4) === 0 ? "DC":"AC");
+ ((huffmanTableSpec >> 4) === 0
+ ? huffmanTablesDC
+ : huffmanTablesAC)[huffmanTableSpec & 15] =
+ buildHuffmanTable(codeLengths, huffmanValues);
+ }
+ break;
+
+ case 0xFFDD: // DRI (Define Restart Interval)
+ readUint16(); // skip data length
+ resetInterval = readUint16();
+ break;
+
+ case 0xFFDA: // SOS (Start of Scan)
+ readUint16(); // scanLength
+ var selectorsCount = data[offset++];
+ var components = [], component;
+ for (i = 0; i < selectorsCount; i++) {
+ var ci = data[offset++];
+ console.log("ci= " + ci);
+ console.log("offset= " + offset);
+
+ var componentIndex = frame.componentIds[ci];
+ console.log("componentIndex= " + componentIndex);
+ component = frame.components[componentIndex];
+ var tableSpec = data[offset++];
+ component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
+ component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
+ components.push(component);
+ }
+ console.log("components= " + components);
+
+ var spectralStart = data[offset++];
+ var spectralEnd = data[offset++];
+ var successiveApproximation = data[offset++];
+
+ console.log(frame.componentIds);
+ console.log("spectralStart= " + spectralStart);
+ console.log("spectralEnd= " + spectralEnd);
+ console.log("successiveApproximation= " + successiveApproximation);
+ // console.log("components before")
+ // console.log(components)
+ var processed = decodeScan(data, offset,
+ frame, components, resetInterval,
+ spectralStart, spectralEnd,
+ successiveApproximation >> 4, successiveApproximation & 15);
+ offset += processed;
+ console.log("components after");
+ // console.log(frame);
+ for (var i = 0; i < 3; i++){
+ for (var j = 0; j < 10; j++){
+ console.log("component ["+ i +"] : value ["+j+"] ="+ frame.components[i].blockData[j]+"]");
+ }
+ }
+ break;
+
+ case 0xFFFF: // Fill bytes
+ if (data[offset] !== 0xFF) { // Avoid skipping a valid marker.
+ offset--;
+ }
+ break;
+
+ default:
+ if (data[offset - 3] === 0xFF &&
+ data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
+ // could be incorrect encoding -- last 0xFF byte of the previous
+ // block was eaten by the encoder
+ offset -= 3;
+ break;
+ }
+
+ // TODO: Delete this after testing
+ fileMarker = 0xFFD9;
+ // error('JPEG error: unknown marker ' + fileMarker.toString(16));
+ }
+ fileMarker = readUint16();
+ }
+
+ console.log("quantizationTables");
+ console.log(quantizationTables);
+
+ this.width = frame.samplesPerLine;
+ this.height = frame.scanLines;
+ this.jfif = jfif;
+ this.adobe = adobe;
+ this.components = [];
+ for (i = 0; i < frame.components.length; i++) {
+ component = frame.components[i];
+
+ // Prevent errors when DQT markers are placed after SOF{n} markers,
+ // by assigning the `quantizationTable` entry after the entire image
+ // has been parsed (fixes issue7406.pdf).
+ var quantizationTable = quantizationTables[component.quantizationId];
+ if (quantizationTable) {
+ component.quantizationTable = quantizationTable;
+ }
+
+ this.components.push({
+ output: buildComponentData(frame, component),
+ scaleX: component.h / frame.maxH,
+ scaleY: component.v / frame.maxV,
+ blocksPerLine: component.blocksPerLine,
+ blocksPerColumn: component.blocksPerColumn,
+ });
+ }
+
+ console.log("components");
+ console.log(this.components);
+ this.numComponents = this.components.length;
+ },
+
+ _getLinearizedBlockData: function getLinearizedBlockData(width, height) {
+ var scaleX = this.width / width, scaleY = this.height / height;
+
+ var component, componentScaleX, componentScaleY, blocksPerScanline;
+ var x, y, i, j, k;
+ var index;
+ var offset = 0;
+ var output;
+ var numComponents = this.components.length;
+ var dataLength = width * height * numComponents;
+ var data = new Uint8Array(dataLength);
+ var xScaleBlockOffset = new Uint32Array(width);
+ var mask3LSB = 0xfffffff8; // used to clear the 3 LSBs
+
+ for (i = 0; i < numComponents; i++) {
+ component = this.components[i];
+ componentScaleX = component.scaleX * scaleX;
+ componentScaleY = component.scaleY * scaleY;
+ offset = i;
+ output = component.output;
+ blocksPerScanline = (component.blocksPerLine + 1) << 3;
+ // precalculate the xScaleBlockOffset
+ for (x = 0; x < width; x++) {
+ j = 0 | (x * componentScaleX);
+ xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7);
+ }
+ // linearize the blocks of the component
+ for (y = 0; y < height; y++) {
+ j = 0 | (y * componentScaleY);
+ index = blocksPerScanline * (j & mask3LSB) | ((j & 7) << 3);
+ for (x = 0; x < width; x++) {
+ data[offset] = output[index + xScaleBlockOffset[x]];
+ offset += numComponents;
+ }
+ }
+ }
+
+ // decodeTransform contains pairs of multiplier (-256..256) and additive
+ var transform = this.decodeTransform;
+ if (transform) {
+ for (i = 0; i < dataLength;) {
+ for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) {
+ data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1];
+ }
+ }
+ }
+ return data;
+ },
+
+ _isColorConversionNeeded: function isColorConversionNeeded() {
+ if (this.adobe && this.adobe.transformCode) {
+ // The adobe transform marker overrides any previous setting
+ return true;
+ } else if (this.numComponents === 3) {
+ if (!this.adobe && this.colorTransform === 0) {
+ // If the Adobe transform marker is not present and the image
+ // dictionary has a 'ColorTransform' entry, explicitly set to `0`,
+ // then the colours should *not* be transformed.
+ return false;
+ }
+ return true;
+ }
+ // `this.numComponents !== 3`
+ if (!this.adobe && this.colorTransform === 1) {
+ // If the Adobe transform marker is not present and the image
+ // dictionary has a 'ColorTransform' entry, explicitly set to `1`,
+ // then the colours should be transformed.
+ return true;
+ }
+ return false;
+ },
+
+ _convertYccToRgb: function convertYccToRgb(data) {
+ var Y, Cb, Cr;
+ for (var i = 0, length = data.length; i < length; i += 3) {
+ Y = data[i ];
+ Cb = data[i + 1];
+ Cr = data[i + 2];
+ data[i ] = clamp0to255(Y - 179.456 + 1.402 * Cr);
+ data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr);
+ data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb);
+ }
+ return data;
+ },
+
+ _convertYcckToRgb: function convertYcckToRgb(data) {
+ var Y, Cb, Cr, k;
+ var offset = 0;
+ for (var i = 0, length = data.length; i < length; i += 4) {
+ Y = data[i];
+ Cb = data[i + 1];
+ Cr = data[i + 2];
+ k = data[i + 3];
+
+ var r = -122.67195406894 +
+ Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr -
+ 5.4080610064599e-5 * Y + 0.00048449797120281 * k -
+ 0.154362151871126) +
+ Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y -
+ 0.00477271405408747 * k + 1.53380253221734) +
+ Y * (0.000961250184130688 * Y - 0.00266257332283933 * k +
+ 0.48357088451265) +
+ k * (-0.000336197177618394 * k + 0.484791561490776);
+
+ var g = 107.268039397724 +
+ Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr +
+ 0.000659397001245577 * Y + 0.000426105652938837 * k -
+ 0.176491792462875) +
+ Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y +
+ 0.000770482631801132 * k - 0.151051492775562) +
+ Y * (0.00126935368114843 * Y - 0.00265090189010898 * k +
+ 0.25802910206845) +
+ k * (-0.000318913117588328 * k - 0.213742400323665);
+
+ var b = -20.810012546947 +
+ Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr +
+ 0.0020741088115012 * Y - 0.00288260236853442 * k +
+ 0.814272968359295) +
+ Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y +
+ 0.000560833691242812 * k - 0.195152027534049) +
+ Y * (0.00174418132927582 * Y - 0.00255243321439347 * k +
+ 0.116935020465145) +
+ k * (-0.000343531996510555 * k + 0.24165260232407);
+
+ data[offset++] = clamp0to255(r);
+ data[offset++] = clamp0to255(g);
+ data[offset++] = clamp0to255(b);
+ }
+ return data;
+ },
+
+ _convertYcckToCmyk: function convertYcckToCmyk(data) {
+ var Y, Cb, Cr;
+ for (var i = 0, length = data.length; i < length; i += 4) {
+ Y = data[i];
+ Cb = data[i + 1];
+ Cr = data[i + 2];
+ data[i ] = clamp0to255(434.456 - Y - 1.402 * Cr);
+ data[i + 1] = clamp0to255(119.541 - Y + 0.344 * Cb + 0.714 * Cr);
+ data[i + 2] = clamp0to255(481.816 - Y - 1.772 * Cb);
+ // K in data[i + 3] is unchanged
+ }
+ return data;
+ },
+
+ _convertCmykToRgb: function convertCmykToRgb(data) {
+ var c, m, y, k;
+ var offset = 0;
+ var min = -255 * 255 * 255;
+ var scale = 1 / 255 / 255;
+ for (var i = 0, length = data.length; i < length; i += 4) {
+ c = data[i];
+ m = data[i + 1];
+ y = data[i + 2];
+ k = data[i + 3];
+
+ var r =
+ c * (-4.387332384609988 * c + 54.48615194189176 * m +
+ 18.82290502165302 * y + 212.25662451639585 * k -
+ 72734.4411664936) +
+ m * (1.7149763477362134 * m - 5.6096736904047315 * y -
+ 17.873870861415444 * k - 1401.7366389350734) +
+ y * (-2.5217340131683033 * y - 21.248923337353073 * k +
+ 4465.541406466231) -
+ k * (21.86122147463605 * k + 48317.86113160301);
+ var g =
+ c * (8.841041422036149 * c + 60.118027045597366 * m +
+ 6.871425592049007 * y + 31.159100130055922 * k -
+ 20220.756542821975) +
+ m * (-15.310361306967817 * m + 17.575251261109482 * y +
+ 131.35250912493976 * k - 48691.05921601825) +
+ y * (4.444339102852739 * y + 9.8632861493405 * k -
+ 6341.191035517494) -
+ k * (20.737325471181034 * k + 47890.15695978492);
+ var b =
+ c * (0.8842522430003296 * c + 8.078677503112928 * m +
+ 30.89978309703729 * y - 0.23883238689178934 * k -
+ 3616.812083916688) +
+ m * (10.49593273432072 * m + 63.02378494754052 * y +
+ 50.606957656360734 * k - 28620.90484698408) +
+ y * (0.03296041114873217 * y + 115.60384449646641 * k -
+ 49363.43385999684) -
+ k * (22.33816807309886 * k + 45932.16563550634);
+
+ data[offset++] = r >= 0 ? 255 : r <= min ? 0 : 255 + r * scale | 0;
+ data[offset++] = g >= 0 ? 255 : g <= min ? 0 : 255 + g * scale | 0;
+ data[offset++] = b >= 0 ? 255 : b <= min ? 0 : 255 + b * scale | 0;
+ }
+ return data;
+ },
+
+ getData: function getData(width, height, forceRGBoutput) {
+ if (this.numComponents > 4) {
+ error('JPEG error: Unsupported color mode');
+ }
+ // type of data: Uint8Array(width * height * numComponents)
+ var data = this._getLinearizedBlockData(width, height);
+
+ if (this.numComponents === 1 && forceRGBoutput) {
+ var dataLength = data.length;
+ var rgbData = new Uint8Array(dataLength * 3);
+ var offset = 0;
+ for (var i = 0; i < dataLength; i++) {
+ var grayColor = data[i];
+ rgbData[offset++] = grayColor;
+ rgbData[offset++] = grayColor;
+ rgbData[offset++] = grayColor;
+ }
+ return rgbData;
+ } else if (this.numComponents === 3 && this._isColorConversionNeeded()) {
+ return this._convertYccToRgb(data);
+ } else if (this.numComponents === 4) {
+ if (this._isColorConversionNeeded()) {
+ if (forceRGBoutput) {
+ return this._convertYcckToRgb(data);
+ }
+ return this._convertYcckToCmyk(data);
+ } else if (forceRGBoutput) {
+ return this._convertCmykToRgb(data);
+ }
+ }
+ return data;
+ },
+ };
+
+ return JpegImage;
+})();
+
+// export {
+// JpegImage,
+// };
diff --git a/tests/ImageSharp.Tests/xunit.runner.json b/tests/ImageSharp.Tests/xunit.runner.json
index df1c3d50d..cbaa8f432 100644
--- a/tests/ImageSharp.Tests/xunit.runner.json
+++ b/tests/ImageSharp.Tests/xunit.runner.json
@@ -1,3 +1,4 @@
{
- "methodDisplay": "method"
+ "methodDisplay": "method",
+ "diagnosticMessages": true
}
\ No newline at end of file