diff --git a/src/ImageSharp/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs
new file mode 100644
index 0000000000..e1d51da8d4
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+// Uncomment this for verbose profiler results:
+// #define PROFILING
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Global inlining options. Helps temporarily disable inling for better profiler output.
+ ///
+ internal static class InliningOptions
+ {
+#if PROFILING
+ public const MethodImplOptions ShortMethod = 0;
+#else
+ public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining;
+#endif
+ public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
new file mode 100644
index 0000000000..c7f3666604
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg
+{
+ internal static class JpegThrowHelper
+ {
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowImageFormatException(string errorMessage)
+ {
+ throw new ImageFormatException(errorMessage);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowBadHuffmanCode()
+ {
+ throw new ImageFormatException("Bad Huffman code.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs
new file mode 100644
index 0000000000..6cb0d6dfe5
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
+{
+ ///
+ /// The collection of lookup tables used for fast AC entropy scan decoding.
+ ///
+ internal sealed class FastACTables : IDisposable
+ {
+ private Buffer2D tables;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator used to allocate memory for image processing operations.
+ public FastACTables(MemoryAllocator memoryAllocator)
+ {
+ this.tables = memoryAllocator.AllocateClean2D(512, 4);
+ }
+
+ ///
+ /// Gets the representing the table at the index in the collection.
+ ///
+ /// The table index.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan GetTableSpan(int index)
+ {
+ return this.tables.GetRowSpan(index);
+ }
+
+ ///
+ /// Gets a reference to the first element of the AC table indexed by
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref short GetAcTableReference(PdfJsFrameComponent component)
+ {
+ return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0];
+ }
+
+ ///
+ /// Builds a lookup table for fast AC entropy scan decoding.
+ ///
+ /// The table index.
+ /// The collection of AC Huffman tables.
+ public void BuildACTableLut(int index, PdfJsHuffmanTables acHuffmanTables)
+ {
+ const int FastBits = ScanDecoder.FastBits;
+ Span fastAC = this.tables.GetRowSpan(index);
+ ref PdfJsHuffmanTable huffman = ref acHuffmanTables[index];
+
+ int i;
+ for (i = 0; i < (1 << FastBits); i++)
+ {
+ byte fast = huffman.Lookahead[i];
+ fastAC[i] = 0;
+ if (fast < byte.MaxValue)
+ {
+ int rs = huffman.Values[fast];
+ int run = (rs >> 4) & 15;
+ int magbits = rs & 15;
+ int len = huffman.Sizes[fast];
+
+ if (magbits > 0 && len + magbits <= FastBits)
+ {
+ // Magnitude code followed by receive_extend code
+ int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits);
+ int m = 1 << (magbits - 1);
+ if (k < m)
+ {
+ k += (int)((~0U << magbits) + 1);
+ }
+
+ // if the result is small enough, we can fit it in fastAC table
+ if (k >= -128 && k <= 127)
+ {
+ fastAC[i] = (short)((k * 256) + (run * 16) + (len + magbits));
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.tables?.Dispose();
+ this.tables = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs
new file mode 100644
index 0000000000..c509903c98
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
+{
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct FixedByteBuffer512
+ {
+ public fixed byte Data[1 << ScanDecoder.FastBits];
+
+ public byte this[int idx]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ ref byte self = ref Unsafe.As(ref this);
+ return Unsafe.Add(ref self, idx);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs
similarity index 84%
rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs
rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs
index 2c16a918f4..b304dbf8c2 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs
@@ -7,16 +7,16 @@
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
[StructLayout(LayoutKind.Sequential)]
- internal unsafe struct FixedInt16Buffer256
+ internal unsafe struct FixedInt16Buffer257
{
- public fixed short Data[256];
+ public fixed short Data[257];
public short this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
- ref short self = ref Unsafe.As(ref this);
+ ref short self = ref Unsafe.As(ref this);
return Unsafe.Add(ref self, idx);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs
similarity index 70%
rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs
rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs
index 51381cb27a..f8507ec47c 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs
@@ -7,16 +7,16 @@
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
[StructLayout(LayoutKind.Sequential)]
- internal unsafe struct FixedInt64Buffer18
+ internal unsafe struct FixedInt32Buffer18
{
- public fixed long Data[18];
+ public fixed int Data[18];
- public long this[int idx]
+ public int this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
- ref long self = ref Unsafe.As(ref this);
+ ref int self = ref Unsafe.As(ref this);
return Unsafe.Add(ref self, idx);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs
similarity index 70%
rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs
rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs
index 20d4b77336..9b076d9daa 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs
@@ -7,16 +7,16 @@
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{
[StructLayout(LayoutKind.Sequential)]
- internal unsafe struct FixedInt16Buffer18
+ internal unsafe struct FixedUInt32Buffer18
{
- public fixed short Data[18];
+ public fixed uint Data[18];
- public short this[int idx]
+ public uint this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
- ref short self = ref Unsafe.As(ref this);
+ ref uint self = ref Unsafe.As(ref this);
return Unsafe.Add(ref self, idx);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
index eefe8b97ea..7501b0d83c 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
@@ -129,7 +129,7 @@ public void Init()
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}
- this.SpectralBlocks = this.memoryAllocator.Allocate2D(blocksPerColumnForMcu, blocksPerLineForMcu + 1, true);
+ this.SpectralBlocks = this.memoryAllocator.AllocateClean2D(blocksPerColumnForMcu, blocksPerLineForMcu + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -144,5 +144,13 @@ public int GetBlockBufferOffset(int row, int col)
{
return 64 * (((this.WidthInBlocks + 1) * row) + col);
}
+
+ // TODO: we need consistence in (row, col) VS (col, row) ordering
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref short GetBlockDataReference(int row, int col)
+ {
+ ref Block8x8 blockRef = ref this.GetBlockReference(col, row);
+ return ref Unsafe.As(ref blockRef);
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs
index 2789f0cc00..15ae56331c 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs
@@ -17,163 +17,114 @@ internal unsafe struct PdfJsHuffmanTable
///
/// Gets the max code array
///
- public FixedInt64Buffer18 MaxCode;
+ public FixedUInt32Buffer18 MaxCode;
///
/// Gets the value offset array
///
- public FixedInt16Buffer18 ValOffset;
+ public FixedInt32Buffer18 ValOffset;
///
/// Gets the huffman value array
///
- public FixedByteBuffer256 HuffVal;
+ public FixedByteBuffer256 Values;
///
/// Gets the lookahead array
///
- public FixedInt16Buffer256 Lookahead;
+ public FixedByteBuffer512 Lookahead;
+
+ ///
+ /// Gets the sizes array
+ ///
+ public FixedInt16Buffer257 Sizes;
///
/// Initializes a new instance of the struct.
///
/// The to use for buffer allocations.
- /// The code lengths
+ /// The code lengths
/// The huffman values
- public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan lengths, ReadOnlySpan values)
+ public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan count, ReadOnlySpan values)
{
- const int length = 257;
- using (IBuffer huffsize = memoryAllocator.Allocate(length))
- using (IBuffer huffcode = memoryAllocator.Allocate(length))
+ const int Length = 257;
+ using (IBuffer huffcode = memoryAllocator.Allocate(Length))
{
- ref short huffsizeRef = ref MemoryMarshal.GetReference(huffsize.GetSpan());
ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan());
- GenerateSizeTable(lengths, ref huffsizeRef);
- GenerateCodeTable(ref huffsizeRef, ref huffcodeRef, length);
- this.GenerateDecoderTables(lengths, ref huffcodeRef);
- this.GenerateLookaheadTables(lengths, values, ref huffcodeRef);
- }
-
- fixed (byte* huffValRef = this.HuffVal.Data)
- {
- var huffValSpan = new Span(huffValRef, 256);
-
- values.CopyTo(huffValSpan);
- }
- }
-
- ///
- /// Figure C.1: make table of Huffman code length for each symbol
- ///
- /// The code lengths
- /// The huffman size span ref
- private static void GenerateSizeTable(ReadOnlySpan lengths, ref short huffsizeRef)
- {
- short index = 0;
- for (short l = 1; l <= 16; l++)
- {
- byte i = lengths[l];
- for (short j = 0; j < i; j++)
- {
- Unsafe.Add(ref huffsizeRef, index) = l;
- index++;
- }
- }
-
- Unsafe.Add(ref huffsizeRef, index) = 0;
- }
-
- ///
- /// Figure C.2: generate the codes themselves
- ///
- /// The huffman size span ref
- /// The huffman code span ref
- /// The length of the huffsize span
- private static void GenerateCodeTable(ref short huffsizeRef, ref short huffcodeRef, int length)
- {
- short k = 0;
- short si = huffsizeRef;
- short code = 0;
- for (short i = 0; i < length; i++)
- {
- while (Unsafe.Add(ref huffsizeRef, k) == si)
- {
- Unsafe.Add(ref huffcodeRef, k) = code;
- code++;
- k++;
- }
-
- code <<= 1;
- si++;
- }
- }
-
- ///
- /// Figure F.15: generate decoding tables for bit-sequential decoding
- ///
- /// The code lengths
- /// The huffman code span ref
- private void GenerateDecoderTables(ReadOnlySpan lengths, ref short huffcodeRef)
- {
- fixed (short* valOffsetRef = this.ValOffset.Data)
- fixed (long* maxcodeRef = this.MaxCode.Data)
- {
- short bitcount = 0;
- for (int i = 1; i <= 16; i++)
+ // Figure C.1: make table of Huffman code length for each symbol
+ fixed (short* sizesRef = this.Sizes.Data)
{
- if (lengths[i] != 0)
+ short x = 0;
+ for (short i = 1; i < 17; i++)
{
- // valOffsetRef[l] = huffcodeRef[] index of 1st symbol of code length i, minus the minimum code of length i
- valOffsetRef[i] = (short)(bitcount - Unsafe.Add(ref huffcodeRef, bitcount));
- bitcount += lengths[i];
- maxcodeRef[i] = Unsafe.Add(ref huffcodeRef, bitcount - 1); // maximum code of length i
- }
- else
- {
- maxcodeRef[i] = -1; // -1 if no codes of this length
+ byte l = count[i];
+ for (short j = 0; j < l; j++)
+ {
+ sizesRef[x] = i;
+ x++;
+ }
}
- }
- valOffsetRef[17] = 0;
- maxcodeRef[17] = 0xFFFFFL;
- }
- }
+ sizesRef[x] = 0;
- ///
- /// Generates lookup tables to speed up decoding
- ///
- /// The code lengths
- /// The huffman value array
- /// The huffman code span ref
- private void GenerateLookaheadTables(ReadOnlySpan lengths, ReadOnlySpan huffval, ref short huffcodeRef)
- {
- // TODO: This generation code matches the libJpeg code but the lookahead table is not actually used yet.
- // To use it we need to implement fast lookup path in PdfJsScanDecoder.DecodeHuffman
- // This should yield much faster scan decoding as usually, more than 95% of the Huffman codes
- // will be 8 or fewer bits long and can be handled without looping.
- fixed (short* lookaheadRef = this.Lookahead.Data)
- {
- var lookaheadSpan = new Span(lookaheadRef, 256);
+ // Figure C.2: generate the codes themselves
+ int k = 0;
+ fixed (int* valOffsetRef = this.ValOffset.Data)
+ fixed (uint* maxcodeRef = this.MaxCode.Data)
+ {
+ uint code = 0;
+ int j;
+ for (j = 1; j < 17; j++)
+ {
+ // Compute delta to add to code to compute symbol id.
+ valOffsetRef[j] = (int)(k - code);
+ if (sizesRef[k] == j)
+ {
+ while (sizesRef[k] == j)
+ {
+ Unsafe.Add(ref huffcodeRef, k++) = (short)code++;
+ }
+ }
+
+ // Figure F.15: generate decoding tables for bit-sequential decoding.
+ // Compute largest code + 1 for this size. preshifted as need later.
+ maxcodeRef[j] = code << (16 - j);
+ code <<= 1;
+ }
- lookaheadSpan.Fill(2034); // 9 << 8;
+ maxcodeRef[j] = 0xFFFFFFFF;
+ }
- int p = 0;
- for (int l = 1; l <= 8; l++)
- {
- for (int i = 1; i <= lengths[l]; i++, p++)
+ // Generate non-spec lookup tables to speed up decoding.
+ fixed (byte* lookaheadRef = this.Lookahead.Data)
{
- // l = current code's length, p = its index in huffcode[] & huffval[].
- // Generate left-justified code followed by all possible bit sequences
- int lookBits = Unsafe.Add(ref huffcodeRef, p) << (8 - l);
- for (int ctr = 1 << (8 - l); ctr > 0; ctr--)
+ const int FastBits = ScanDecoder.FastBits;
+ var fast = new Span(lookaheadRef, 1 << FastBits);
+ fast.Fill(0xFF); // Flag for non-accelerated
+
+ for (int i = 0; i < k; i++)
{
- lookaheadRef[lookBits] = (short)((l << 8) | huffval[p]);
- lookBits++;
+ int s = sizesRef[i];
+ if (s <= ScanDecoder.FastBits)
+ {
+ int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - s);
+ int m = 1 << (FastBits - s);
+ for (int j = 0; j < m; j++)
+ {
+ fast[c + j] = (byte)i;
+ }
+ }
}
}
}
}
+
+ fixed (byte* huffValRef = this.Values.Data)
+ {
+ var huffValSpan = new Span(huffValRef, 256);
+ values.CopyTo(huffValSpan);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs
index 3a559bb864..5cbde2b88c 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs
@@ -17,7 +17,7 @@ internal sealed class PdfJsHuffmanTables
/// Gets or sets the table at the given index.
///
/// The index
- /// The
+ /// The
public ref PdfJsHuffmanTable this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
deleted file mode 100644
index c9f1c555d8..0000000000
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
+++ /dev/null
@@ -1,866 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-
-#if DEBUG
-using System.Diagnostics;
-#endif
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Formats.Jpeg.Components;
-using SixLabors.Memory;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
-{
- ///
- /// Provides the means to decode a spectral scan
- ///
- internal struct PdfJsScanDecoder
- {
- private ZigZag dctZigZag;
-
- private byte[] markerBuffer;
-
- private int mcuToRead;
-
- private int mcusPerLine;
-
- private int mcu;
-
- private int bitsData;
-
- private int bitsCount;
-
- 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(
- PdfJsFrame frame,
- DoubleBufferedStreamReader stream,
- PdfJsHuffmanTables dcHuffmanTables,
- PdfJsHuffmanTables acHuffmanTables,
- PdfJsFrameComponent[] components,
- int componentIndex,
- int componentsLength,
- ushort resetInterval,
- int spectralStart,
- int spectralEnd,
- int successivePrev,
- int successive)
- {
- this.dctZigZag = ZigZag.CreateUnzigTable();
- 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;
- this.mcusPerLine = frame.McusPerLine;
-
- this.mcu = 0;
- int mcuExpected;
- if (componentsLength == 1)
- {
- mcuExpected = components[this.compIndex].WidthInBlocks * components[this.compIndex].HeightInBlocks;
- }
- else
- {
- mcuExpected = this.mcusPerLine * frame.McusPerColumn;
- }
-
- while (this.mcu < mcuExpected)
- {
- // Reset interval stuff
- this.mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - this.mcu, resetInterval) : mcuExpected;
- for (int i = 0; i < components.Length; i++)
- {
- PdfJsFrameComponent c = components[i];
- c.DcPredictor = 0;
- }
-
- this.eobrun = 0;
-
- if (!progressive)
- {
- this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, stream);
- }
- else
- {
- bool isAc = this.specStart != 0;
- bool isFirst = successivePrev == 0;
- PdfJsHuffmanTables huffmanTables = isAc ? acHuffmanTables : dcHuffmanTables;
- this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, stream);
- }
-
- // Reset
- // TODO: I do not understand why these values are reset? We should surely be tracking the bits across mcu's?
- this.bitsCount = 0;
- this.bitsData = 0;
- this.unexpectedMarkerReached = false;
-
- // Some images include more scan blocks than expected, skip past those and
- // attempt to find the next valid marker
- PdfJsFileMarker fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
- byte marker = fileMarker.Marker;
-
- // RSTn - We've already 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;
- }
-
-#if DEBUG
- Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
-#endif
- }
- }
-
- private void DecodeScanBaseline(
- PdfJsHuffmanTables dcHuffmanTables,
- PdfJsHuffmanTables acHuffmanTables,
- PdfJsFrameComponent[] components,
- int componentsLength,
- DoubleBufferedStreamReader stream)
- {
- if (componentsLength == 1)
- {
- PdfJsFrameComponent component = components[this.compIndex];
- ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan()));
- ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
- ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
-
- for (int n = 0; n < this.mcuToRead; n++)
- {
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
- {
- continue;
- }
-
- this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, stream);
- this.mcu++;
- }
- }
- else
- {
- for (int n = 0; n < this.mcuToRead; n++)
- {
- for (int i = 0; i < componentsLength; i++)
- {
- PdfJsFrameComponent component = components[i];
- ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan()));
- ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId];
- ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId];
- int h = component.HorizontalSamplingFactor;
- int v = component.VerticalSamplingFactor;
-
- 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, component, ref blockDataRef, j, k, stream);
- }
- }
- }
-
- this.mcu++;
- }
- }
- }
-
- private void DecodeScanProgressive(
- PdfJsHuffmanTables huffmanTables,
- bool isAC,
- bool isFirst,
- PdfJsFrameComponent[] components,
- int componentsLength,
- DoubleBufferedStreamReader stream)
- {
- if (componentsLength == 1)
- {
- PdfJsFrameComponent component = components[this.compIndex];
- ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan()));
- ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId];
-
- for (int n = 0; n < this.mcuToRead; n++)
- {
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
- {
- continue;
- }
-
- if (isAC)
- {
- if (isFirst)
- {
- this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, stream);
- }
- else
- {
- this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, stream);
- }
- }
- else
- {
- if (isFirst)
- {
- this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, stream);
- }
- else
- {
- this.DecodeBlockDCSuccessive(component, ref blockDataRef, stream);
- }
- }
-
- this.mcu++;
- }
- }
- else
- {
- for (int n = 0; n < this.mcuToRead; n++)
- {
- for (int i = 0; i < componentsLength; i++)
- {
- PdfJsFrameComponent component = components[i];
- ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan()));
- ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId];
- int h = component.HorizontalSamplingFactor;
- int v = component.VerticalSamplingFactor;
-
- for (int j = 0; j < v; j++)
- {
- for (int k = 0; k < h; k++)
- {
- // No need to continue here.
- if (this.endOfStreamReached || this.unexpectedMarkerReached)
- {
- break;
- }
-
- if (isAC)
- {
- if (isFirst)
- {
- this.DecodeMcuACFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream);
- }
- else
- {
- this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, j, k, stream);
- }
- }
- else
- {
- if (isFirst)
- {
- this.DecodeMcuDCFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream);
- }
- else
- {
- this.DecodeMcuDCSuccessive(component, ref blockDataRef, j, k, stream);
- }
- }
- }
- }
- }
-
- this.mcu++;
- }
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
- {
- int blockRow = this.mcu / component.WidthInBlocks;
- int blockCol = this.mcu % component.WidthInBlocks;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
- {
- int mcuRow = this.mcu / this.mcusPerLine;
- int mcuCol = this.mcu % this.mcusPerLine;
- int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
- int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
- {
- int blockRow = this.mcu / component.WidthInBlocks;
- int blockCol = this.mcu % component.WidthInBlocks;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
- {
- int mcuRow = this.mcu / this.mcusPerLine;
- int mcuCol = this.mcu % this.mcusPerLine;
- int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
- int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
- {
- int blockRow = this.mcu / component.WidthInBlocks;
- int blockCol = this.mcu % component.WidthInBlocks;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
- {
- int mcuRow = this.mcu / this.mcusPerLine;
- int mcuCol = this.mcu % this.mcusPerLine;
- int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
- int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
- {
- int blockRow = this.mcu / component.WidthInBlocks;
- int blockCol = this.mcu % component.WidthInBlocks;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
- {
- int mcuRow = this.mcu / this.mcusPerLine;
- int mcuCol = this.mcu % this.mcusPerLine;
- int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
- int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream)
- {
- int blockRow = this.mcu / component.WidthInBlocks;
- int blockCol = this.mcu % component.WidthInBlocks;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream)
- {
- int mcuRow = this.mcu / this.mcusPerLine;
- int mcuCol = this.mcu % this.mcusPerLine;
- int blockRow = (mcuRow * component.VerticalSamplingFactor) + row;
- int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col;
- int offset = component.GetBlockBufferOffset(blockRow, blockCol);
- this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryReadBit(DoubleBufferedStreamReader stream, out int bit)
- {
- if (this.bitsCount == 0)
- {
- if (!this.TryFillBits(stream))
- {
- bit = 0;
- return false;
- }
- }
-
- this.bitsCount--;
- bit = (this.bitsData >> this.bitsCount) & 1;
- return true;
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private bool TryFillBits(DoubleBufferedStreamReader stream)
- {
- // TODO: Read more then 1 byte at a time.
- // In LibJpegTurbo this is be 25 bits (32-7) but I cannot get this to work
- // for some images, I'm assuming because I am crossing MCU boundaries and not maintining the correct buffer state.
- const int MinGetBits = 7;
-
- if (!this.unexpectedMarkerReached)
- {
- // Attempt to load to the minimum bit count.
- while (this.bitsCount < MinGetBits)
- {
- int c = stream.ReadByte();
-
- switch (c)
- {
- case -0x1:
-
- // We've encountered the end of the file stream which means there's no EOI marker in the image.
- this.endOfStreamReached = true;
- return false;
-
- case JpegConstants.Markers.XFF:
- int nextByte = stream.ReadByte();
-
- if (nextByte == -0x1)
- {
- this.endOfStreamReached = true;
- return false;
- }
-
- if (nextByte != 0)
- {
-#if DEBUG
- Debug.WriteLine($"DecodeScan - Unexpected marker {(c << 8) | nextByte:X} at {stream.Position}");
-#endif
-
- // We've encountered an unexpected marker. Reverse the stream and exit.
- this.unexpectedMarkerReached = true;
- stream.Position -= 2;
-
- // TODO: double check we need this.
- // Fill buffer with zero bits.
- if (this.bitsCount == 0)
- {
- this.bitsData <<= MinGetBits;
- this.bitsCount = MinGetBits;
- }
-
- return true;
- }
-
- break;
- }
-
- // OK, load the next byte into bitsData
- this.bitsData = (this.bitsData << 8) | c;
- this.bitsCount += 8;
- }
- }
-
- return true;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int PeekBits(int count)
- {
- return this.bitsData >> (this.bitsCount - count) & ((1 << count) - 1);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DropBits(int count)
- {
- this.bitsCount -= count;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryDecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream, out short value)
- {
- value = -1;
-
- // TODO: Implement fast Huffman decoding.
- // In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream
- // Then a LUT is used to avoid the loop when decoding the Huffman value.
- // using 3 methods: FillBits, PeekBits, and DropBits.
- // The LUT has been ported from LibJpegTurbo as has this code but it doesn't work.
- // this.TryFillBits(stream);
- //
- // const int LookAhead = 8;
- // int look = this.PeekBits(LookAhead);
- // look = tree.Lookahead[look];
- // int bits = look >> LookAhead;
- //
- // if (bits <= LookAhead)
- // {
- // this.DropBits(bits);
- // value = (short)(look & ((1 << LookAhead) - 1));
- // return true;
- // }
- if (!this.TryReadBit(stream, out int bit))
- {
- return false;
- }
-
- short code = (short)bit;
-
- // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
- int i = 1;
-
- while (code > tree.MaxCode[i])
- {
- if (!this.TryReadBit(stream, out bit))
- {
- return false;
- }
-
- code <<= 1;
- code |= (short)bit;
- i++;
- }
-
- int j = tree.ValOffset[i];
- value = tree.HuffVal[(j + code) & 0xFF];
- return true;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryReceive(int length, DoubleBufferedStreamReader stream, out int value)
- {
- value = 0;
- while (length > 0)
- {
- if (!this.TryReadBit(stream, out int bit))
- {
- return false;
- }
-
- value = (value << 1) | bit;
- length--;
- }
-
- return true;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryReceiveAndExtend(int length, DoubleBufferedStreamReader stream, out int value)
- {
- if (length == 1)
- {
- if (!this.TryReadBit(stream, out value))
- {
- return false;
- }
-
- value = value == 1 ? 1 : -1;
- }
- else
- {
- if (!this.TryReceive(length, stream, out value))
- {
- return false;
- }
-
- if (value < 1 << (length - 1))
- {
- value += (-1 << length) + 1;
- }
- }
-
- return true;
- }
-
- private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
- {
- if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
- {
- return;
- }
-
- int diff = 0;
- if (t != 0)
- {
- if (!this.TryReceiveAndExtend(t, stream, out diff))
- {
- return;
- }
- }
-
- Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff);
-
- int k = 1;
- while (k < 64)
- {
- if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
- {
- return;
- }
-
- int s = rs & 15;
- int r = rs >> 4;
-
- if (s == 0)
- {
- if (r < 15)
- {
- break;
- }
-
- k += 16;
- continue;
- }
-
- k += r;
-
- byte z = this.dctZigZag[k];
-
- if (!this.TryReceiveAndExtend(s, stream, out int re))
- {
- return;
- }
-
- Unsafe.Add(ref blockDataRef, offset + z) = (short)re;
- k++;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream)
- {
- if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
- {
- return;
- }
-
- int diff = 0;
- if (t != 0)
- {
- if (!this.TryReceiveAndExtend(t, stream, out diff))
- {
- return;
- }
- }
-
- Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff << this.successiveState);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream)
- {
- if (!this.TryReadBit(stream, out int bit))
- {
- return;
- }
-
- Unsafe.Add(ref blockDataRef, offset) |= (short)(bit << this.successiveState);
- }
-
- private void DecodeACFirst(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
- {
- if (this.eobrun > 0)
- {
- this.eobrun--;
- return;
- }
-
- int k = this.specStart;
- int e = this.specEnd;
- while (k <= e)
- {
- if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
- {
- return;
- }
-
- int s = rs & 15;
- int r = rs >> 4;
-
- if (s == 0)
- {
- if (r < 15)
- {
- if (!this.TryReceive(r, stream, out int eob))
- {
- return;
- }
-
- this.eobrun = eob + (1 << r) - 1;
- break;
- }
-
- k += 16;
- continue;
- }
-
- k += r;
-
- byte z = this.dctZigZag[k];
-
- if (!this.TryReceiveAndExtend(s, stream, out int v))
- {
- return;
- }
-
- Unsafe.Add(ref blockDataRef, offset + z) = (short)(v * (1 << this.successiveState));
- k++;
- }
- }
-
- private void DecodeACSuccessive(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
- {
- int k = this.specStart;
- int e = this.specEnd;
- int r = 0;
-
- while (k <= e)
- {
- int offsetZ = offset + this.dctZigZag[k];
- ref short blockOffsetZRef = ref Unsafe.Add(ref blockDataRef, offsetZ);
- int sign = blockOffsetZRef < 0 ? -1 : 1;
-
- switch (this.successiveACState)
- {
- case 0: // Initial state
-
- if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
- {
- return;
- }
-
- int s = rs & 15;
- r = rs >> 4;
- if (s == 0)
- {
- if (r < 15)
- {
- if (!this.TryReceive(r, stream, out int eob))
- {
- return;
- }
-
- this.eobrun = eob + (1 << r);
- this.successiveACState = 4;
- }
- else
- {
- r = 16;
- this.successiveACState = 1;
- }
- }
- else
- {
- if (s != 1)
- {
- throw new ImageFormatException("Invalid ACn encoding");
- }
-
- if (!this.TryReceiveAndExtend(s, stream, out int v))
- {
- return;
- }
-
- this.successiveACNextValue = v;
- this.successiveACState = r > 0 ? 2 : 3;
- }
-
- continue;
- case 1: // Skipping r zero items
- case 2:
- if (blockOffsetZRef != 0)
- {
- if (!this.TryReadBit(stream, out int bit))
- {
- return;
- }
-
- blockOffsetZRef += (short)(sign * (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 (blockOffsetZRef != 0)
- {
- if (!this.TryReadBit(stream, out int bit))
- {
- return;
- }
-
- blockOffsetZRef += (short)(sign * (bit << this.successiveState));
- }
- else
- {
- blockOffsetZRef = (short)(this.successiveACNextValue << this.successiveState);
- this.successiveACState = 0;
- }
-
- break;
- case 4: // Eob
- if (blockOffsetZRef != 0)
- {
- if (!this.TryReadBit(stream, out int bit))
- {
- return;
- }
-
- blockOffsetZRef += (short)(sign * (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/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs
new file mode 100644
index 0000000000..8575bac69e
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs
@@ -0,0 +1,958 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Formats.Jpeg.Components;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
+{
+ ///
+ /// Decodes the Huffman encoded spectral scan.
+ /// Originally ported from
+ /// with additional fixes for both performance and common encoding errors.
+ ///
+ internal class ScanDecoder
+ {
+ // The number of bits that can be read via a LUT.
+ public const int FastBits = 9;
+
+ // LUT mask for n rightmost bits. Bmask[n] = (1 << n) - 1
+ private static readonly uint[] Bmask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 };
+
+ // LUT Bias[n] = (-1 << n) + 1
+ private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 };
+
+ private readonly PdfJsFrame frame;
+ private readonly PdfJsHuffmanTables dcHuffmanTables;
+ private readonly PdfJsHuffmanTables acHuffmanTables;
+ private readonly FastACTables fastACTables;
+
+ private readonly DoubleBufferedStreamReader stream;
+ private readonly PdfJsFrameComponent[] components;
+ private readonly ZigZag dctZigZag;
+
+ // The restart interval.
+ private readonly int restartInterval;
+
+ // The current component index.
+ private readonly int componentIndex;
+
+ // The number of interleaved components.
+ private readonly int componentsLength;
+
+ // The spectral selection start.
+ private readonly int spectralStart;
+
+ // The spectral selection end.
+ private readonly int spectralEnd;
+
+ // The successive approximation high bit end.
+ private readonly int successiveHigh;
+
+ // The successive approximation low bit end.
+ private readonly int successiveLow;
+
+ // The number of valid bits left to read in the buffer.
+ private int codeBits;
+
+ // The entropy encoded code buffer.
+ private uint codeBuffer;
+
+ // Whether there is more data to pull from the stream for the current mcu.
+ private bool nomore;
+
+ // Whether we have prematurely reached the end of the file.
+ private bool eof;
+
+ // The current, if any, marker in the input stream.
+ private byte marker;
+
+ // Whether we have a bad marker, I.E. One that is not between RST0 and RST7
+ private bool badMarker;
+
+ // The opening position of an identified marker.
+ private long markerPosition;
+
+ // How many mcu's are left to do.
+ private int todo;
+
+ // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
+ private int eobrun;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input stream.
+ /// The image frame.
+ /// The DC Huffman tables.
+ /// The AC Huffman tables.
+ /// The fast AC decoding tables.
+ /// 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 ScanDecoder(
+ DoubleBufferedStreamReader stream,
+ PdfJsFrame frame,
+ PdfJsHuffmanTables dcHuffmanTables,
+ PdfJsHuffmanTables acHuffmanTables,
+ FastACTables fastACTables,
+ int componentIndex,
+ int componentsLength,
+ int restartInterval,
+ int spectralStart,
+ int spectralEnd,
+ int successiveHigh,
+ int successiveLow)
+ {
+ this.dctZigZag = ZigZag.CreateUnzigTable();
+ this.stream = stream;
+ this.frame = frame;
+ this.dcHuffmanTables = dcHuffmanTables;
+ this.acHuffmanTables = acHuffmanTables;
+ this.fastACTables = fastACTables;
+ this.components = frame.Components;
+ this.marker = JpegConstants.Markers.XFF;
+ this.markerPosition = 0;
+ this.componentIndex = componentIndex;
+ this.componentsLength = componentsLength;
+ this.restartInterval = restartInterval;
+ this.spectralStart = spectralStart;
+ this.spectralEnd = spectralEnd;
+ this.successiveHigh = successiveHigh;
+ this.successiveLow = successiveLow;
+ }
+
+ ///
+ /// Decodes the entropy coded data.
+ ///
+ public void ParseEntropyCodedData()
+ {
+ this.Reset();
+
+ if (!this.frame.Progressive)
+ {
+ this.ParseBaselineData();
+ }
+ else
+ {
+ this.ParseProgressiveData();
+ }
+
+ if (this.badMarker)
+ {
+ this.stream.Position = this.markerPosition;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y));
+
+ private void ParseBaselineData()
+ {
+ if (this.componentsLength == 1)
+ {
+ this.ParseBaselineDataNonInterleaved();
+ }
+ else
+ {
+ this.ParseBaselineDataInterleaved();
+ }
+ }
+
+ private void ParseBaselineDataInterleaved()
+ {
+ // Interleaved
+ int mcu = 0;
+ int mcusPerColumn = this.frame.McusPerColumn;
+ int mcusPerLine = this.frame.McusPerLine;
+ for (int j = 0; j < mcusPerColumn; j++)
+ {
+ for (int i = 0; i < mcusPerLine; i++)
+ {
+ // Scan an interleaved mcu... process components in order
+ for (int k = 0; k < this.componentsLength; k++)
+ {
+ PdfJsFrameComponent component = this.components[k];
+
+ ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
+ int h = component.HorizontalSamplingFactor;
+ int v = component.VerticalSamplingFactor;
+
+ // Scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component
+ for (int y = 0; y < v; y++)
+ {
+ for (int x = 0; x < h; x++)
+ {
+ if (this.eof)
+ {
+ return;
+ }
+
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ int blockRow = (mcuRow * v) + y;
+ int blockCol = (mcuCol * h) + x;
+
+ this.DecodeBlockBaseline(
+ component,
+ blockRow,
+ blockCol,
+ ref dcHuffmanTable,
+ ref acHuffmanTable,
+ ref fastACRef);
+ }
+ }
+ }
+
+ // After all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ mcu++;
+ if (!this.ContinueOnMcuComplete())
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Non-interleaved data, we just need to process one block at a ti
+ /// in trivial scanline order
+ /// number of blocks to do just depends on how many actual "pixels"
+ /// component has, independent of interleaved MCU blocking and such
+ ///
+ private void ParseBaselineDataNonInterleaved()
+ {
+ PdfJsFrameComponent component = this.components[this.componentIndex];
+
+ int w = component.WidthInBlocks;
+ int h = component.HeightInBlocks;
+
+ ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
+
+ int mcu = 0;
+ for (int j = 0; j < h; j++)
+ {
+ for (int i = 0; i < w; i++)
+ {
+ if (this.eof)
+ {
+ return;
+ }
+
+ int blockRow = mcu / w;
+ int blockCol = mcu % w;
+
+ this.DecodeBlockBaseline(
+ component,
+ blockRow,
+ blockCol,
+ ref dcHuffmanTable,
+ ref acHuffmanTable,
+ ref fastACRef);
+
+ // Every data block is an MCU, so countdown the restart interval
+ mcu++;
+ if (!this.ContinueOnMcuComplete())
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ private void ParseProgressiveData()
+ {
+ if (this.componentsLength == 1)
+ {
+ this.ParseProgressiveDataNonInterleaved();
+ }
+ else
+ {
+ this.ParseProgressiveDataInterleaved();
+ }
+ }
+
+ private void ParseProgressiveDataInterleaved()
+ {
+ // Interleaved
+ int mcu = 0;
+ int mcusPerColumn = this.frame.McusPerColumn;
+ int mcusPerLine = this.frame.McusPerLine;
+ for (int j = 0; j < mcusPerColumn; j++)
+ {
+ for (int i = 0; i < mcusPerLine; i++)
+ {
+ // Scan an interleaved mcu... process components in order
+ for (int k = 0; k < this.componentsLength; k++)
+ {
+ PdfJsFrameComponent component = this.components[k];
+ ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ int h = component.HorizontalSamplingFactor;
+ int v = component.VerticalSamplingFactor;
+
+ // Scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component
+ for (int y = 0; y < v; y++)
+ {
+ for (int x = 0; x < h; x++)
+ {
+ if (this.eof)
+ {
+ return;
+ }
+
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ int blockRow = (mcuRow * v) + y;
+ int blockCol = (mcuCol * h) + x;
+
+ this.DecodeBlockProgressiveDC(
+ component,
+ blockRow,
+ blockCol,
+ ref dcHuffmanTable);
+ }
+ }
+ }
+
+ // After all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ mcu++;
+ if (!this.ContinueOnMcuComplete())
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Non-interleaved data, we just need to process one block at a time,
+ /// in trivial scanline order
+ /// number of blocks to do just depends on how many actual "pixels" this
+ /// component has, independent of interleaved MCU blocking and such
+ ///
+ private void ParseProgressiveDataNonInterleaved()
+ {
+ PdfJsFrameComponent component = this.components[this.componentIndex];
+
+ int w = component.WidthInBlocks;
+ int h = component.HeightInBlocks;
+
+ ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
+
+ int mcu = 0;
+ for (int j = 0; j < h; j++)
+ {
+ for (int i = 0; i < w; i++)
+ {
+ if (this.eof)
+ {
+ return;
+ }
+
+ int blockRow = mcu / w;
+ int blockCol = mcu % w;
+
+ if (this.spectralStart == 0)
+ {
+ this.DecodeBlockProgressiveDC(
+ component,
+ blockRow,
+ blockCol,
+ ref dcHuffmanTable);
+ }
+ else
+ {
+ this.DecodeBlockProgressiveAC(
+ component,
+ blockRow,
+ blockCol,
+ ref acHuffmanTable,
+ ref fastACRef);
+ }
+
+ // Every data block is an MCU, so countdown the restart interval
+ mcu++;
+ if (!this.ContinueOnMcuComplete())
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ private void DecodeBlockBaseline(
+ PdfJsFrameComponent component,
+ int row,
+ int col,
+ ref PdfJsHuffmanTable dcTable,
+ ref PdfJsHuffmanTable acTable,
+ ref short fastACRef)
+ {
+ this.CheckBits();
+ int t = this.DecodeHuffman(ref dcTable);
+
+ if (t < 0)
+ {
+ JpegThrowHelper.ThrowBadHuffmanCode();
+ }
+
+ ref short blockDataRef = ref component.GetBlockDataReference(row, col);
+
+ int diff = t != 0 ? this.ExtendReceive(t) : 0;
+ int dc = component.DcPredictor + diff;
+ component.DcPredictor = dc;
+ blockDataRef = (short)dc;
+
+ // Decode AC Components, See Jpeg Spec
+ int k = 1;
+ do
+ {
+ int zig;
+ int s;
+
+ this.CheckBits();
+ int c = this.PeekBits();
+ int r = Unsafe.Add(ref fastACRef, c);
+
+ if (r != 0)
+ {
+ // Fast AC path
+ k += (r >> 4) & 15; // Run
+ s = r & 15; // Combined Length
+ this.codeBuffer <<= s;
+ this.codeBits -= s;
+
+ // Decode into unzigzag location
+ zig = this.dctZigZag[k++];
+ Unsafe.Add(ref blockDataRef, zig) = (short)(r >> 8);
+ }
+ else
+ {
+ int rs = this.DecodeHuffman(ref acTable);
+
+ if (rs < 0)
+ {
+ JpegThrowHelper.ThrowBadHuffmanCode();
+ }
+
+ s = rs & 15;
+ r = rs >> 4;
+
+ if (s == 0)
+ {
+ if (rs != 0xF0)
+ {
+ break; // End block
+ }
+
+ k += 16;
+ }
+ else
+ {
+ k += r;
+
+ // Decode into unzigzag location
+ zig = this.dctZigZag[k++];
+ Unsafe.Add(ref blockDataRef, zig) = (short)this.ExtendReceive(s);
+ }
+ }
+ } while (k < 64);
+ }
+
+ private void DecodeBlockProgressiveDC(
+ PdfJsFrameComponent component,
+ int row,
+ int col,
+ ref PdfJsHuffmanTable dcTable)
+ {
+ if (this.spectralEnd != 0)
+ {
+ JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC.");
+ }
+
+ this.CheckBits();
+
+ ref short blockDataRef = ref component.GetBlockDataReference(row, col);
+
+ if (this.successiveHigh == 0)
+ {
+ // First scan for DC coefficient, must be first
+ int t = this.DecodeHuffman(ref dcTable);
+ int diff = t != 0 ? this.ExtendReceive(t) : 0;
+
+ int dc = component.DcPredictor + diff;
+ component.DcPredictor = dc;
+
+ blockDataRef = (short)(dc << this.successiveLow);
+ }
+ else
+ {
+ // Refinement scan for DC coefficient
+ if (this.GetBit() != 0)
+ {
+ blockDataRef += (short)(1 << this.successiveLow);
+ }
+ }
+ }
+
+ private void DecodeBlockProgressiveAC(
+ PdfJsFrameComponent component,
+ int row,
+ int col,
+ ref PdfJsHuffmanTable acTable,
+ ref short fastACRef)
+ {
+ if (this.spectralStart == 0)
+ {
+ JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC.");
+ }
+
+ ref short blockDataRef = ref component.GetBlockDataReference(row, col);
+
+ if (this.successiveHigh == 0)
+ {
+ // MCU decoding for AC initial scan (either spectral selection,
+ // or first pass of successive approximation).
+ int shift = this.successiveLow;
+
+ if (this.eobrun != 0)
+ {
+ this.eobrun--;
+ return;
+ }
+
+ int k = this.spectralStart;
+ do
+ {
+ int zig;
+ int s;
+
+ this.CheckBits();
+ int c = this.PeekBits();
+ int r = Unsafe.Add(ref fastACRef, c);
+
+ if (r != 0)
+ {
+ // Fast AC path
+ k += (r >> 4) & 15; // Run
+ s = r & 15; // Combined length
+ this.codeBuffer <<= s;
+ this.codeBits -= s;
+
+ // Decode into unzigzag location
+ zig = this.dctZigZag[k++];
+ Unsafe.Add(ref blockDataRef, zig) = (short)((r >> 8) << shift);
+ }
+ else
+ {
+ int rs = this.DecodeHuffman(ref acTable);
+
+ if (rs < 0)
+ {
+ JpegThrowHelper.ThrowBadHuffmanCode();
+ }
+
+ s = rs & 15;
+ r = rs >> 4;
+
+ if (s == 0)
+ {
+ if (r < 15)
+ {
+ this.eobrun = 1 << r;
+ if (r != 0)
+ {
+ this.eobrun += this.GetBits(r);
+ }
+
+ this.eobrun--;
+ break;
+ }
+
+ k += 16;
+ }
+ else
+ {
+ k += r;
+ zig = this.dctZigZag[k++];
+ Unsafe.Add(ref blockDataRef, zig) = (short)(this.ExtendReceive(s) << shift);
+ }
+ }
+ }
+ while (k <= this.spectralEnd);
+ }
+ else
+ {
+ // Refinement scan for these AC coefficients
+ this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable);
+ }
+ }
+
+ private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref PdfJsHuffmanTable acTable)
+ {
+ int k;
+
+ // Refinement scan for these AC coefficients
+ short bit = (short)(1 << this.successiveLow);
+
+ if (this.eobrun != 0)
+ {
+ this.eobrun--;
+ for (k = this.spectralStart; k <= this.spectralEnd; k++)
+ {
+ ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k]);
+ if (p != 0)
+ {
+ if (this.GetBit() != 0)
+ {
+ if ((p & bit) == 0)
+ {
+ if (p > 0)
+ {
+ p += bit;
+ }
+ else
+ {
+ p -= bit;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ k = this.spectralStart;
+ do
+ {
+ int rs = this.DecodeHuffman(ref acTable);
+ if (rs < 0)
+ {
+ JpegThrowHelper.ThrowBadHuffmanCode();
+ }
+
+ int s = rs & 15;
+ int r = rs >> 4;
+
+ if (s == 0)
+ {
+ // r=15 s=0 should write 16 0s, so we just do
+ // a run of 15 0s and then write s (which is 0),
+ // so we don't have to do anything special here
+ if (r < 15)
+ {
+ this.eobrun = (1 << r) - 1;
+
+ if (r != 0)
+ {
+ this.eobrun += this.GetBits(r);
+ }
+
+ r = 64; // Force end of block
+ }
+ }
+ else
+ {
+ if (s != 1)
+ {
+ JpegThrowHelper.ThrowBadHuffmanCode();
+ }
+
+ // Sign bit
+ if (this.GetBit() != 0)
+ {
+ s = bit;
+ }
+ else
+ {
+ s = -bit;
+ }
+ }
+
+ // Advance by r
+ while (k <= this.spectralEnd)
+ {
+ ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k++]);
+ if (p != 0)
+ {
+ if (this.GetBit() != 0)
+ {
+ if ((p & bit) == 0)
+ {
+ if (p > 0)
+ {
+ p += bit;
+ }
+ else
+ {
+ p -= bit;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (r == 0)
+ {
+ p = (short)s;
+ break;
+ }
+
+ r--;
+ }
+ }
+ }
+ while (k <= this.spectralEnd);
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int GetBits(int n)
+ {
+ if (this.codeBits < n)
+ {
+ this.FillBuffer();
+ }
+
+ uint k = LRot(this.codeBuffer, n);
+ this.codeBuffer = k & ~Bmask[n];
+ k &= Bmask[n];
+ this.codeBits -= n;
+ return (int)k;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int GetBit()
+ {
+ if (this.codeBits < 1)
+ {
+ this.FillBuffer();
+ }
+
+ uint k = this.codeBuffer;
+ this.codeBuffer <<= 1;
+ this.codeBits--;
+
+ return (int)(k & 0x80000000);
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private void FillBuffer()
+ {
+ // Attempt to load at least the minimum nbumber of required bits into the buffer.
+ // We fail to do so only if we hit a marker or reach the end of the input stream.
+ do
+ {
+ int b = this.nomore ? 0 : this.stream.ReadByte();
+
+ if (b == -1)
+ {
+ // We've encountered the end of the file stream which means there's no EOI marker in the image
+ // or the SOS marker has the wrong dimensions set.
+ this.eof = true;
+ b = 0;
+ }
+
+ // Found a marker.
+ if (b == JpegConstants.Markers.XFF)
+ {
+ this.markerPosition = this.stream.Position - 1;
+ int c = this.stream.ReadByte();
+ while (c == JpegConstants.Markers.XFF)
+ {
+ c = this.stream.ReadByte();
+
+ if (c == -1)
+ {
+ this.eof = true;
+ c = 0;
+ break;
+ }
+ }
+
+ if (c != 0)
+ {
+ this.marker = (byte)c;
+ this.nomore = true;
+ if (!this.HasRestart())
+ {
+ this.badMarker = true;
+ }
+
+ return;
+ }
+ }
+
+ this.codeBuffer |= (uint)b << (24 - this.codeBits);
+ this.codeBits += 8;
+ }
+ while (this.codeBits <= 24);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int DecodeHuffman(ref PdfJsHuffmanTable table)
+ {
+ this.CheckBits();
+
+ // Look at the top FastBits and determine what symbol ID it is,
+ // if the code is <= FastBits.
+ int c = this.PeekBits();
+ int k = table.Lookahead[c];
+ if (k < 0xFF)
+ {
+ int s = table.Sizes[k];
+ if (s > this.codeBits)
+ {
+ return -1;
+ }
+
+ this.codeBuffer <<= s;
+ this.codeBits -= s;
+ return table.Values[k];
+ }
+
+ return this.DecodeHuffmanSlow(ref table);
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private int DecodeHuffmanSlow(ref PdfJsHuffmanTable table)
+ {
+ // Naive test is to shift the code_buffer down so k bits are
+ // valid, then test against MaxCode. To speed this up, we've
+ // preshifted maxcode left so that it has (16-k) 0s at the
+ // end; in other words, regardless of the number of bits, it
+ // wants to be compared against something shifted to have 16;
+ // that way we don't need to shift inside the loop.
+ uint temp = this.codeBuffer >> 16;
+ int k;
+ for (k = FastBits + 1; ; k++)
+ {
+ if (temp < table.MaxCode[k])
+ {
+ break;
+ }
+ }
+
+ if (k == 17)
+ {
+ // Error! code not found
+ this.codeBits -= 16;
+ return -1;
+ }
+
+ if (k > this.codeBits)
+ {
+ return -1;
+ }
+
+ // Convert the huffman code to the symbol id
+ int c = (int)(((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]);
+
+ // Convert the id to a symbol
+ this.codeBits -= k;
+ this.codeBuffer <<= k;
+ return table.Values[c];
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int ExtendReceive(int n)
+ {
+ if (this.codeBits < n)
+ {
+ this.FillBuffer();
+ }
+
+ int sgn = (int)this.codeBuffer >> 31;
+ uint k = LRot(this.codeBuffer, n);
+ this.codeBuffer = k & ~Bmask[n];
+ k &= Bmask[n];
+ this.codeBits -= n;
+ return (int)(k + (Bias[n] & ~sgn));
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void CheckBits()
+ {
+ if (this.codeBits < 16)
+ {
+ this.FillBuffer();
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1));
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private bool ContinueOnMcuComplete()
+ {
+ if (--this.todo > 0)
+ {
+ return true;
+ }
+
+ if (this.codeBits < 24)
+ {
+ this.FillBuffer();
+ }
+
+ // If it's NOT a restart, then just bail, so we get corrupt data rather than no data.
+ // Reset the stream to before any bad markers to ensure we can read sucessive segments.
+ if (this.badMarker)
+ {
+ this.stream.Position = this.markerPosition;
+ }
+
+ if (!this.HasRestart())
+ {
+ return false;
+ }
+
+ this.Reset();
+
+ return true;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private bool HasRestart()
+ {
+ byte m = this.marker;
+ return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7;
+ }
+
+ private void Reset()
+ {
+ this.codeBits = 0;
+ this.codeBuffer = 0;
+
+ for (int i = 0; i < this.components.Length; i++)
+ {
+ PdfJsFrameComponent c = this.components[i];
+ c.DcPredictor = 0;
+ }
+
+ this.nomore = false;
+ this.marker = JpegConstants.Markers.XFF;
+ this.markerPosition = 0;
+ this.badMarker = false;
+ this.eobrun = 0;
+
+ // No more than 1<<31 MCUs if no restartInterval? that's plenty safe since we don't even allow 1<<30 pixels
+ this.todo = this.restartInterval > 0 ? this.restartInterval : int.MaxValue;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
index bd1d84ecc9..a360d54771 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
@@ -58,6 +58,11 @@ internal sealed class PdfJsJpegDecoderCore : IRawJpegData
///
private PdfJsHuffmanTables acHuffmanTables;
+ ///
+ /// The fast AC tables used for entropy decoding
+ ///
+ private FastACTables fastACTables;
+
///
/// The reset interval determined by RST markers
///
@@ -239,6 +244,7 @@ public void ParseStream(Stream stream, bool metadataOnly = false)
this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables();
+ this.fastACTables = new FastACTables(this.configuration.MemoryAllocator);
}
while (fileMarker.Marker != JpegConstants.Markers.EOI)
@@ -353,12 +359,14 @@ public void Dispose()
{
this.InputStream?.Dispose();
this.Frame?.Dispose();
+ this.fastACTables?.Dispose();
// Set large fields to null.
this.InputStream = null;
this.Frame = null;
this.dcHuffmanTables = null;
this.acHuffmanTables = null;
+ this.fastACTables = null;
}
///
@@ -723,11 +731,20 @@ private void ProcessDefineHuffmanTablesMarker(int remaining)
i += 17 + codeLengthSum;
+ int tableType = huffmanTableSpec >> 4;
+ int tableIndex = huffmanTableSpec & 15;
+
this.BuildHuffmanTable(
- huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables,
- huffmanTableSpec & 15,
+ tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables,
+ tableIndex,
codeLengths.GetSpan(),
huffmanValues.GetSpan());
+
+ if (huffmanTableSpec >> 4 != 0)
+ {
+ // Build a table that decodes both magnitude and value of small ACs in one go.
+ this.fastACTables.BuildACTableLut(huffmanTableSpec & 15, this.acHuffmanTables);
+ }
}
}
}
@@ -786,21 +803,22 @@ private void ProcessStartOfScanMarker()
int spectralStart = this.temp[0];
int spectralEnd = this.temp[1];
int successiveApproximation = this.temp[2];
- PdfJsScanDecoder scanDecoder = default;
-
- scanDecoder.DecodeScan(
- this.Frame,
- this.InputStream,
- this.dcHuffmanTables,
- this.acHuffmanTables,
- this.Frame.Components,
- componentIndex,
- selectorsCount,
- this.resetInterval,
- spectralStart,
- spectralEnd,
- successiveApproximation >> 4,
- successiveApproximation & 15);
+
+ var sd = new ScanDecoder(
+ this.InputStream,
+ this.Frame,
+ this.dcHuffmanTables,
+ this.acHuffmanTables,
+ this.fastACTables,
+ componentIndex,
+ selectorsCount,
+ this.resetInterval,
+ spectralStart,
+ spectralEnd,
+ successiveApproximation >> 4,
+ successiveApproximation & 15);
+
+ sd.ParseEntropyCodedData();
}
///
@@ -827,6 +845,11 @@ private ushort ReadUint16()
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);
}
+ ///
+ /// Post processes the pixels into the destination image.
+ ///
+ /// The pixel format.
+ /// The .
private Image PostProcessIntoImage()
where TPixel : struct, IPixel
{
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
new file mode 100644
index 0000000000..059f312b3e
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using BenchmarkDotNet.Attributes;
+using System.Drawing;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
+using SixLabors.ImageSharp.Tests;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
+{
+ [Config(typeof(Config.ShortClr))]
+ public class DecodeJpegParseStreamOnly
+ {
+ [Params(TestImages.Jpeg.Baseline.Jpeg420Exif)]
+ public string TestImage { get; set; }
+
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ private byte[] jpegBytes;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
+ }
+
+ [Benchmark(Baseline = true, Description = "System.Drawing FULL")]
+ public Size JpegSystemDrawing()
+ {
+ using (var memoryStream = new MemoryStream(this.jpegBytes))
+ {
+ using (var image = System.Drawing.Image.FromStream(memoryStream))
+ {
+ return image.Size;
+ }
+ }
+ }
+
+ [Benchmark(Description = "PdfJsJpegDecoderCore.ParseStream")]
+ public void ParseStreamPdfJs()
+ {
+ using (var memoryStream = new MemoryStream(this.jpegBytes))
+ {
+ var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder() { IgnoreMetadata = true });
+ decoder.ParseStream(memoryStream);
+ decoder.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
index 539ab73195..3c98d5be72 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
@@ -38,6 +38,7 @@ public partial class JpegDecoderTests
TestImages.Jpeg.Issues.NoEoiProgressive517,
TestImages.Jpeg.Issues.BadRstProgressive518,
TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159,
+ TestImages.Jpeg.Issues.DhtHasWrongLength624,
};
///
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 6d3a76e75f..b0bdad8e5c 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -144,6 +144,7 @@ public static class Issues
public const string NoEoiProgressive517 = "Jpg/issues/Issue517-No-EOI-Progressive.jpg";
public const string BadRstProgressive518 = "Jpg/issues/Issue518-Bad-RST-Progressive.jpg";
public const string InvalidCast520 = "Jpg/issues/Issue520-InvalidCast.jpg";
+ public const string DhtHasWrongLength624 = "Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg";
}
public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray();
diff --git a/tests/Images/External b/tests/Images/External
index d9d93bbdd1..98fb7e2e4d 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit d9d93bbdd18dd7b818c0d19cc8f967be98045d3c
+Subproject commit 98fb7e2e4d5935b1c733bd2b206b6145b71ef378
diff --git a/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg b/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg
new file mode 100644
index 0000000000..2077b75182
Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg differ