Browse Source

Lazily derive the huffman tables. Fix #839 (#841)

* Lazily derive the huffman tables. Fix #839

* Lazy invoke fast table

* Add performance tweaks to scan decoder.

* Remove unneccessary classes.
af/merge-core
James Jackson-South 7 years ago
committed by GitHub
parent
commit
a69eb5bbed
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 61
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs
  2. 90
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs
  3. 31
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  4. 26
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs
  5. 164
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
  6. 22
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  7. 3
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  8. 1
      tests/ImageSharp.Tests/TestImages.cs
  9. 3
      tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg

61
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
{
/// <summary>
/// Gets the lookahead array
/// </summary>
public fixed short Lookahead[512];
/// <summary>
/// Derives a lookup table for fast AC entropy scan decoding.
/// This can happen multiple times during progressive decoding but always outside mcu loops.
/// </summary>
/// <param name="huffmanTable">The AC Huffman table.</param>
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));
}
}
}
}
}
}
}

90
src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs

@ -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
{
/// <summary>
/// The collection of lookup tables used for fast AC entropy scan decoding.
/// </summary>
internal sealed class FastACTables : IDisposable
{
private Buffer2D<short> tables;
/// <summary>
/// Initializes a new instance of the <see cref="FastACTables"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator used to allocate memory for image processing operations.</param>
public FastACTables(MemoryAllocator memoryAllocator) => this.tables = memoryAllocator.Allocate2D<short>(512, 4, AllocationOptions.Clean);
/// <summary>
/// Gets the <see cref="Span{Int16}"/> representing the table at the index in the collection.
/// </summary>
/// <param name="index">The table index.</param>
/// <returns><see cref="Span{Int16}"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlySpan<short> GetTableSpan(int index) => this.tables.GetRowSpan(index);
/// <summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="JpegComponent.ACHuffmanTableId"/>
/// </summary>
/// <param name="component">The frame component.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public ref short GetAcTableReference(JpegComponent component) => ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0];
/// <summary>
/// Builds a lookup table for fast AC entropy scan decoding.
/// </summary>
/// <param name="index">The table index.</param>
/// <param name="acHuffmanTables">The collection of AC Huffman tables.</param>
public unsafe void BuildACTableLut(int index, HuffmanTables acHuffmanTables)
{
const int FastBits = ScanDecoder.FastBits;
Span<short> 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));
}
}
}
}
}
/// <inheritdoc />
public void Dispose()
{
this.tables?.Dispose();
this.tables = null;
}
}
}

31
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -17,6 +17,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct HuffmanTable 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
/// <summary> /// <summary>
/// Gets the max code array /// Gets the max code array
/// </summary> /// </summary>
@ -50,16 +58,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <param name="values">The huffman values</param> /// <param name="values">The huffman values</param>
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values) public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> 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);
}
/// <summary>
/// Expands the HuffmanTable into its derived form.
/// </summary>
public void Derive()
{
if (this.isDerived)
{
return;
}
const int Length = 257; const int Length = 257;
using (IMemoryOwner<short> huffcode = memoryAllocator.Allocate<short>(Length)) using (IMemoryOwner<short> huffcode = this.memoryAllocator.Allocate<short>(Length))
{ {
ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan()); 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 // Figure C.1: make table of Huffman code length for each symbol
ref short sizesRef = ref this.Sizes[0]; ref short sizesRef = ref this.Sizes[0];
short x = 0; short x = 0;
for (short i = 1; i < 17; i++) for (short i = 1; i < 17; i++)
{ {
byte length = Unsafe.Add(ref codeLengthsRef, i); byte length = Unsafe.Add(ref codeLengthsRef, i);
@ -119,7 +142,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), 256); this.isDerived = true;
} }
} }
} }

26
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs

@ -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
{
/// <summary>
/// Defines a 2 pairs of huffman tables.
/// </summary>
internal sealed class HuffmanTables
{
private readonly HuffmanTable[] tables = new HuffmanTable[4];
/// <summary>
/// Gets or sets the table at the given index.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The <see cref="HuffmanTable"/></returns>
public ref HuffmanTable this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this.tables[index];
}
}
}

164
src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -25,9 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 }; 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 JpegFrame frame;
private readonly HuffmanTables dcHuffmanTables; private readonly HuffmanTable[] dcHuffmanTables;
private readonly HuffmanTables acHuffmanTables; private readonly HuffmanTable[] acHuffmanTables;
private readonly FastACTables fastACTables; private readonly FastACTable[] fastACTables;
private readonly DoubleBufferedStreamReader stream; private readonly DoubleBufferedStreamReader stream;
private readonly JpegComponent[] components; private readonly JpegComponent[] components;
@ -95,9 +96,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public ScanDecoder( public ScanDecoder(
DoubleBufferedStreamReader stream, DoubleBufferedStreamReader stream,
JpegFrame frame, JpegFrame frame,
HuffmanTables dcHuffmanTables, HuffmanTable[] dcHuffmanTables,
HuffmanTables acHuffmanTables, HuffmanTable[] acHuffmanTables,
FastACTables fastACTables, FastACTable[] fastACTables,
int componentsLength, int componentsLength,
int restartInterval, int restartInterval,
int spectralStart, int spectralStart,
@ -159,17 +160,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
private void ParseBaselineDataInterleaved() private unsafe void ParseBaselineDataInterleaved()
{ {
// Interleaved // Interleaved
int mcu = 0; int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn; int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine; 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 j = 0; j < mcusPerColumn; j++)
{ {
for (int i = 0; i < mcusPerLine; i++) for (int i = 0; i < mcusPerLine; i++)
{ {
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsLength; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
@ -177,18 +196,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; 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 h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor; int v = component.VerticalSamplingFactor;
int mcuRow = mcu / mcusPerLine;
// Scan out an mcu's worth of this component; that's just determined // Scan out an mcu's worth of this component; that's just determined
// by the basic H and V specified for the component // by the basic H and V specified for the component
for (int y = 0; y < v; y++) for (int y = 0; y < v; y++)
{ {
int blockRow = (mcuRow * v) + y; int blockRow = (mcuRow * v) + y;
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++) for (int x = 0; x < h; x++)
{ {
if (this.eof) if (this.eof)
@ -196,12 +217,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return; return;
} }
int mcuCol = mcu % mcusPerLine;
int blockCol = (mcuCol * h) + x; int blockCol = (mcuCol * h) + x;
this.DecodeBlockBaseline( this.DecodeBlockBaseline(
component, component,
ref blockSpan[blockCol], ref Unsafe.Add(ref blockRef, blockCol),
ref dcHuffmanTable, ref dcHuffmanTable,
ref acHuffmanTable, ref acHuffmanTable,
ref fastACRef); ref fastACRef);
@ -225,7 +245,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// number of blocks to do just depends on how many actual "pixels" each component has, /// number of blocks to do just depends on how many actual "pixels" each component has,
/// independent of interleaved MCU blocking and such. /// independent of interleaved MCU blocking and such.
/// </summary> /// </summary>
private void ParseBaselineDataNonInterleaved() private unsafe void ParseBaselineDataNonInterleaved()
{ {
JpegComponent component = this.components[this.frame.ComponentOrder[0]]; JpegComponent component = this.components[this.frame.ComponentOrder[0]];
@ -234,14 +254,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; 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; int mcu = 0;
for (int j = 0; j < h; j++) for (int j = 0; j < h; j++)
{ {
// TODO: Isn't blockRow == j actually? int blockRow = j;
int blockRow = mcu / w; Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int i = 0; i < w; i++) for (int i = 0; i < w; i++)
{ {
@ -250,12 +275,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return; return;
} }
// TODO: Isn't blockCol == i actually?
int blockCol = mcu % w;
this.DecodeBlockBaseline( this.DecodeBlockBaseline(
component, component,
ref blockSpan[blockCol], ref Unsafe.Add(ref blockRef, i),
ref dcHuffmanTable, ref dcHuffmanTable,
ref acHuffmanTable, ref acHuffmanTable,
ref fastACRef); ref fastACRef);
@ -340,16 +362,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int mcu = 0; int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn; int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine; 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 j = 0; j < mcusPerColumn; j++)
{ {
for (int i = 0; i < mcusPerLine; i++) for (int i = 0; i < mcusPerLine; i++)
{ {
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsLength; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
int h = component.HorizontalSamplingFactor; int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor; int v = component.VerticalSamplingFactor;
@ -357,9 +392,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// by the basic H and V specified for the component // by the basic H and V specified for the component
for (int y = 0; y < v; y++) for (int y = 0; y < v; y++)
{ {
int mcuRow = mcu / mcusPerLine;
int blockRow = (mcuRow * v) + y; int blockRow = (mcuRow * v) + y;
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++) for (int x = 0; x < h; x++)
{ {
@ -368,12 +403,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return; return;
} }
int mcuCol = mcu % mcusPerLine;
int blockCol = (mcuCol * h) + x; int blockCol = (mcuCol * h) + x;
this.DecodeBlockProgressiveDC( this.DecodeBlockProgressiveDC(
component, component,
ref blockSpan[blockCol], ref Unsafe.Add(ref blockRef, blockCol),
ref dcHuffmanTable); ref dcHuffmanTable);
} }
} }
@ -396,56 +430,78 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// number of blocks to do just depends on how many actual "pixels" this /// number of blocks to do just depends on how many actual "pixels" this
/// component has, independent of interleaved MCU blocking and such /// component has, independent of interleaved MCU blocking and such
/// </summary> /// </summary>
private void ParseProgressiveDataNonInterleaved() private unsafe void ParseProgressiveDataNonInterleaved()
{ {
JpegComponent component = this.components[this.frame.ComponentOrder[0]]; JpegComponent component = this.components[this.frame.ComponentOrder[0]];
int w = component.WidthInBlocks; int w = component.WidthInBlocks;
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; if (this.spectralStart == 0)
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++)
{ {
// TODO: isn't blockRow == j actually? ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
int blockRow = mcu / w; dcHuffmanTable.Derive();
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
for (int i = 0; i < w; i++) int mcu = 0;
for (int j = 0; j < h; j++)
{ {
if (this.eof) Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(j);
{ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
return;
}
// TODO: isn't blockCol == i actually?
int blockCol = mcu % w;
ref Block8x8 block = ref blockSpan[blockCol]; for (int i = 0; i < w; i++)
if (this.spectralStart == 0)
{ {
if (this.eof)
{
return;
}
this.DecodeBlockProgressiveDC( this.DecodeBlockProgressiveDC(
component, component,
ref block, ref Unsafe.Add(ref blockRef, i),
ref dcHuffmanTable); 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<Block8x8> 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( this.DecodeBlockProgressiveAC(
ref block, ref Unsafe.Add(ref blockRef, i),
ref acHuffmanTable, ref acHuffmanTable,
ref fastACRef); ref fastACRef);
}
// Every data block is an MCU, so countdown the restart interval // Every data block is an MCU, so countdown the restart interval
mcu++; mcu++;
if (!this.ContinueOnMcuComplete()) if (!this.ContinueOnMcuComplete())
{ {
return; return;
}
} }
} }
} }

22
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -52,17 +52,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// The DC Huffman tables /// The DC Huffman tables
/// </summary> /// </summary>
private HuffmanTables dcHuffmanTables; private HuffmanTable[] dcHuffmanTables;
/// <summary> /// <summary>
/// The AC Huffman tables /// The AC Huffman tables
/// </summary> /// </summary>
private HuffmanTables acHuffmanTables; private HuffmanTable[] acHuffmanTables;
/// <summary> /// <summary>
/// The fast AC tables used for entropy decoding /// The fast AC tables used for entropy decoding
/// </summary> /// </summary>
private FastACTables fastACTables; private FastACTable[] fastACTables;
/// <summary> /// <summary>
/// The reset interval determined by RST markers /// The reset interval determined by RST markers
@ -266,9 +266,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Only assign what we need // Only assign what we need
if (!metadataOnly) if (!metadataOnly)
{ {
this.dcHuffmanTables = new HuffmanTables(); const int maxTables = 4;
this.acHuffmanTables = new HuffmanTables(); this.dcHuffmanTables = new HuffmanTable[maxTables];
this.fastACTables = new FastACTables(this.configuration.MemoryAllocator); this.acHuffmanTables = new HuffmanTable[maxTables];
this.fastACTables = new FastACTable[maxTables];
} }
// Break only when we discover a valid EOI marker. // Break only when we discover a valid EOI marker.
@ -378,7 +379,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
this.InputStream?.Dispose(); this.InputStream?.Dispose();
this.Frame?.Dispose(); this.Frame?.Dispose();
this.fastACTables?.Dispose();
// Set large fields to null. // Set large fields to null.
this.InputStream = null; this.InputStream = null;
@ -872,12 +872,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
tableIndex, tableIndex,
codeLengths.GetSpan(), codeLengths.GetSpan(),
huffmanValues.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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="codeLengths">The codelengths</param> /// <param name="codeLengths">The codelengths</param>
/// <param name="values">The values</param> /// <param name="values">The values</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values) private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
=> tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values); => tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
/// <summary> /// <summary>

3
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -79,7 +79,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.Fuzz.ArgumentException826A, TestImages.Jpeg.Issues.Fuzz.ArgumentException826A,
TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, TestImages.Jpeg.Issues.Fuzz.ArgumentException826B,
TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, TestImages.Jpeg.Issues.Fuzz.ArgumentException826C,
TestImages.Jpeg.Issues.Fuzz.AccessViolationException827 TestImages.Jpeg.Issues.Fuzz.AccessViolationException827,
TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839
}; };
private static readonly Dictionary<string, float> CustomToleranceValues = private static readonly Dictionary<string, float> CustomToleranceValues =

1
tests/ImageSharp.Tests/TestImages.cs

@ -196,6 +196,7 @@ namespace SixLabors.ImageSharp.Tests
public const string ArgumentException826B = "Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg"; 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 ArgumentException826C = "Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg";
public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg";
public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg";
} }
} }

3
tests/Images/Input/Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b37ac1cc34680db9721542ef5354f7509860afd37888a24f6350f29f5b63eea1
size 397
Loading…
Cancel
Save