diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs
new file mode 100644
index 0000000000..57d2221ba3
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ internal unsafe struct FastACTable
+ {
+ ///
+ /// Gets the lookahead array
+ ///
+ public fixed short Lookahead[512];
+
+ ///
+ /// Derives a lookup table for fast AC entropy scan decoding.
+ /// This can happen multiple times during progressive decoding but always outside mcu loops.
+ ///
+ /// The AC Huffman table.
+ public void Derive(ref HuffmanTable huffmanTable)
+ {
+ const int FastBits = ScanDecoder.FastBits;
+ ref short fastACRef = ref this.Lookahead[0];
+ ref byte huffmanLookaheadRef = ref huffmanTable.Lookahead[0];
+ ref byte huffmanValuesRef = ref huffmanTable.Values[0];
+ ref short huffmanSizesRef = ref huffmanTable.Sizes[0];
+
+ int i;
+ for (i = 0; i < (1 << FastBits); i++)
+ {
+ byte fast = Unsafe.Add(ref huffmanLookaheadRef, i);
+ Unsafe.Add(ref fastACRef, i) = 0;
+
+ if (fast < byte.MaxValue)
+ {
+ int rs = Unsafe.Add(ref huffmanValuesRef, fast);
+ int run = (rs >> 4) & 15;
+ int magbits = rs & 15;
+ int len = Unsafe.Add(ref huffmanSizesRef, 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)
+ {
+ Unsafe.Add(ref fastACRef, i) = (short)((k << 8) + (run << 4) + (len + magbits));
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs
deleted file mode 100644
index 06b46746a6..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Runtime.CompilerServices;
-
-using SixLabors.ImageSharp.Memory;
-using SixLabors.Memory;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
-{
- ///
- /// 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.Allocate2D(512, 4, AllocationOptions.Clean);
-
- ///
- /// Gets the representing the table at the index in the collection.
- ///
- /// The table index.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public ReadOnlySpan GetTableSpan(int index) => this.tables.GetRowSpan(index);
-
- ///
- /// Gets a reference to the first element of the AC table indexed by
- ///
- /// The frame component.
- [MethodImpl(InliningOptions.ShortMethod)]
- public ref short GetAcTableReference(JpegComponent component) => 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 unsafe void BuildACTableLut(int index, HuffmanTables acHuffmanTables)
- {
- const int FastBits = ScanDecoder.FastBits;
- Span fastAC = this.tables.GetRowSpan(index);
- ref HuffmanTable huffmanTable = ref acHuffmanTables[index];
-
- int i;
- for (i = 0; i < (1 << FastBits); i++)
- {
- byte fast = huffmanTable.Lookahead[i];
- fastAC[i] = 0;
- if (fast < byte.MaxValue)
- {
- int rs = huffmanTable.Values[fast];
- int run = (rs >> 4) & 15;
- int magbits = rs & 15;
- int len = huffmanTable.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 << 8) + (run << 4) + (len + magbits));
- }
- }
- }
- }
- }
-
- ///
- public void Dispose()
- {
- this.tables?.Dispose();
- this.tables = null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
index 90b3624651..a2c9bade19 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
@@ -17,6 +17,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct HuffmanTable
{
+ private bool isDerived;
+
+ private readonly MemoryAllocator memoryAllocator;
+
+#pragma warning disable IDE0044 // Add readonly modifier
+ private fixed byte codeLengths[17];
+#pragma warning restore IDE0044 // Add readonly modifier
+
///
/// Gets the max code array
///
@@ -50,16 +58,31 @@ internal unsafe struct HuffmanTable
/// The huffman values
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan codeLengths, ReadOnlySpan values)
{
+ this.isDerived = false;
+ this.memoryAllocator = memoryAllocator;
+ Unsafe.CopyBlockUnaligned(ref this.codeLengths[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length);
+ Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length);
+ }
+
+ ///
+ /// Expands the HuffmanTable into its derived form.
+ ///
+ public void Derive()
+ {
+ if (this.isDerived)
+ {
+ return;
+ }
+
const int Length = 257;
- using (IMemoryOwner huffcode = memoryAllocator.Allocate(Length))
+ using (IMemoryOwner huffcode = this.memoryAllocator.Allocate(Length))
{
ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan());
- ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths);
+ ref byte codeLengthsRef = ref this.codeLengths[0];
// Figure C.1: make table of Huffman code length for each symbol
ref short sizesRef = ref this.Sizes[0];
short x = 0;
-
for (short i = 1; i < 17; i++)
{
byte length = Unsafe.Add(ref codeLengthsRef, i);
@@ -119,7 +142,7 @@ public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan codeLeng
}
}
- Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), 256);
+ this.isDerived = true;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs
deleted file mode 100644
index dc066aa0ac..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System.Runtime.CompilerServices;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
-{
- ///
- /// Defines a 2 pairs of huffman tables.
- ///
- internal sealed class HuffmanTables
- {
- 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 => ref this.tables[index];
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
index 070652a9f9..ef4b359ff2 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
@@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@@ -25,9 +26,9 @@ internal class ScanDecoder
private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 };
private readonly JpegFrame frame;
- private readonly HuffmanTables dcHuffmanTables;
- private readonly HuffmanTables acHuffmanTables;
- private readonly FastACTables fastACTables;
+ private readonly HuffmanTable[] dcHuffmanTables;
+ private readonly HuffmanTable[] acHuffmanTables;
+ private readonly FastACTable[] fastACTables;
private readonly DoubleBufferedStreamReader stream;
private readonly JpegComponent[] components;
@@ -95,9 +96,9 @@ internal class ScanDecoder
public ScanDecoder(
DoubleBufferedStreamReader stream,
JpegFrame frame,
- HuffmanTables dcHuffmanTables,
- HuffmanTables acHuffmanTables,
- FastACTables fastACTables,
+ HuffmanTable[] dcHuffmanTables,
+ HuffmanTable[] acHuffmanTables,
+ FastACTable[] fastACTables,
int componentsLength,
int restartInterval,
int spectralStart,
@@ -159,17 +160,35 @@ private void ParseBaselineData()
}
}
- private void ParseBaselineDataInterleaved()
+ private unsafe void ParseBaselineDataInterleaved()
{
// Interleaved
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine;
+
+ // Pre-derive the huffman table to avoid in-loop checks.
+ for (int i = 0; i < this.componentsLength; i++)
+ {
+ int order = this.frame.ComponentOrder[i];
+ JpegComponent component = this.components[order];
+
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ dcHuffmanTable.Derive();
+ acHuffmanTable.Derive();
+
+ ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
+ fastAcTable.Derive(ref acHuffmanTable);
+ }
+
for (int j = 0; j < mcusPerColumn; j++)
{
for (int i = 0; i < mcusPerLine; i++)
{
// Scan an interleaved mcu... process components in order
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++)
{
int order = this.frame.ComponentOrder[k];
@@ -177,18 +196,20 @@ private void ParseBaselineDataInterleaved()
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
- ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
+ ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
+ ref short fastACRef = ref fastAcTable.Lookahead[0];
+
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
- int mcuRow = mcu / mcusPerLine;
-
// 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++)
{
int blockRow = (mcuRow * v) + y;
Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
for (int x = 0; x < h; x++)
{
if (this.eof)
@@ -196,12 +217,11 @@ private void ParseBaselineDataInterleaved()
return;
}
- int mcuCol = mcu % mcusPerLine;
int blockCol = (mcuCol * h) + x;
this.DecodeBlockBaseline(
component,
- ref blockSpan[blockCol],
+ ref Unsafe.Add(ref blockRef, blockCol),
ref dcHuffmanTable,
ref acHuffmanTable,
ref fastACRef);
@@ -225,7 +245,7 @@ private void ParseBaselineDataInterleaved()
/// number of blocks to do just depends on how many actual "pixels" each component has,
/// independent of interleaved MCU blocking and such.
///
- private void ParseBaselineDataNonInterleaved()
+ private unsafe void ParseBaselineDataNonInterleaved()
{
JpegComponent component = this.components[this.frame.ComponentOrder[0]];
@@ -234,14 +254,19 @@ private void ParseBaselineDataNonInterleaved()
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
- ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
+ dcHuffmanTable.Derive();
+ acHuffmanTable.Derive();
+
+ ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
+ fastAcTable.Derive(ref acHuffmanTable);
+ ref short fastACRef = ref fastAcTable.Lookahead[0];
int mcu = 0;
for (int j = 0; j < h; j++)
{
- // TODO: Isn't blockRow == j actually?
- int blockRow = mcu / w;
- Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
+ int blockRow = j;
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++)
{
@@ -250,12 +275,9 @@ private void ParseBaselineDataNonInterleaved()
return;
}
- // TODO: Isn't blockCol == i actually?
- int blockCol = mcu % w;
-
this.DecodeBlockBaseline(
component,
- ref blockSpan[blockCol],
+ ref Unsafe.Add(ref blockRef, i),
ref dcHuffmanTable,
ref acHuffmanTable,
ref fastACRef);
@@ -340,16 +362,29 @@ private void ParseProgressiveDataInterleaved()
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine;
+
+ // Pre-derive the huffman table to avoid in-loop checks.
+ for (int k = 0; k < this.componentsLength; k++)
+ {
+ int order = this.frame.ComponentOrder[k];
+ JpegComponent component = this.components[order];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ dcHuffmanTable.Derive();
+ }
+
for (int j = 0; j < mcusPerColumn; j++)
{
for (int i = 0; i < mcusPerLine; i++)
{
// Scan an interleaved mcu... process components in order
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++)
{
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@@ -357,9 +392,9 @@ private void ParseProgressiveDataInterleaved()
// by the basic H and V specified for the component
for (int y = 0; y < v; y++)
{
- int mcuRow = mcu / mcusPerLine;
int blockRow = (mcuRow * v) + y;
Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++)
{
@@ -368,12 +403,11 @@ private void ParseProgressiveDataInterleaved()
return;
}
- int mcuCol = mcu % mcusPerLine;
int blockCol = (mcuCol * h) + x;
this.DecodeBlockProgressiveDC(
component,
- ref blockSpan[blockCol],
+ ref Unsafe.Add(ref blockRef, blockCol),
ref dcHuffmanTable);
}
}
@@ -396,56 +430,78 @@ private void ParseProgressiveDataInterleaved()
/// 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()
+ private unsafe void ParseProgressiveDataNonInterleaved()
{
JpegComponent component = this.components[this.frame.ComponentOrder[0]];
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
- ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
-
- int mcu = 0;
- for (int j = 0; j < h; j++)
+ if (this.spectralStart == 0)
{
- // TODO: isn't blockRow == j actually?
- int blockRow = mcu / w;
- Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ dcHuffmanTable.Derive();
- for (int i = 0; i < w; i++)
+ int mcu = 0;
+ for (int j = 0; j < h; j++)
{
- if (this.eof)
- {
- return;
- }
-
- // TODO: isn't blockCol == i actually?
- int blockCol = mcu % w;
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
- ref Block8x8 block = ref blockSpan[blockCol];
-
- if (this.spectralStart == 0)
+ for (int i = 0; i < w; i++)
{
+ if (this.eof)
+ {
+ return;
+ }
+
this.DecodeBlockProgressiveDC(
component,
- ref block,
+ ref Unsafe.Add(ref blockRef, i),
ref dcHuffmanTable);
+
+ // Every data block is an MCU, so countdown the restart interval
+ mcu++;
+ if (!this.ContinueOnMcuComplete())
+ {
+ return;
+ }
}
- else
+ }
+ }
+ else
+ {
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ acHuffmanTable.Derive();
+
+ ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
+ fastAcTable.Derive(ref acHuffmanTable);
+ ref short fastACRef = ref fastAcTable.Lookahead[0];
+
+ int mcu = 0;
+ for (int j = 0; j < h; j++)
+ {
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int i = 0; i < w; i++)
{
+ if (this.eof)
+ {
+ return;
+ }
+
this.DecodeBlockProgressiveAC(
- ref block,
+ ref Unsafe.Add(ref blockRef, i),
ref acHuffmanTable,
ref fastACRef);
- }
- // Every data block is an MCU, so countdown the restart interval
- mcu++;
- if (!this.ContinueOnMcuComplete())
- {
- return;
+ // Every data block is an MCU, so countdown the restart interval
+ mcu++;
+ if (!this.ContinueOnMcuComplete())
+ {
+ return;
+ }
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 9bcfd9ff64..891dd52826 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -52,17 +52,17 @@ internal sealed class JpegDecoderCore : IRawJpegData
///
/// The DC Huffman tables
///
- private HuffmanTables dcHuffmanTables;
+ private HuffmanTable[] dcHuffmanTables;
///
/// The AC Huffman tables
///
- private HuffmanTables acHuffmanTables;
+ private HuffmanTable[] acHuffmanTables;
///
/// The fast AC tables used for entropy decoding
///
- private FastACTables fastACTables;
+ private FastACTable[] fastACTables;
///
/// The reset interval determined by RST markers
@@ -266,9 +266,10 @@ public void ParseStream(Stream stream, bool metadataOnly = false)
// Only assign what we need
if (!metadataOnly)
{
- this.dcHuffmanTables = new HuffmanTables();
- this.acHuffmanTables = new HuffmanTables();
- this.fastACTables = new FastACTables(this.configuration.MemoryAllocator);
+ const int maxTables = 4;
+ this.dcHuffmanTables = new HuffmanTable[maxTables];
+ this.acHuffmanTables = new HuffmanTable[maxTables];
+ this.fastACTables = new FastACTable[maxTables];
}
// Break only when we discover a valid EOI marker.
@@ -378,7 +379,6 @@ public void Dispose()
{
this.InputStream?.Dispose();
this.Frame?.Dispose();
- this.fastACTables?.Dispose();
// Set large fields to null.
this.InputStream = null;
@@ -872,12 +872,6 @@ private void ProcessDefineHuffmanTablesMarker(int remaining)
tableIndex,
codeLengths.GetSpan(),
huffmanValues.GetSpan());
-
- if (tableType != 0)
- {
- // Build a table that decodes both magnitude and value of small ACs in one go.
- this.fastACTables.BuildACTableLut(tableIndex, this.acHuffmanTables);
- }
}
}
}
@@ -967,7 +961,7 @@ private void ProcessStartOfScanMarker()
/// The codelengths
/// The values
[MethodImpl(InliningOptions.ShortMethod)]
- private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values)
+ private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values)
=> tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
///
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
index ff49de2481..8a94826077 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
@@ -79,7 +79,8 @@ public partial class JpegDecoderTests
TestImages.Jpeg.Issues.Fuzz.ArgumentException826A,
TestImages.Jpeg.Issues.Fuzz.ArgumentException826B,
TestImages.Jpeg.Issues.Fuzz.ArgumentException826C,
- TestImages.Jpeg.Issues.Fuzz.AccessViolationException827
+ TestImages.Jpeg.Issues.Fuzz.AccessViolationException827,
+ TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839
};
private static readonly Dictionary CustomToleranceValues =
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index c99fe8d652..cf1e49b271 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -196,6 +196,7 @@ public static class Fuzz
public const string ArgumentException826B = "Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg";
public const string ArgumentException826C = "Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg";
public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg";
+ public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg";
}
}
diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg
new file mode 100644
index 0000000000..0e284349e3
Binary files /dev/null and b/tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg differ