Browse Source

Merge branch 'master' into feature/histogramEqualization

pull/644/head
Brian Popow 8 years ago
committed by GitHub
parent
commit
3e08e30449
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      src/ImageSharp/Common/Helpers/InliningOptions.cs
  2. 26
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  3. 96
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs
  4. 24
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs
  5. 6
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs
  6. 8
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs
  7. 8
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs
  8. 10
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs
  9. 195
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs
  10. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs
  11. 866
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
  12. 958
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs
  13. 57
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs
  14. 17
      src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
  15. 52
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  16. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  17. 119
      tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs
  18. 1
      tests/ImageSharp.Tests/TestImages.cs
  19. 2
      tests/Images/External
  20. BIN
      tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg

22
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
{
/// <summary>
/// Global inlining options. Helps temporarily disable inling for better profiler output.
/// </summary>
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;
}
}

26
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
{
/// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// </summary>
/// <param name="errorMessage">The error message for the exception</param>
[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.");
}
}
}

96
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
{
/// <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.AllocateClean2D<short>(512, 4);
}
/// <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(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<short> GetTableSpan(int index)
{
return this.tables.GetRowSpan(index);
}
/// <summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="PdfJsFrameComponent.ACHuffmanTableId"/>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref short GetAcTableReference(PdfJsFrameComponent component)
{
return 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 void BuildACTableLut(int index, PdfJsHuffmanTables acHuffmanTables)
{
const int FastBits = ScanDecoder.FastBits;
Span<short> 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));
}
}
}
}
}
/// <inheritdoc />
public void Dispose()
{
this.tables?.Dispose();
this.tables = null;
}
}
}

24
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<FixedByteBuffer512, byte>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}

6
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs → src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs

@ -7,16 +7,16 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
[StructLayout(LayoutKind.Sequential)] [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] public short this[int idx]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
ref short self = ref Unsafe.As<FixedInt16Buffer256, short>(ref this); ref short self = ref Unsafe.As<FixedInt16Buffer257, short>(ref this);
return Unsafe.Add(ref self, idx); return Unsafe.Add(ref self, idx);
} }
} }

8
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs → src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs

@ -7,16 +7,16 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
[StructLayout(LayoutKind.Sequential)] [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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
ref long self = ref Unsafe.As<FixedInt64Buffer18, long>(ref this); ref int self = ref Unsafe.As<FixedInt32Buffer18, int>(ref this);
return Unsafe.Add(ref self, idx); return Unsafe.Add(ref self, idx);
} }
} }

8
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs → src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs

@ -7,16 +7,16 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
[StructLayout(LayoutKind.Sequential)] [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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
ref short self = ref Unsafe.As<FixedInt16Buffer18, short>(ref this); ref uint self = ref Unsafe.As<FixedUInt32Buffer18, uint>(ref this);
return Unsafe.Add(ref self, idx); return Unsafe.Add(ref self, idx);
} }
} }

10
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs

@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
} }
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(blocksPerColumnForMcu, blocksPerLineForMcu + 1, true); this.SpectralBlocks = this.memoryAllocator.AllocateClean2D<Block8x8>(blocksPerColumnForMcu, blocksPerLineForMcu + 1);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -144,5 +144,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
return 64 * (((this.WidthInBlocks + 1) * row) + 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<Block8x8, short>(ref blockRef);
}
} }
} }

195
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs

@ -17,163 +17,114 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <summary> /// <summary>
/// Gets the max code array /// Gets the max code array
/// </summary> /// </summary>
public FixedInt64Buffer18 MaxCode; public FixedUInt32Buffer18 MaxCode;
/// <summary> /// <summary>
/// Gets the value offset array /// Gets the value offset array
/// </summary> /// </summary>
public FixedInt16Buffer18 ValOffset; public FixedInt32Buffer18 ValOffset;
/// <summary> /// <summary>
/// Gets the huffman value array /// Gets the huffman value array
/// </summary> /// </summary>
public FixedByteBuffer256 HuffVal; public FixedByteBuffer256 Values;
/// <summary> /// <summary>
/// Gets the lookahead array /// Gets the lookahead array
/// </summary> /// </summary>
public FixedInt16Buffer256 Lookahead; public FixedByteBuffer512 Lookahead;
/// <summary>
/// Gets the sizes array
/// </summary>
public FixedInt16Buffer257 Sizes;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct. /// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param> /// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="lengths">The code lengths</param> /// <param name="count">The code lengths</param>
/// <param name="values">The huffman values</param> /// <param name="values">The huffman values</param>
public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> lengths, ReadOnlySpan<byte> values) public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> count, ReadOnlySpan<byte> values)
{ {
const int length = 257; const int Length = 257;
using (IBuffer<short> huffsize = memoryAllocator.Allocate<short>(length)) using (IBuffer<short> huffcode = memoryAllocator.Allocate<short>(Length))
using (IBuffer<short> huffcode = memoryAllocator.Allocate<short>(length))
{ {
ref short huffsizeRef = ref MemoryMarshal.GetReference(huffsize.GetSpan());
ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan()); ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan());
GenerateSizeTable(lengths, ref huffsizeRef); // Figure C.1: make table of Huffman code length for each symbol
GenerateCodeTable(ref huffsizeRef, ref huffcodeRef, length); fixed (short* sizesRef = this.Sizes.Data)
this.GenerateDecoderTables(lengths, ref huffcodeRef);
this.GenerateLookaheadTables(lengths, values, ref huffcodeRef);
}
fixed (byte* huffValRef = this.HuffVal.Data)
{
var huffValSpan = new Span<byte>(huffValRef, 256);
values.CopyTo(huffValSpan);
}
}
/// <summary>
/// Figure C.1: make table of Huffman code length for each symbol
/// </summary>
/// <param name="lengths">The code lengths</param>
/// <param name="huffsizeRef">The huffman size span ref</param>
private static void GenerateSizeTable(ReadOnlySpan<byte> 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;
}
/// <summary>
/// Figure C.2: generate the codes themselves
/// </summary>
/// <param name="huffsizeRef">The huffman size span ref</param>
/// <param name="huffcodeRef">The huffman code span ref</param>
/// <param name="length">The length of the huffsize span</param>
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++;
}
}
/// <summary>
/// Figure F.15: generate decoding tables for bit-sequential decoding
/// </summary>
/// <param name="lengths">The code lengths</param>
/// <param name="huffcodeRef">The huffman code span ref</param>
private void GenerateDecoderTables(ReadOnlySpan<byte> 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++)
{ {
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 byte l = count[i];
valOffsetRef[i] = (short)(bitcount - Unsafe.Add(ref huffcodeRef, bitcount)); for (short j = 0; j < l; j++)
bitcount += lengths[i]; {
maxcodeRef[i] = Unsafe.Add(ref huffcodeRef, bitcount - 1); // maximum code of length i sizesRef[x] = i;
} x++;
else }
{
maxcodeRef[i] = -1; // -1 if no codes of this length
} }
}
valOffsetRef[17] = 0; sizesRef[x] = 0;
maxcodeRef[17] = 0xFFFFFL;
}
}
/// <summary> // Figure C.2: generate the codes themselves
/// Generates lookup tables to speed up decoding int k = 0;
/// </summary> fixed (int* valOffsetRef = this.ValOffset.Data)
/// <param name="lengths">The code lengths</param> fixed (uint* maxcodeRef = this.MaxCode.Data)
/// <param name="huffval">The huffman value array</param> {
/// <param name="huffcodeRef">The huffman code span ref</param> uint code = 0;
private void GenerateLookaheadTables(ReadOnlySpan<byte> lengths, ReadOnlySpan<byte> huffval, ref short huffcodeRef) int j;
{ for (j = 1; j < 17; j++)
// 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 // Compute delta to add to code to compute symbol id.
// This should yield much faster scan decoding as usually, more than 95% of the Huffman codes valOffsetRef[j] = (int)(k - code);
// will be 8 or fewer bits long and can be handled without looping. if (sizesRef[k] == j)
fixed (short* lookaheadRef = this.Lookahead.Data) {
{ while (sizesRef[k] == j)
var lookaheadSpan = new Span<short>(lookaheadRef, 256); {
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; // Generate non-spec lookup tables to speed up decoding.
for (int l = 1; l <= 8; l++) fixed (byte* lookaheadRef = this.Lookahead.Data)
{
for (int i = 1; i <= lengths[l]; i++, p++)
{ {
// l = current code's length, p = its index in huffcode[] & huffval[]. const int FastBits = ScanDecoder.FastBits;
// Generate left-justified code followed by all possible bit sequences var fast = new Span<byte>(lookaheadRef, 1 << FastBits);
int lookBits = Unsafe.Add(ref huffcodeRef, p) << (8 - l); fast.Fill(0xFF); // Flag for non-accelerated
for (int ctr = 1 << (8 - l); ctr > 0; ctr--)
for (int i = 0; i < k; i++)
{ {
lookaheadRef[lookBits] = (short)((l << 8) | huffval[p]); int s = sizesRef[i];
lookBits++; 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<byte>(huffValRef, 256);
values.CopyTo(huffValSpan);
}
} }
} }
} }

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// Gets or sets the table at the given index. /// Gets or sets the table at the given index.
/// </summary> /// </summary>
/// <param name="index">The index</param> /// <param name="index">The index</param>
/// <returns>The <see cref="List{HuffmanBranch}"/></returns> /// <returns>The <see cref="PdfJsHuffmanTable"/></returns>
public ref PdfJsHuffmanTable this[int index] public ref PdfJsHuffmanTable this[int index]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

866
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs

@ -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
{
/// <summary>
/// Provides the means to decode a spectral scan
/// </summary>
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;
/// <summary>
/// Decodes the spectral scan
/// </summary>
/// <param name="frame">The image frame</param>
/// <param name="stream">The input stream</param>
/// <param name="dcHuffmanTables">The DC Huffman tables</param>
/// <param name="acHuffmanTables">The AC Huffman tables</param>
/// <param name="components">The scan components</param>
/// <param name="componentIndex">The component index within the array</param>
/// <param name="componentsLength">The length of the components. Different to the array length</param>
/// <param name="resetInterval">The reset interval</param>
/// <param name="spectralStart">The spectral selection start</param>
/// <param name="spectralEnd">The spectral selection end</param>
/// <param name="successivePrev">The successive approximation bit high end</param>
/// <param name="successive">The successive approximation bit low end</param>
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<Block8x8, short>(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<Block8x8, short>(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<Block8x8, short>(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<Block8x8, short>(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;
}
}
}
}
}

958
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
{
/// <summary>
/// Decodes the Huffman encoded spectral scan.
/// Originally ported from <see href="https://github.com/rds1983/StbSharp"/>
/// with additional fixes for both performance and common encoding errors.
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="ScanDecoder"/> class.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="frame">The image frame.</param>
/// <param name="dcHuffmanTables">The DC Huffman tables.</param>
/// <param name="acHuffmanTables">The AC Huffman tables.</param>
/// <param name="fastACTables">The fast AC decoding tables.</param>
/// <param name="componentIndex">The component index within the array.</param>
/// <param name="componentsLength">The length of the components. Different to the array length.</param>
/// <param name="restartInterval">The reset interval.</param>
/// <param name="spectralStart">The spectral selection start.</param>
/// <param name="spectralEnd">The spectral selection end.</param>
/// <param name="successiveHigh">The successive approximation bit high end.</param>
/// <param name="successiveLow">The successive approximation bit low end.</param>
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;
}
/// <summary>
/// Decodes the entropy coded data.
/// </summary>
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;
}
}
}
}
/// <summary>
/// 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
/// </summary>
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;
}
}
}
}
/// <summary>
/// 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
/// </summary>
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;
}
}
}

57
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

@ -58,6 +58,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary> /// </summary>
private PdfJsHuffmanTables acHuffmanTables; private PdfJsHuffmanTables acHuffmanTables;
/// <summary>
/// The fast AC tables used for entropy decoding
/// </summary>
private FastACTables fastACTables;
/// <summary> /// <summary>
/// The reset interval determined by RST markers /// The reset interval determined by RST markers
/// </summary> /// </summary>
@ -239,6 +244,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.QuantizationTables = new Block8x8F[4]; this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new PdfJsHuffmanTables(); this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables(); this.acHuffmanTables = new PdfJsHuffmanTables();
this.fastACTables = new FastACTables(this.configuration.MemoryAllocator);
} }
while (fileMarker.Marker != JpegConstants.Markers.EOI) while (fileMarker.Marker != JpegConstants.Markers.EOI)
@ -353,12 +359,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{ {
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;
this.Frame = null; this.Frame = null;
this.dcHuffmanTables = null; this.dcHuffmanTables = null;
this.acHuffmanTables = null; this.acHuffmanTables = null;
this.fastACTables = null;
} }
/// <summary> /// <summary>
@ -723,11 +731,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
i += 17 + codeLengthSum; i += 17 + codeLengthSum;
int tableType = huffmanTableSpec >> 4;
int tableIndex = huffmanTableSpec & 15;
this.BuildHuffmanTable( this.BuildHuffmanTable(
huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables,
huffmanTableSpec & 15, tableIndex,
codeLengths.GetSpan(), codeLengths.GetSpan(),
huffmanValues.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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
int spectralStart = this.temp[0]; int spectralStart = this.temp[0];
int spectralEnd = this.temp[1]; int spectralEnd = this.temp[1];
int successiveApproximation = this.temp[2]; int successiveApproximation = this.temp[2];
PdfJsScanDecoder scanDecoder = default;
var sd = new ScanDecoder(
scanDecoder.DecodeScan( this.InputStream,
this.Frame, this.Frame,
this.InputStream, this.dcHuffmanTables,
this.dcHuffmanTables, this.acHuffmanTables,
this.acHuffmanTables, this.fastACTables,
this.Frame.Components, componentIndex,
componentIndex, selectorsCount,
selectorsCount, this.resetInterval,
this.resetInterval, spectralStart,
spectralStart, spectralEnd,
spectralEnd, successiveApproximation >> 4,
successiveApproximation >> 4, successiveApproximation & 15);
successiveApproximation & 15);
sd.ParseEntropyCodedData();
} }
/// <summary> /// <summary>
@ -827,6 +845,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);
} }
/// <summary>
/// Post processes the pixels into the destination image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
private Image<TPixel> PostProcessIntoImage<TPixel>() private Image<TPixel> PostProcessIntoImage<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {

17
src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs

@ -167,11 +167,22 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// <returns>True if the profile is valid; False otherwise</returns> /// <returns>True if the profile is valid; False otherwise</returns>
public bool CheckIsValid() public bool CheckIsValid()
{ {
return Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) && const int minSize = 128;
const int maxSize = 50_000_000; // it's unlikely there is a profile bigger than 50MB
bool arrayValid = true;
if (this.data != null)
{
arrayValid = this.data.Length >= minSize &&
this.data.Length >= this.Header.Size;
}
return arrayValid &&
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) &&
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) && Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) &&
Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) && Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) &&
this.Header.Size >= 128 && this.Header.Size >= minSize &&
this.Header.Size < 50_000_000; // it's unlikely there is a profile bigger than 50MB this.Header.Size < maxSize;
} }
/// <summary> /// <summary>

52
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();
}
}
}
}

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

@ -38,6 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.NoEoiProgressive517, TestImages.Jpeg.Issues.NoEoiProgressive517,
TestImages.Jpeg.Issues.BadRstProgressive518, TestImages.Jpeg.Issues.BadRstProgressive518,
TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159,
TestImages.Jpeg.Issues.DhtHasWrongLength624,
}; };
/// <summary> /// <summary>

119
tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs

@ -99,12 +99,12 @@ namespace SixLabors.ImageSharp.Tests
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Nr of tag table entries (0) // Nr of tag table entries
(byte)(nrOfEntries >> 24), (byte)(nrOfEntries >> 16), (byte)(nrOfEntries >> 8), (byte)nrOfEntries (byte)(nrOfEntries >> 24), (byte)(nrOfEntries >> 16), (byte)(nrOfEntries >> 8), (byte)nrOfEntries
}); });
} }
public static byte[] Profile_Random_Array = ArrayHelper.Concat(CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat(CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array),
new byte[] new byte[]
{ {
0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown)
@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests
IccTestDataTagDataEntry.Unknown_Arr IccTestDataTagDataEntry.Unknown_Arr
); );
public static IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168, public static readonly IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168,
#if !NETSTANDARD1_1 #if !NETSTANDARD1_1
Profile_Random_Id_Value, Profile_Random_Id_Value,
#else #else
@ -132,41 +132,110 @@ namespace SixLabors.ImageSharp.Tests
IccTestDataTagDataEntry.Unknown_Val IccTestDataTagDataEntry.Unknown_Val
}); });
public static byte[] Header_Corrupt1_Array = public static readonly byte[] Header_CorruptDataColorSpace_Array =
{ {
0x81, 0xB1, 0x81, 0xE4, 0x82, 0x16, 0x82, 0x49, 0x82, 0x7B, 0x82, 0xAD, 0x82, 0xDF, 0x83, 0x11, 0x00, 0x00, 0x00, 0x80, // Size
0x83, 0x43, 0x83, 0x75, 0x83, 0xA7, 0x83, 0xD8, 0x84, 0x0A, 0x84, 0x3B, 0x84, 0x6C, 0x84, 0x9E, 0x61, 0x62, 0x63, 0x64, // CmmType
0x84, 0xCF, 0x85, 0x00, 0x85, 0x31, 0x85, 0x62, 0x85, 0x93, 0x85, 0xC3, 0x85, 0xF4, 0x86, 0x24, 0x04, 0x30, 0x00, 0x00, // Version
0x86, 0x55, 0x86, 0x85, 0x86, 0xB5, 0x86, 0xE6, 0x87, 0x16, 0x87, 0x46, 0x87, 0x76, 0x87, 0xA5, 0x6D, 0x6E, 0x74, 0x72, // Class
0x87, 0xD5, 0x88, 0x05, 0x88, 0x34, 0x88, 0x64, 0x88, 0x93, 0x88, 0xC3, 0x88, 0xF2, 0x89, 0x21, 0x68, 0x45, 0x8D, 0x6A, // DataColorSpace
0x89, 0x50, 0x89, 0x7F, 0x89, 0xAE, 0x89, 0xDD, 0x8A, 0x0C, 0x8A, 0x3B, 0x8A, 0x69, 0x8A, 0x98, 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace
0x8A, 0xC6, 0x8A, 0xF5, 0x8B, 0x23, 0x8B, 0x51, 0x8B, 0x7F, 0x8B, 0xAE, 0x8B, 0xDC, 0x8C, 0x09, 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate
0x8C, 0x37, 0x8C, 0x65, 0x8C, 0x93, 0x8C, 0xC1, 0x8C, 0xEE, 0x8D, 0x1C, 0x8D, 0x49, 0x8D, 0x76, 0x61, 0x63, 0x73, 0x70, // FileSignature
0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature
0x00, 0x00, 0x00, 0x01, // Flags
0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer
0x3A, 0xDE, 0x68, 0xB1, // DeviceModel
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes
0x00, 0x00, 0x00, 0x03, // RenderingIntent
0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant
0x64, 0x63, 0x62, 0x61, // CreatorSignature
// Profile ID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
}; };
public static byte[] Header_Corrupt2_Array = public static readonly byte[] Header_CorruptProfileConnectionSpace_Array =
{ {
0x23, 0x74, 0x6D, 0x6D, 0xB1, 0xBC, 0x28, 0xB2, 0x6D, 0x0B, 0xA3, 0x9C, 0x2D, 0x60, 0x6C, 0xB4, 0x00, 0x00, 0x00, 0x80, // Size
0x96, 0xF2, 0x31, 0x88, 0x6C, 0x67, 0x8B, 0xA9, 0x35, 0x31, 0x6C, 0x24, 0x81, 0xAE, 0x38, 0x64, 0x62, 0x63, 0x64, 0x65, // CmmType
0x6B, 0xE9, 0x78, 0xEC, 0x3B, 0x28, 0x6B, 0xB7, 0x71, 0x4F, 0x3D, 0x87, 0x6B, 0x8C, 0x6A, 0xC3, 0x04, 0x30, 0x00, 0x00, // Version
0x3F, 0x87, 0x6B, 0x68, 0x65, 0x33, 0x41, 0x30, 0x6B, 0x4A, 0x60, 0x8C, 0x42, 0x8C, 0x6B, 0x32, 0x6D, 0x6E, 0x74, 0x72, // Class
0x5C, 0xB8, 0x43, 0xA2, 0x6B, 0x1F, 0x59, 0xA4, 0x44, 0x79, 0x6B, 0x10, 0x57, 0x3B, 0x45, 0x1A, 0x52, 0x47, 0x42, 0x20, // DataColorSpace
0x6B, 0x05, 0x55, 0x68, 0x45, 0x8D, 0x6A, 0xFE, 0x54, 0x15, 0x45, 0xDA, 0x6A, 0xF9, 0x53, 0x2A, 0x68, 0x45, 0x8D, 0x6A, // ProfileConnectionSpace
0x46, 0x16, 0x6A, 0xF5, 0x52, 0x74, 0x46, 0x27, 0x6A, 0xF4, 0x52, 0x43, 0x46, 0x27, 0x6A, 0xF4, 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate
0x52, 0x43, 0x46, 0x27, 0x6A, 0xF4, 0x52, 0x43, 0x46, 0x27, 0x6A, 0xF4, 0x52, 0x43, 0x46, 0x27, 0x61, 0x63, 0x73, 0x70, // FileSignature
0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature
0x00, 0x00, 0x00, 0x01, // Flags
0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer
0x3A, 0xDE, 0x68, 0xB1, // DeviceModel
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes
0x00, 0x00, 0x00, 0x03, // RenderingIntent
0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant
0x64, 0x63, 0x62, 0x61, // CreatorSignature
// Profile ID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
}; };
public static readonly byte[] Header_CorruptRenderingIntent_Array =
{
0x00, 0x00, 0x00, 0x80, // Size
0x63, 0x64, 0x65, 0x66, // CmmType
0x04, 0x30, 0x00, 0x00, // Version
0x6D, 0x6E, 0x74, 0x72, // Class
0x52, 0x47, 0x42, 0x20, // DataColorSpace
0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace
0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate
0x61, 0x63, 0x73, 0x70, // FileSignature
0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature
0x00, 0x00, 0x00, 0x01, // Flags
0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer
0x3A, 0xDE, 0x68, 0xB1, // DeviceModel
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes
0x33, 0x41, 0x30, 0x6B, // RenderingIntent
0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant
0x64, 0x63, 0x62, 0x61, // CreatorSignature
// Profile ID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
public static readonly byte[] Header_DataTooSmall_Array = new byte[127];
public static readonly byte[] Header_InvalidSizeSmall_Array = CreateHeaderRandomArray(127, 0, Header_Random_Id_Array);
public static readonly byte[] Header_InvalidSizeBig_Array = CreateHeaderRandomArray(50_000_000, 0, Header_Random_Id_Array);
public static readonly byte[] Header_SizeBiggerThanData_Array = CreateHeaderRandomArray(160, 0, Header_Random_Id_Array);
public static object[][] ProfileIdTestData = public static readonly object[][] ProfileIdTestData =
{ {
new object[] { Header_Random_Array, Header_Random_Id_Value }, new object[] { Header_Random_Array, Header_Random_Id_Value },
new object[] { Profile_Random_Array, Profile_Random_Id_Value }, new object[] { Profile_Random_Array, Profile_Random_Id_Value },
}; };
public static object[][] ProfileValidityTestData = public static readonly object[][] ProfileValidityTestData =
{ {
new object[] { Header_Corrupt1_Array, false }, new object[] { Header_CorruptDataColorSpace_Array, false },
new object[] { Header_Corrupt2_Array, false }, new object[] { Header_CorruptProfileConnectionSpace_Array, false },
new object[] { Header_CorruptRenderingIntent_Array, false },
new object[] { Header_DataTooSmall_Array, false },
new object[] { Header_InvalidSizeSmall_Array, false },
new object[] { Header_InvalidSizeBig_Array, false },
new object[] { Header_SizeBiggerThanData_Array, false },
new object[] { Header_Random_Array, true }, new object[] { Header_Random_Array, true },
}; };
} }

1
tests/ImageSharp.Tests/TestImages.cs

@ -144,6 +144,7 @@ namespace SixLabors.ImageSharp.Tests
public const string NoEoiProgressive517 = "Jpg/issues/Issue517-No-EOI-Progressive.jpg"; 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 BadRstProgressive518 = "Jpg/issues/Issue518-Bad-RST-Progressive.jpg";
public const string InvalidCast520 = "Jpg/issues/Issue520-InvalidCast.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(); public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray();

2
tests/Images/External

@ -1 +1 @@
Subproject commit d9d93bbdd18dd7b818c0d19cc8f967be98045d3c Subproject commit 98fb7e2e4d5935b1c733bd2b206b6145b71ef378

BIN
tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Loading…
Cancel
Save