From 285f8dde83c937142a5a6584e9698e0dececea8e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 9 May 2018 01:30:11 +1000 Subject: [PATCH 01/32] Begin porting stb_image --- .../Jpeg/PdfJsPort/Components/FastACTables.cs | 34 + .../Components/FixedInt16Buffer257.cs | 24 + .../Components/PdfJsFrameComponent.cs | 4 +- .../PdfJsPort/Components/PdfJsHuffmanTable.cs | 70 +- .../Components/PdfJsHuffmanTables.cs | 2 +- .../PdfJsPort/Components/PdfJsScanDecoder.cs | 17 +- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 628 ++++++++++++++++++ .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 59 +- 8 files changed, 798 insertions(+), 40 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs create mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs create mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs new file mode 100644 index 0000000000..8d37c567ed --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components +{ + /// + /// The collection of tables used for fast AC entropy scan decoding. + /// + internal sealed class FastACTables : IDisposable + { + /// + /// Initializes a new instance of the class. + /// + /// The memory manager used to allocate memory for image processing operations. + public FastACTables(MemoryManager memoryManager) + { + this.Tables = memoryManager.AllocateClean2D(512, 4); + } + + /// + /// Gets the collection of tables. + /// + public Buffer2D Tables { get; } + + /// + public void Dispose() + { + this.Tables?.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs new file mode 100644 index 0000000000..b304dbf8c2 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.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 FixedInt16Buffer257 + { + public fixed short Data[257]; + + public short this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref short self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index 7f50a8529c..f063309eac 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -36,9 +36,9 @@ public PdfJsFrameComponent(MemoryManager memoryManager, PdfJsFrame frame, byte i public byte Id { get; } /// - /// Gets or sets Pred TODO: What does pred stand for? + /// Gets or sets DC coefficient predictor /// - public int Pred { get; set; } + public int DcPredictor { get; set; } /// /// Gets the horizontal sampling factor. diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 875a862638..0541de91b0 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -27,13 +27,18 @@ internal unsafe struct PdfJsHuffmanTable /// /// Gets the huffman value array /// - public FixedByteBuffer256 HuffVal; + public FixedByteBuffer256 Values; /// /// Gets the lookahead array /// public FixedInt16Buffer256 Lookahead; + /// + /// Gets the sizes array + /// + public FixedInt16Buffer257 Sizes; + /// /// Initializes a new instance of the struct. /// @@ -42,20 +47,18 @@ internal unsafe struct PdfJsHuffmanTable /// The huffman values public PdfJsHuffmanTable(MemoryManager memoryManager, ReadOnlySpan lengths, ReadOnlySpan values) { - const int length = 257; - using (IBuffer huffsize = memoryManager.Allocate(length)) - using (IBuffer huffcode = memoryManager.Allocate(length)) + const int Length = 257; + using (IBuffer huffcode = memoryManager.Allocate(Length)) { - ref short huffsizeRef = ref MemoryMarshal.GetReference(huffsize.Span); ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.Span); - GenerateSizeTable(lengths, ref huffsizeRef); - GenerateCodeTable(ref huffsizeRef, ref huffcodeRef, length); + this.GenerateSizeTable(lengths); + this.GenerateCodeTable(ref huffcodeRef, Length); this.GenerateDecoderTables(lengths, ref huffcodeRef); this.GenerateLookaheadTables(lengths, values, ref huffcodeRef); } - fixed (byte* huffValRef = this.HuffVal.Data) + fixed (byte* huffValRef = this.Values.Data) { var huffValSpan = new Span(huffValRef, 256); @@ -67,45 +70,49 @@ public PdfJsHuffmanTable(MemoryManager memoryManager, ReadOnlySpan lengths /// Figure C.1: make table of Huffman code length for each symbol /// /// The code lengths - /// The huffman size span ref - private static void GenerateSizeTable(ReadOnlySpan lengths, ref short huffsizeRef) + private void GenerateSizeTable(ReadOnlySpan lengths) { - short index = 0; - for (short l = 1; l <= 16; l++) + fixed (short* sizesRef = this.Sizes.Data) { - byte i = lengths[l]; - for (short j = 0; j < i; j++) + short index = 0; + for (short l = 1; l <= 16; l++) { - Unsafe.Add(ref huffsizeRef, index) = l; - index++; + byte i = lengths[l]; + for (short j = 0; j < i; j++) + { + sizesRef[index] = l; + index++; + } } - } - Unsafe.Add(ref huffsizeRef, index) = 0; + sizesRef[index] = 0; + } } /// /// Figure C.2: generate the codes themselves /// - /// The huffman size span ref /// The huffman code span ref /// The length of the huffsize span - private static void GenerateCodeTable(ref short huffsizeRef, ref short huffcodeRef, int length) + private void GenerateCodeTable(ref short huffcodeRef, int length) { - short k = 0; - short si = huffsizeRef; - short code = 0; - for (short i = 0; i < length; i++) + fixed (short* sizesRef = this.Sizes.Data) { - while (Unsafe.Add(ref huffsizeRef, k) == si) + short k = 0; + short si = sizesRef[0]; + short code = 0; + for (short i = 0; i < length; i++) { - Unsafe.Add(ref huffcodeRef, k) = code; - code++; - k++; - } + while (sizesRef[k] == si) + { + Unsafe.Add(ref huffcodeRef, k) = code; + code++; + k++; + } - code <<= 1; - si++; + code <<= 1; + si++; + } } } @@ -148,6 +155,7 @@ private void GenerateDecoderTables(ReadOnlySpan lengths, ref short huffcod /// The huffman code span ref private void GenerateLookaheadTables(ReadOnlySpan lengths, ReadOnlySpan huffval, ref short huffcodeRef) { + // TODO: Rewrite this to match stb_Image // TODO: This generation code matches the libJpeg code but the lookahead table is not actually used yet. // To use it we need to implement fast lookup path in PdfJsScanDecoder.DecodeHuffman // This should yield much faster scan decoding as usually, more than 95% of the Huffman codes diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs index 3a559bb864..5cbde2b88c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs @@ -17,7 +17,7 @@ internal sealed class PdfJsHuffmanTables /// Gets or sets the table at the given index. /// /// The index - /// The + /// The public ref PdfJsHuffmanTable this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs index c6b14d6fb0..62c8f984f0 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs @@ -107,7 +107,7 @@ public void DecodeScan( for (int i = 0; i < components.Length; i++) { PdfJsFrameComponent c = components[i]; - c.Pred = 0; + c.DcPredictor = 0; } this.eobrun = 0; @@ -552,7 +552,7 @@ private bool TryDecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamRe } int j = tree.ValOffset[i]; - value = tree.HuffVal[(j + code) & 0xFF]; + value = tree.Values[(j + code) & 0xFF]; return true; } @@ -618,7 +618,7 @@ private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRe } } - Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff); + Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff); int k = 1; while (k < 64) @@ -673,7 +673,7 @@ private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef } } - Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff << this.successiveState); + Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff << this.successiveState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -860,5 +860,14 @@ private void DecodeACSuccessive(ref short blockDataRef, int offset, ref PdfJsHuf } } } + + private void Reset() + { + // 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; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs new file mode 100644 index 0000000000..af7233bfe8 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -0,0 +1,628 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components +{ + internal class ScanDecoder + { + public const int FastBits = 9; + + // bmask[n] = (1 << n) - 1 + private static readonly uint[] stbi__bmask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 }; + + // bias[n] = (-1 << n) + 1 + private static readonly int[] stbi__jbias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 }; + + private readonly DoubleBufferedStreamReader stream; + private readonly PdfJsFrameComponent[] components; + private readonly ZigZag dctZigZag; + private int codeBits; + private uint codeBuffer; + private bool nomore; + private byte marker; + + private int todo; + private int restartInterval; + private int componentIndex; + private int componentsLength; + private int eobrun; + private int spectralStart; + private int spectralEnd; + private int successiveHigh; + private int successiveLow; + + /// + /// Initializes a new instance of the class. + /// + /// The input stream + /// The scan components + /// The component index within the array + /// The length of the components. Different to the array length + /// The reset interval + /// The spectral selection start + /// The spectral selection end + /// The successive approximation bit high end + /// The successive approximation bit low end + public ScanDecoder( + DoubleBufferedStreamReader stream, + PdfJsFrameComponent[] components, + int componentIndex, + int componentsLength, + int restartInterval, + int spectralStart, + int spectralEnd, + int successiveHigh, + int successiveLow) + { + this.dctZigZag = ZigZag.CreateUnzigTable(); + this.stream = stream; + this.components = components; + this.marker = PdfJsJpegConstants.Markers.Prefix; + this.componentIndex = componentIndex; + this.componentsLength = componentsLength; + this.restartInterval = restartInterval; + this.spectralStart = spectralStart; + this.spectralEnd = spectralEnd; + this.successiveHigh = successiveHigh; + this.successiveLow = successiveLow; + } + + /// + /// Decodes the entropy coded data. + /// + /// The image frame. + /// The DC Huffman tables. + /// The AC Huffman tables. + /// The fast AC decoding tables. + /// The + public int ParseEntropyCodedData( + PdfJsFrame frame, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + FastACTables fastACTables) + { + this.Reset(); + + if (!frame.Progressive) + { + if (this.componentsLength == 1) + { + int i, j; + int n = this.componentIndex; + PdfJsFrameComponent component = this.components[n]; + + // 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 + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + + int mcu = 0; + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + int blockRow = mcu / w; + int blockCol = mcu % w; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + mcu++; + } + } + } + } + + return 1; + } + + private int DecodeBlock( + PdfJsFrameComponent component, + ref short blockDataRef, + ref PdfJsHuffmanTable dcTable, + ref PdfJsHuffmanTable acTable, + Span fac) + { + if (this.codeBits < 16) + { + this.GrowBufferUnsafe(); + } + + int t = this.DecodeHuffman(ref dcTable); + + if (t < 0) + { + throw new ImageFormatException("Bad Huffman code"); + } + + 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 = fac[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) + { + throw new ImageFormatException("Bad Huffman code"); + } + + 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); + + return 1; + } + + private int DecodeBlockProgressiveDC( + PdfJsFrameComponent component, + ref short blockDataRef, + ref PdfJsHuffmanTable dcTable) + { + if (this.spectralEnd != 0) + { + throw new ImageFormatException("Can't merge DC and AC."); + } + + this.CheckBits(); + + 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); + } + } + + return 1; + } + + private int DecodeBlockProgressiveAC( + PdfJsFrameComponent component, + ref short blockDataRef, + ref PdfJsHuffmanTable acTable, + Span fac) + { + int k; + + if (this.spectralStart == 0) + { + throw new ImageFormatException("Can't merge DC and AC."); + } + + if (this.successiveHigh == 0) + { + int shift = this.successiveLow; + + if (this.eobrun > 0) + { + this.eobrun--; + return 1; + } + + k = this.spectralStart; + do + { + int zig; + int s; + + this.CheckBits(); + int c = this.PeekBits(); + int r = fac[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) + { + throw new ImageFormatException("Bad Huffman code."); + } + + 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 + 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) + { + throw new ImageFormatException("Bad Huffman code."); + } + + 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) + { + throw new ImageFormatException("Bad Huffman code."); + } + + // 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); + } + } + + return 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetBits(int n) + { + if (this.codeBits < n) + { + this.GrowBufferUnsafe(); + } + + uint k = this.Lrot(this.codeBuffer, n); + this.codeBuffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + this.codeBits -= n; + return (int)k; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetBit() + { + if (this.codeBits < 1) + { + this.GrowBufferUnsafe(); + } + + uint k = this.codeBuffer; + this.codeBuffer <<= 1; + this.codeBits--; + + return (int)(k & 0x80000000); + } + + private void GrowBufferUnsafe() + { + do + { + // TODO: EOF + uint b = (uint)(this.nomore ? 0 : this.stream.ReadByte()); + if (b == PdfJsJpegConstants.Markers.Prefix) + { + long position = this.stream.Position - 1; + int c = this.stream.ReadByte(); + while (c == PdfJsJpegConstants.Markers.Prefix) + { + if (c != 0) + { + this.marker = (byte)c; + this.nomore = true; + this.stream.Position = position; + return; + } + } + } + + this.codeBuffer |= b << (24 - this.codeBits); + this.codeBits += 8; + } + while (this.codeBits <= 24); + } + + 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 < byte.MaxValue) + { + int s = table.Sizes[k]; + if (s > this.codeBits) + { + return -1; + } + + this.codeBuffer <<= s; + this.codeBits -= s; + return table.Values[k]; + } + + // 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; + 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 + c = (int)((this.codeBuffer >> (32 - k)) & stbi__bmask[k]) + table.ValOffset[k]; + + // Convert the id to a symbol + this.codeBits -= k; + this.codeBuffer <<= k; + return table.Values[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ExtendReceive(int n) + { + if (this.codeBits < n) + { + this.GrowBufferUnsafe(); + } + + int sgn = (int)(this.codeBuffer >> 31); + uint k = this.Lrot(this.codeBuffer, n); + this.codeBuffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + this.codeBits -= n; + return (int)(k + (stbi__jbias[n] & ~sgn)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckBits() + { + if (this.codeBuffer < 16) + { + this.GrowBufferUnsafe(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int PeekBits() + { + return (int)(this.codeBuffer >> ((32 - FastBits) & ((1 << FastBits) - 1))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Lrot(uint x, int y) + { + return (x << y) | (x >> (32 - y)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsRestartMarker(byte x) + { + return x >= PdfJsJpegConstants.Markers.RST0 && x <= PdfJsJpegConstants.Markers.RST7; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Reset() + { + this.codeBits = 0; + this.codeBuffer = 0; + this.nomore = false; + + for (int i = 0; i < this.components.Length; i++) + { + PdfJsFrameComponent c = this.components[i]; + c.DcPredictor = 0; + } + + this.marker = PdfJsJpegConstants.Markers.Prefix; + this.todo = this.restartInterval > 0 ? this.restartInterval : 0x7FFFFFFF; + 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 + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index df803a9202..18a444c5bd 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -57,6 +57,11 @@ internal sealed class PdfJsJpegDecoderCore : IRawJpegData /// private PdfJsHuffmanTables acHuffmanTables; + /// + /// The fast AC tables used for entropy decoding + /// + private FastACTables fastACTables; + /// /// The reset interval determined by RST markers /// @@ -228,6 +233,7 @@ public void ParseStream(Stream stream, bool metadataOnly = false) this.QuantizationTables = new Block8x8F[4]; this.dcHuffmanTables = new PdfJsHuffmanTables(); this.acHuffmanTables = new PdfJsHuffmanTables(); + this.fastACTables = new FastACTables(this.configuration.MemoryManager); } while (fileMarker.Marker != PdfJsJpegConstants.Markers.EOI) @@ -341,12 +347,14 @@ public void Dispose() { this.InputStream?.Dispose(); this.Frame?.Dispose(); + this.fastACTables?.Dispose(); // Set large fields to null. this.InputStream = null; this.Frame = null; this.dcHuffmanTables = null; this.acHuffmanTables = null; + this.fastACTables = null; } /// @@ -714,11 +722,20 @@ private void ProcessDefineHuffmanTablesMarker(int remaining) i += 17 + codeLengthSum; + int tableType = huffmanTableSpec >> 4; + int tableIndex = huffmanTableSpec & 15; + this.BuildHuffmanTable( - huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, - huffmanTableSpec & 15, + tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, + tableIndex, codeLengths.Span, huffmanValues.Span); + + if (tableType != 0) + { + // Build a table that decodes both magnitude and value of small ACs in one go. + this.BuildFastACTable(tableIndex); + } } } } @@ -829,5 +846,43 @@ private Image PostProcessIntoImage() return image; } } + + private void BuildFastACTable(int index) + { + const int FastBits = ScanDecoder.FastBits; + Span fastac = this.fastACTables.Tables.GetRowSpan(index); + ref PdfJsHuffmanTable huffman = ref this.acHuffmanTables[index]; + + int i; + for (i = 0; i < (1 << FastBits); i++) + { + short fast = huffman.Lookahead[i]; + fastac[i] = 0; + if (fast < 255) + { + 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)); + } + } + } + } + } } } \ No newline at end of file From 71903c5dbaf7356426370950a417ce3d264bfef7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 9 May 2018 10:57:43 +1000 Subject: [PATCH 02/32] Minor cleanup --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index af7233bfe8..e9f91ef06c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -94,9 +94,7 @@ public int ParseEntropyCodedData( { if (this.componentsLength == 1) { - int i, j; - int n = this.componentIndex; - PdfJsFrameComponent component = this.components[n]; + PdfJsFrameComponent component = this.components[this.componentIndex]; // Non-interleaved data, we just need to process one block at a time, // in trivial scanline order @@ -110,18 +108,42 @@ public int ParseEntropyCodedData( Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); int mcu = 0; - for (j = 0; j < h; j++) + for (int j = 0; j < h; j++) { - for (i = 0; i < w; i++) + for (int i = 0; i < w; i++) { int blockRow = mcu / w; int blockCol = mcu % w; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); mcu++; + + // Every data block is an MCU, so countdown the restart interval + if (this.todo-- <= 0) + { + if (this.codeBits < 24) + { + this.GrowBufferUnsafe(); + } + + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.IsRestartMarker(this.marker)) + { + return 1; + } + + this.Reset(); + } } } } + else + { + // Interleaved + int i, j, k, x, y; + + } } return 1; From c8a9b30457ac9580d49aec0f1bfe427553a92886 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 10 May 2018 00:47:16 +1000 Subject: [PATCH 03/32] Wire up huffman tables. (doesn't work) --- .../Components/FixedByteBuffer512.cs | 24 ++++ .../PdfJsPort/Components/PdfJsHuffmanTable.cs | 126 +++++++++++++----- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 87 +++++++++--- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 50 ++++--- 4 files changed, 219 insertions(+), 68 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs new file mode 100644 index 0000000000..c509903c98 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct FixedByteBuffer512 + { + public fixed byte Data[1 << ScanDecoder.FastBits]; + + public byte this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref byte self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 0541de91b0..1cc342f5ac 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -32,7 +32,7 @@ internal unsafe struct PdfJsHuffmanTable /// /// Gets the lookahead array /// - public FixedInt16Buffer256 Lookahead; + public FixedByteBuffer512 Lookahead; /// /// Gets the sizes array @@ -43,19 +43,76 @@ internal unsafe struct PdfJsHuffmanTable /// Initializes a new instance of the struct. /// /// The to use for buffer allocations. - /// The code lengths + /// The code lengths /// The huffman values - public PdfJsHuffmanTable(MemoryManager memoryManager, ReadOnlySpan lengths, ReadOnlySpan values) + public PdfJsHuffmanTable(MemoryManager memoryManager, ReadOnlySpan count, ReadOnlySpan values) { const int Length = 257; using (IBuffer huffcode = memoryManager.Allocate(Length)) { + // Span codes = huffcode.Span; ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.Span); - this.GenerateSizeTable(lengths); - this.GenerateCodeTable(ref huffcodeRef, Length); - this.GenerateDecoderTables(lengths, ref huffcodeRef); - this.GenerateLookaheadTables(lengths, values, ref huffcodeRef); + this.GenerateSizeTable(count); + + //int k = 0; + //fixed (short* sizesRef = this.Sizes.Data) + //fixed (short* deltaRef = this.ValOffset.Data) + //fixed (long* maxcodeRef = this.MaxCode.Data) + //{ + // uint code = 0; + // int j; + // for (j = 1; j <= 16; j++) + // { + // // Compute delta to add to code to compute symbol id. + // deltaRef[j] = (short)(k - code); + // if (sizesRef[k] == j) + // { + // while (sizesRef[k] == j) + // { + // codes[k++] = (short)code++; + + // // Unsafe.Add(ref huffcodeRef, k++) = (short)code++; + + // // TODO: Throw if invalid? + // } + // } + + // // Compute largest code + 1 for this size. preshifted as neeed later. + // maxcodeRef[j] = code << (16 - j); + // code <<= 1; + // } + + // maxcodeRef[j] = 0xFFFFFFFF; + //} + + //fixed (short* lookaheadRef = this.Lookahead.Data) + //{ + // const int FastBits = ScanDecoder.FastBits; + // var fast = new Span(lookaheadRef, 1 << FastBits); + // fast.Fill(255); // Flag for non-accelerated + + // fixed (short* sizesRef = this.Sizes.Data) + // { + // for (int i = 0; i < k; i++) + // { + // int s = sizesRef[i]; + // if (s <= ScanDecoder.FastBits) + // { + // int c = codes[i] << (FastBits - s); + // int m = 1 << (FastBits - s); + // for (int j = 0; j < m; j++) + // { + // fast[c + j] = (byte)i; + // } + // } + // } + // } + //} + + this.GenerateCodeTable(ref huffcodeRef, Length, out int k); + this.GenerateDecoderTables(count, ref huffcodeRef); + this.GenerateLookaheadTables(count, values, ref huffcodeRef, k); } fixed (byte* huffValRef = this.Values.Data) @@ -74,18 +131,18 @@ private void GenerateSizeTable(ReadOnlySpan lengths) { fixed (short* sizesRef = this.Sizes.Data) { - short index = 0; - for (short l = 1; l <= 16; l++) + short k = 0; + for (short i = 1; i < 17; i++) { - byte i = lengths[l]; - for (short j = 0; j < i; j++) + byte l = lengths[i]; + for (short j = 0; j < l; j++) { - sizesRef[index] = l; - index++; + sizesRef[k] = i; + k++; } } - sizesRef[index] = 0; + sizesRef[k] = 0; } } @@ -94,11 +151,12 @@ private void GenerateSizeTable(ReadOnlySpan lengths) /// /// The huffman code span ref /// The length of the huffsize span - private void GenerateCodeTable(ref short huffcodeRef, int length) + /// The length of any valid codes + private void GenerateCodeTable(ref short huffcodeRef, int length, out int k) { fixed (short* sizesRef = this.Sizes.Data) { - short k = 0; + k = 0; short si = sizesRef[0]; short code = 0; for (short i = 0; i < length; i++) @@ -134,7 +192,7 @@ private void GenerateDecoderTables(ReadOnlySpan lengths, ref short huffcod // valOffsetRef[l] = huffcodeRef[] index of 1st symbol of code length i, minus the minimum code of length i valOffsetRef[i] = (short)(bitcount - Unsafe.Add(ref huffcodeRef, bitcount)); bitcount += lengths[i]; - maxcodeRef[i] = Unsafe.Add(ref huffcodeRef, bitcount - 1); // maximum code of length i + maxcodeRef[i] = Unsafe.Add(ref huffcodeRef, bitcount - 1) << (16 - i); // maximum code of length i preshifted for faster reading later } else { @@ -143,41 +201,43 @@ private void GenerateDecoderTables(ReadOnlySpan lengths, ref short huffcod } valOffsetRef[17] = 0; - maxcodeRef[17] = 0xFFFFFL; + maxcodeRef[17] = 0xFFFFFFFFL; } } /// - /// Generates lookup tables to speed up decoding + /// Generates non-spec lookup tables to speed up decoding /// /// The code lengths /// The huffman value array /// The huffman code span ref - private void GenerateLookaheadTables(ReadOnlySpan lengths, ReadOnlySpan huffval, ref short huffcodeRef) + /// The lengths of any valid codes + private void GenerateLookaheadTables(ReadOnlySpan lengths, ReadOnlySpan huffval, ref short huffcodeRef, int k) { // TODO: Rewrite this to match stb_Image // TODO: This generation code matches the libJpeg code but the lookahead table is not actually used yet. // To use it we need to implement fast lookup path in PdfJsScanDecoder.DecodeHuffman // This should yield much faster scan decoding as usually, more than 95% of the Huffman codes // will be 8 or fewer bits long and can be handled without looping. - fixed (short* lookaheadRef = this.Lookahead.Data) + fixed (byte* lookaheadRef = this.Lookahead.Data) { - var lookaheadSpan = new Span(lookaheadRef, 256); - - lookaheadSpan.Fill(2034); // 9 << 8; + const int FastBits = ScanDecoder.FastBits; + var lookaheadSpan = new Span(lookaheadRef, 1 << ScanDecoder.FastBits); - int p = 0; - for (int l = 1; l <= 8; l++) + lookaheadSpan.Fill(255); // Flag for non-accelerated + fixed (short* sizesRef = this.Sizes.Data) { - for (int i = 1; i <= lengths[l]; i++, p++) + for (int i = 0; i < k; ++i) { - // l = current code's length, p = its index in huffcode[] & huffval[]. - // Generate left-justified code followed by all possible bit sequences - int lookBits = Unsafe.Add(ref huffcodeRef, p) << (8 - l); - for (int ctr = 1 << (8 - l); ctr > 0; ctr--) + int s = sizesRef[i]; + if (s <= ScanDecoder.FastBits) { - lookaheadRef[lookBits] = (short)((l << 8) | huffval[p]); - lookBits++; + int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - s); + int m = 1 << (FastBits - s); + for (int j = 0; j < m; ++j) + { + lookaheadRef[c + j] = (byte)i; + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index e9f91ef06c..217b3cb62b 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -15,10 +15,10 @@ internal class ScanDecoder public const int FastBits = 9; // bmask[n] = (1 << n) - 1 - private static readonly uint[] stbi__bmask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 }; + private static readonly uint[] Bmask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 }; // bias[n] = (-1 << n) + 1 - private static readonly int[] stbi__jbias = { 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 DoubleBufferedStreamReader stream; private readonly PdfJsFrameComponent[] components; @@ -141,8 +141,61 @@ public int ParseEntropyCodedData( else { // Interleaved - int i, j, k, x, y; + int mcu = 0; + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = 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 short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + 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++) + { + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + } + } + } + + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + if (this.todo-- <= 0) + { + if (this.codeBits < 24) + { + this.GrowBufferUnsafe(); + } + + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.IsRestartMarker(this.marker)) + { + return 1; + } + this.Reset(); + } + } + } } } @@ -156,11 +209,7 @@ private int DecodeBlock( ref PdfJsHuffmanTable acTable, Span fac) { - if (this.codeBits < 16) - { - this.GrowBufferUnsafe(); - } - + this.CheckBits(); int t = this.DecodeHuffman(ref dcTable); if (t < 0) @@ -477,8 +526,8 @@ private int GetBits(int n) } uint k = this.Lrot(this.codeBuffer, n); - this.codeBuffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; + this.codeBuffer = k & ~Bmask[n]; + k &= Bmask[n]; this.codeBits -= n; return (int)k; } @@ -503,7 +552,7 @@ private void GrowBufferUnsafe() do { // TODO: EOF - uint b = (uint)(this.nomore ? 0 : this.stream.ReadByte()); + int b = this.nomore ? 0 : this.stream.ReadByte(); if (b == PdfJsJpegConstants.Markers.Prefix) { long position = this.stream.Position - 1; @@ -514,13 +563,17 @@ private void GrowBufferUnsafe() { this.marker = (byte)c; this.nomore = true; - this.stream.Position = position; + if (!this.IsRestartMarker(this.marker)) + { + this.stream.Position = position; + } + return; } } } - this.codeBuffer |= b << (24 - this.codeBits); + this.codeBuffer |= (uint)b << (24 - this.codeBits); this.codeBits += 8; } while (this.codeBits <= 24); @@ -575,7 +628,7 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) } // Convert the huffman code to the symbol id - c = (int)((this.codeBuffer >> (32 - k)) & stbi__bmask[k]) + table.ValOffset[k]; + c = (int)((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]; // Convert the id to a symbol this.codeBits -= k; @@ -593,10 +646,10 @@ private int ExtendReceive(int n) int sgn = (int)(this.codeBuffer >> 31); uint k = this.Lrot(this.codeBuffer, n); - this.codeBuffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; + this.codeBuffer = k & ~Bmask[n]; + k &= Bmask[n]; this.codeBits -= n; - return (int)(k + (stbi__jbias[n] & ~sgn)); + return (int)(k + (Bias[n] & ~sgn)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 18a444c5bd..5bf4ab5aa4 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -731,10 +731,10 @@ private void ProcessDefineHuffmanTablesMarker(int remaining) codeLengths.Span, huffmanValues.Span); - if (tableType != 0) + if (huffmanTableSpec >> 4 != 0) { // Build a table that decodes both magnitude and value of small ACs in one go. - this.BuildFastACTable(tableIndex); + this.BuildFastACTable(huffmanTableSpec & 15); } } } @@ -794,21 +794,35 @@ private void ProcessStartOfScanMarker() int spectralStart = this.temp[0]; int spectralEnd = this.temp[1]; int successiveApproximation = this.temp[2]; - var scanDecoder = default(PdfJsScanDecoder); - - scanDecoder.DecodeScan( - this.Frame, - this.InputStream, - this.dcHuffmanTables, - this.acHuffmanTables, - this.Frame.Components, - componentIndex, - selectorsCount, - this.resetInterval, - spectralStart, - spectralEnd, - successiveApproximation >> 4, - successiveApproximation & 15); + + var sd = new ScanDecoder( + this.InputStream, + this.Frame.Components, + componentIndex, + selectorsCount, + this.resetInterval, + spectralStart, + spectralEnd, + successiveApproximation >> 4, + successiveApproximation & 15); + + sd.ParseEntropyCodedData(this.Frame, this.dcHuffmanTables, this.acHuffmanTables, this.fastACTables); + + //var scanDecoder = default(PdfJsScanDecoder); + + //scanDecoder.DecodeScan( + // this.Frame, + // this.InputStream, + // this.dcHuffmanTables, + // this.acHuffmanTables, + // this.Frame.Components, + // componentIndex, + // selectorsCount, + // this.resetInterval, + // spectralStart, + // spectralEnd, + // successiveApproximation >> 4, + // successiveApproximation & 15); } /// @@ -856,7 +870,7 @@ private void BuildFastACTable(int index) int i; for (i = 0; i < (1 << FastBits); i++) { - short fast = huffman.Lookahead[i]; + byte fast = huffman.Lookahead[i]; fastac[i] = 0; if (fast < 255) { From 6d928f66401d7fd81208b1cb2ff1174932fffc97 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 11 May 2018 11:32:01 +1000 Subject: [PATCH 04/32] Meh --- .../Components/FixedByteBuffer257.cs | 24 ++++ .../PdfJsPort/Components/PdfJsHuffmanTable.cs | 120 +++++++++--------- .../PdfJsPort/Components/PdfJsScanDecoder.cs | 9 -- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 2 +- 4 files changed, 85 insertions(+), 70 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.cs diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.cs new file mode 100644 index 0000000000..3015243168 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.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 FixedByteBuffer257 + { + public fixed byte Data[257]; + + public byte this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref byte self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 1cc342f5ac..a63573030f 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -50,69 +50,69 @@ public PdfJsHuffmanTable(MemoryManager memoryManager, ReadOnlySpan count, const int Length = 257; using (IBuffer huffcode = memoryManager.Allocate(Length)) { - // Span codes = huffcode.Span; + Span codes = huffcode.Span; ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.Span); this.GenerateSizeTable(count); - //int k = 0; - //fixed (short* sizesRef = this.Sizes.Data) - //fixed (short* deltaRef = this.ValOffset.Data) - //fixed (long* maxcodeRef = this.MaxCode.Data) - //{ - // uint code = 0; - // int j; - // for (j = 1; j <= 16; j++) - // { - // // Compute delta to add to code to compute symbol id. - // deltaRef[j] = (short)(k - code); - // if (sizesRef[k] == j) - // { - // while (sizesRef[k] == j) - // { - // codes[k++] = (short)code++; - - // // Unsafe.Add(ref huffcodeRef, k++) = (short)code++; - - // // TODO: Throw if invalid? - // } - // } - - // // Compute largest code + 1 for this size. preshifted as neeed later. - // maxcodeRef[j] = code << (16 - j); - // code <<= 1; - // } - - // maxcodeRef[j] = 0xFFFFFFFF; - //} - - //fixed (short* lookaheadRef = this.Lookahead.Data) - //{ - // const int FastBits = ScanDecoder.FastBits; - // var fast = new Span(lookaheadRef, 1 << FastBits); - // fast.Fill(255); // Flag for non-accelerated - - // fixed (short* sizesRef = this.Sizes.Data) - // { - // for (int i = 0; i < k; i++) - // { - // int s = sizesRef[i]; - // if (s <= ScanDecoder.FastBits) - // { - // int c = codes[i] << (FastBits - s); - // int m = 1 << (FastBits - s); - // for (int j = 0; j < m; j++) - // { - // fast[c + j] = (byte)i; - // } - // } - // } - // } - //} - - this.GenerateCodeTable(ref huffcodeRef, Length, out int k); - this.GenerateDecoderTables(count, ref huffcodeRef); - this.GenerateLookaheadTables(count, values, ref huffcodeRef, k); + int k = 0; + fixed (short* size = this.Sizes.Data) + fixed (short* delta = this.ValOffset.Data) + fixed (long* maxcode = this.MaxCode.Data) + { + uint code = 0; + int j; + for (j = 1; j <= 16; j++) + { + // Compute delta to add to code to compute symbol id. + delta[j] = (short)(k - code); + if (size[k] == j) + { + while (size[k] == j) + { + codes[k++] = (short)code++; + + // Unsafe.Add(ref huffcodeRef, k++) = (short)code++; + + // TODO: Throw if invalid? + } + } + + // Compute largest code + 1 for this size. preshifted as neeed later. + maxcode[j] = code << (16 - j); + code <<= 1; + } + + maxcode[j] = 0xFFFFFFFF; + } + + fixed (byte* lookaheadRef = this.Lookahead.Data) + { + const int FastBits = ScanDecoder.FastBits; + var fast = new Span(lookaheadRef, 1 << FastBits); + fast.Fill(255); // Flag for non-accelerated + + fixed (short* sizesRef = this.Sizes.Data) + { + for (int i = 0; i < k; i++) + { + int s = sizesRef[i]; + if (s <= ScanDecoder.FastBits) + { + int c = codes[i] << (FastBits - s); + int m = 1 << (FastBits - s); + for (int j = 0; j < m; j++) + { + fast[c + j] = (byte)i; + } + } + } + } + } + + // this.GenerateCodeTable(ref huffcodeRef, Length, out int k); + // this.GenerateDecoderTables(count, ref huffcodeRef); + // this.GenerateLookaheadTables(count, values, ref huffcodeRef, k); } fixed (byte* huffValRef = this.Values.Data) @@ -224,7 +224,7 @@ private void GenerateLookaheadTables(ReadOnlySpan lengths, ReadOnlySpan(lookaheadRef, 1 << ScanDecoder.FastBits); - lookaheadSpan.Fill(255); // Flag for non-accelerated + lookaheadSpan.Fill(byte.MaxValue); // Flag for non-accelerated fixed (short* sizesRef = this.Sizes.Data) { for (int i = 0; i < k; ++i) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs index 62c8f984f0..0736fd342b 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs @@ -860,14 +860,5 @@ private void DecodeACSuccessive(ref short blockDataRef, int offset, ref PdfJsHuf } } } - - private void Reset() - { - // 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; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 217b3cb62b..8322be2fe3 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -607,7 +607,7 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) // 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; - for (k = FastBits + 1; ; ++k) + for (k = FastBits + 1; ; k++) { if (temp < table.MaxCode[k]) { From f064d955c2b96cc6fcc169cf106631e8de31e4c8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 31 May 2018 16:16:03 +1000 Subject: [PATCH 05/32] Baseline decoding seems to work --- .../Components/FixedInt16Buffer18.cs | 6 +- .../Components/FixedInt64Buffer18.cs | 6 +- .../PdfJsPort/Components/PdfJsHuffmanTable.cs | 22 +++--- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 70 ++++++++++--------- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 6 +- 5 files changed, 58 insertions(+), 52 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs index 20d4b77336..b193bf59e6 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs @@ -9,14 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [StructLayout(LayoutKind.Sequential)] internal unsafe struct FixedInt16Buffer18 { - public fixed short Data[18]; + public fixed int Data[18]; - public short this[int idx] + public int this[int idx] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - ref short self = ref Unsafe.As(ref this); + ref int self = ref Unsafe.As(ref this); return Unsafe.Add(ref self, idx); } } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs index 51381cb27a..a9266bd6b1 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs @@ -9,14 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [StructLayout(LayoutKind.Sequential)] internal unsafe struct FixedInt64Buffer18 { - public fixed long Data[18]; + public fixed uint Data[18]; - public long this[int idx] + public uint this[int idx] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - ref long self = ref Unsafe.As(ref this); + ref uint self = ref Unsafe.As(ref this); return Unsafe.Add(ref self, idx); } } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index a63573030f..fb18340adf 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -57,15 +57,15 @@ public PdfJsHuffmanTable(MemoryManager memoryManager, ReadOnlySpan count, int k = 0; fixed (short* size = this.Sizes.Data) - fixed (short* delta = this.ValOffset.Data) - fixed (long* maxcode = this.MaxCode.Data) + fixed (int* delta = this.ValOffset.Data) + fixed (uint* maxcode = this.MaxCode.Data) { uint code = 0; int j; for (j = 1; j <= 16; j++) { // Compute delta to add to code to compute symbol id. - delta[j] = (short)(k - code); + delta[j] = (int)(k - code); if (size[k] == j) { while (size[k] == j) @@ -89,8 +89,8 @@ public PdfJsHuffmanTable(MemoryManager memoryManager, ReadOnlySpan count, fixed (byte* lookaheadRef = this.Lookahead.Data) { const int FastBits = ScanDecoder.FastBits; - var fast = new Span(lookaheadRef, 1 << FastBits); - fast.Fill(255); // Flag for non-accelerated + var fast = new Span(lookaheadRef, 1 << FastBits); + fast.Fill(0xFF); // Flag for non-accelerated fixed (short* sizesRef = this.Sizes.Data) { @@ -181,8 +181,8 @@ private void GenerateCodeTable(ref short huffcodeRef, int length, out int k) /// The huffman code span ref private void GenerateDecoderTables(ReadOnlySpan lengths, ref short huffcodeRef) { - fixed (short* valOffsetRef = this.ValOffset.Data) - fixed (long* maxcodeRef = this.MaxCode.Data) + fixed (int* valOffsetRef = this.ValOffset.Data) + fixed (uint* maxcodeRef = this.MaxCode.Data) { short bitcount = 0; for (int i = 1; i <= 16; i++) @@ -190,18 +190,18 @@ private void GenerateDecoderTables(ReadOnlySpan lengths, ref short huffcod if (lengths[i] != 0) { // valOffsetRef[l] = huffcodeRef[] index of 1st symbol of code length i, minus the minimum code of length i - valOffsetRef[i] = (short)(bitcount - Unsafe.Add(ref huffcodeRef, bitcount)); + valOffsetRef[i] = (int)(bitcount - Unsafe.Add(ref huffcodeRef, bitcount)); bitcount += lengths[i]; - maxcodeRef[i] = Unsafe.Add(ref huffcodeRef, bitcount - 1) << (16 - i); // maximum code of length i preshifted for faster reading later + maxcodeRef[i] = (uint)Unsafe.Add(ref huffcodeRef, bitcount - 1) << (16 - i); // maximum code of length i preshifted for faster reading later } else { - maxcodeRef[i] = -1; // -1 if no codes of this length + // maxcodeRef[i] = -1; // -1 if no codes of this length } } valOffsetRef[17] = 0; - maxcodeRef[17] = 0xFFFFFFFFL; + maxcodeRef[17] = 0xFFFFFFFF; } } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 8322be2fe3..9aec5173b7 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -148,32 +148,39 @@ public int ParseEntropyCodedData( { for (int i = 0; i < mcusPerLine; i++) { - // Scan an interleaved mcu... process components in order - for (int k = 0; k < this.componentsLength; k++) + try { - PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); - 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++) + // Scan an interleaved mcu... process components in order + for (int k = 0; k < this.componentsLength; k++) { - for (int x = 0; x < h; x++) + PdfJsFrameComponent component = this.components[k]; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + 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++) { - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; - int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + for (int x = 0; x < h; x++) + { + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + } } } } + catch + { + break; + } // After all interleaved components, that's an interleaved MCU, // so now count down the restart interval @@ -207,7 +214,7 @@ private int DecodeBlock( ref short blockDataRef, ref PdfJsHuffmanTable dcTable, ref PdfJsHuffmanTable acTable, - Span fac) + Span fastAc) { this.CheckBits(); int t = this.DecodeHuffman(ref dcTable); @@ -217,7 +224,7 @@ private int DecodeBlock( throw new ImageFormatException("Bad Huffman code"); } - int diff = t > 0 ? this.ExtendReceive(t) : 0; + int diff = t != 0 ? this.ExtendReceive(t) : 0; int dc = component.DcPredictor + diff; component.DcPredictor = dc; blockDataRef = (short)dc; @@ -231,9 +238,9 @@ private int DecodeBlock( this.CheckBits(); int c = this.PeekBits(); - int r = fac[c]; + int r = fastAc[c]; - if (r > 0) + if (r != 0) { // Fast AC path k += (r >> 4) & 15; // Run @@ -587,7 +594,7 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) // if the code is <= FastBits. int c = this.PeekBits(); int k = table.Lookahead[c]; - if (k < byte.MaxValue) + if (k < 0xFF) { int s = table.Sizes[k]; if (s > this.codeBits) @@ -628,7 +635,7 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) } // Convert the huffman code to the symbol id - c = (int)((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]; + c = (int)(((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]); // Convert the id to a symbol this.codeBits -= k; @@ -644,7 +651,7 @@ private int ExtendReceive(int n) this.GrowBufferUnsafe(); } - int sgn = (int)(this.codeBuffer >> 31); + int sgn = (int)((int)this.codeBuffer >> 31); uint k = this.Lrot(this.codeBuffer, n); this.codeBuffer = k & ~Bmask[n]; k &= Bmask[n]; @@ -655,7 +662,7 @@ private int ExtendReceive(int n) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CheckBits() { - if (this.codeBuffer < 16) + if (this.codeBits < 16) { this.GrowBufferUnsafe(); } @@ -664,7 +671,7 @@ private void CheckBits() [MethodImpl(MethodImplOptions.AggressiveInlining)] private int PeekBits() { - return (int)(this.codeBuffer >> ((32 - FastBits) & ((1 << FastBits) - 1))); + return (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -693,11 +700,10 @@ private void Reset() } this.marker = PdfJsJpegConstants.Markers.Prefix; - this.todo = this.restartInterval > 0 ? this.restartInterval : 0x7FFFFFFF; 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 + // 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 : 0x7FFFFFFF; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 5bf4ab5aa4..f1d1053881 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -808,9 +808,9 @@ private void ProcessStartOfScanMarker() sd.ParseEntropyCodedData(this.Frame, this.dcHuffmanTables, this.acHuffmanTables, this.fastACTables); - //var scanDecoder = default(PdfJsScanDecoder); - - //scanDecoder.DecodeScan( + // var scanDecoder = default(PdfJsScanDecoder); + // + // scanDecoder.DecodeScan( // this.Frame, // this.InputStream, // this.dcHuffmanTables, From bdd3291d69c78e4b7c5d6219e9268fc523c005d2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 31 May 2018 18:37:43 +1000 Subject: [PATCH 06/32] Update reference types --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 9aec5173b7..9a9348f7f3 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -4,8 +4,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components @@ -64,7 +63,7 @@ public ScanDecoder( this.dctZigZag = ZigZag.CreateUnzigTable(); this.stream = stream; this.components = components; - this.marker = PdfJsJpegConstants.Markers.Prefix; + this.marker = JpegConstants.Markers.XFF; this.componentIndex = componentIndex; this.componentsLength = componentsLength; this.restartInterval = restartInterval; @@ -532,7 +531,7 @@ private int GetBits(int n) this.GrowBufferUnsafe(); } - uint k = this.Lrot(this.codeBuffer, n); + uint k = this.LRot(this.codeBuffer, n); this.codeBuffer = k & ~Bmask[n]; k &= Bmask[n]; this.codeBits -= n; @@ -554,17 +553,18 @@ private int GetBit() return (int)(k & 0x80000000); } + [MethodImpl(MethodImplOptions.NoInlining)] private void GrowBufferUnsafe() { do { // TODO: EOF int b = this.nomore ? 0 : this.stream.ReadByte(); - if (b == PdfJsJpegConstants.Markers.Prefix) + if (b == JpegConstants.Markers.XFF) { long position = this.stream.Position - 1; int c = this.stream.ReadByte(); - while (c == PdfJsJpegConstants.Markers.Prefix) + while (c == JpegConstants.Markers.XFF) { if (c != 0) { @@ -586,6 +586,7 @@ private void GrowBufferUnsafe() while (this.codeBits <= 24); } + // TODO: Split into Fast/Slow and inline Fast private int DecodeHuffman(ref PdfJsHuffmanTable table) { this.CheckBits(); @@ -651,8 +652,8 @@ private int ExtendReceive(int n) this.GrowBufferUnsafe(); } - int sgn = (int)((int)this.codeBuffer >> 31); - uint k = this.Lrot(this.codeBuffer, n); + int sgn = (int)this.codeBuffer >> 31; + uint k = this.LRot(this.codeBuffer, n); this.codeBuffer = k & ~Bmask[n]; k &= Bmask[n]; this.codeBits -= n; @@ -675,7 +676,7 @@ private int PeekBits() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private uint Lrot(uint x, int y) + private uint LRot(uint x, int y) { return (x << y) | (x >> (32 - y)); } @@ -683,7 +684,7 @@ private uint Lrot(uint x, int y) [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool IsRestartMarker(byte x) { - return x >= PdfJsJpegConstants.Markers.RST0 && x <= PdfJsJpegConstants.Markers.RST7; + return x >= JpegConstants.Markers.RST0 && x <= JpegConstants.Markers.RST7; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -699,7 +700,7 @@ private void Reset() c.DcPredictor = 0; } - this.marker = PdfJsJpegConstants.Markers.Prefix; + this.marker = JpegConstants.Markers.XFF; 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 From 9e59ac19e0c27920fdfc8148f02492283204a6bd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jun 2018 11:16:35 +1000 Subject: [PATCH 07/32] 6/10 baseline images now pass. --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 9a9348f7f3..f1edd55deb 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -562,21 +562,17 @@ private void GrowBufferUnsafe() int b = this.nomore ? 0 : this.stream.ReadByte(); if (b == JpegConstants.Markers.XFF) { - long position = this.stream.Position - 1; int c = this.stream.ReadByte(); while (c == JpegConstants.Markers.XFF) { - if (c != 0) - { - this.marker = (byte)c; - this.nomore = true; - if (!this.IsRestartMarker(this.marker)) - { - this.stream.Position = position; - } + c = this.stream.ReadByte(); + } - return; - } + if (c != 0) + { + this.marker = (byte)c; + this.nomore = true; + return; } } @@ -586,7 +582,7 @@ private void GrowBufferUnsafe() while (this.codeBits <= 24); } - // TODO: Split into Fast/Slow and inline Fast + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int DecodeHuffman(ref PdfJsHuffmanTable table) { this.CheckBits(); @@ -608,6 +604,12 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) return table.Values[k]; } + return this.DecodeHuffmanSlow(ref table); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + 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 @@ -615,6 +617,7 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) // 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]) @@ -636,7 +639,7 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) } // Convert the huffman code to the symbol id - c = (int)(((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]); + int c = (int)(((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]); // Convert the id to a symbol this.codeBits -= k; From 588e423410a57cc9efd3abb7a76ca086d9912117 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jun 2018 19:08:00 +1000 Subject: [PATCH 08/32] 9/10 baseline now pass! --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 61482569a8..21763c5229 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -118,7 +118,7 @@ public int ParseEntropyCodedData( mcu++; // Every data block is an MCU, so countdown the restart interval - if (this.todo-- <= 0) + if (--this.todo <= 0) { if (this.codeBits < 24) { @@ -147,44 +147,37 @@ public int ParseEntropyCodedData( { for (int i = 0; i < mcusPerLine; i++) { - try + // Scan an interleaved mcu... process components in order + for (int k = 0; k < this.componentsLength; k++) { - // Scan an interleaved mcu... process components in order - for (int k = 0; k < this.componentsLength; k++) + PdfJsFrameComponent component = this.components[k]; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + 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++) { - PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); - 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++) { - for (int x = 0; x < h; x++) - { - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; - int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); - } + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); } } } - catch - { - break; - } // After all interleaved components, that's an interleaved MCU, // so now count down the restart interval mcu++; - if (this.todo-- <= 0) + if (--this.todo <= 0) { if (this.codeBits < 24) { From 681f99f9145a1ca2a81553028e5931ccc0d34c25 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Jul 2018 02:01:29 +1000 Subject: [PATCH 09/32] Can now decode baseline + progressive --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 207 ++++++++++++++++-- 1 file changed, 187 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 21763c5229..6c781dc8d4 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -25,7 +25,10 @@ internal class ScanDecoder private int codeBits; private uint codeBuffer; private bool nomore; + private bool eof; private byte marker; + private bool badMarker; + private long markerPosition; private int todo; private int restartInterval; @@ -64,6 +67,7 @@ public ScanDecoder( this.stream = stream; this.components = components; this.marker = JpegConstants.Markers.XFF; + this.markerPosition = 0; this.componentIndex = componentIndex; this.componentsLength = componentsLength; this.restartInterval = restartInterval; @@ -104,13 +108,18 @@ public int ParseEntropyCodedData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); int mcu = 0; for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { + if (this.eof) + { + continue; + } + int blockRow = mcu / w; int blockCol = mcu % w; int offset = component.GetBlockBufferOffset(blockRow, blockCol); @@ -154,7 +163,7 @@ public int ParseEntropyCodedData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - Span fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -164,6 +173,11 @@ public int ParseEntropyCodedData( { for (int x = 0; x < h; x++) { + if (this.eof) + { + continue; + } + int mcuRow = mcu / mcusPerLine; int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * v) + y; @@ -197,6 +211,138 @@ public int ParseEntropyCodedData( } } } + else + { + if (this.componentsLength == 1) + { + PdfJsFrameComponent component = this.components[this.componentIndex]; + + // 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 + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + + int mcu = 0; + for (int j = 0; j < h; j++) + { + for (int i = 0; i < w; i++) + { + if (this.eof) + { + continue; + } + + int blockRow = mcu / w; + int blockCol = mcu % w; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + + if (this.spectralStart == 0) + { + this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); + } + else + { + this.DecodeBlockProgressiveAC(ref Unsafe.Add(ref blockDataRef, offset), ref acHuffmanTable, fastAC); + } + + mcu++; + + // Every data block is an MCU, so countdown the restart interval + if (--this.todo <= 0) + { + if (this.codeBits < 24) + { + this.GrowBufferUnsafe(); + } + + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.IsRestartMarker(this.marker)) + { + return 1; + } + + this.Reset(); + } + } + } + } + else + { + // Interleaved + int mcu = 0; + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = 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 short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + 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) + { + continue; + } + + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); + } + } + } + + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + if (--this.todo <= 0) + { + if (this.codeBits < 24) + { + this.GrowBufferUnsafe(); + } + + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.IsRestartMarker(this.marker)) + { + return 1; + } + + this.Reset(); + } + } + } + } + } + + if (this.badMarker) + { + this.stream.Position = this.markerPosition; + } return 1; } @@ -206,7 +352,7 @@ private int DecodeBlock( ref short blockDataRef, ref PdfJsHuffmanTable dcTable, ref PdfJsHuffmanTable acTable, - Span fastAc) + ReadOnlySpan fastAc) { this.CheckBits(); int t = this.DecodeHuffman(ref dcTable); @@ -295,7 +441,7 @@ private int DecodeBlockProgressiveDC( { // First scan for DC coefficient, must be first int t = this.DecodeHuffman(ref dcTable); - int diff = t > 0 ? this.ExtendReceive(t) : 0; + int diff = t != 0 ? this.ExtendReceive(t) : 0; int dc = component.DcPredictor + diff; component.DcPredictor = dc; @@ -305,7 +451,7 @@ private int DecodeBlockProgressiveDC( else { // Refinement scan for DC coefficient - if (this.GetBit() > 0) + if (this.GetBit() != 0) { blockDataRef += (short)(1 << this.successiveLow); } @@ -315,10 +461,9 @@ private int DecodeBlockProgressiveDC( } private int DecodeBlockProgressiveAC( - PdfJsFrameComponent component, ref short blockDataRef, ref PdfJsHuffmanTable acTable, - Span fac) + ReadOnlySpan fastAc) { int k; @@ -331,7 +476,7 @@ private int DecodeBlockProgressiveAC( { int shift = this.successiveLow; - if (this.eobrun > 0) + if (this.eobrun != 0) { this.eobrun--; return 1; @@ -345,9 +490,9 @@ private int DecodeBlockProgressiveAC( this.CheckBits(); int c = this.PeekBits(); - int r = fac[c]; + int r = fastAc[c]; - if (r > 0) + if (r != 0) { // Fast AC path k += (r >> 4) & 15; // Run @@ -376,7 +521,7 @@ private int DecodeBlockProgressiveAC( if (r < 15) { this.eobrun = 1 << r; - if (r > 0) + if (r != 0) { this.eobrun += this.GetBits(r); } @@ -402,15 +547,15 @@ private int DecodeBlockProgressiveAC( // Refinement scan for these AC coefficients short bit = (short)(1 << this.successiveLow); - if (this.eobrun > 0) + if (this.eobrun != 0) { this.eobrun--; - for (k = this.spectralStart; k < this.spectralEnd; k++) + 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 (this.GetBit() != 0) { if ((p & bit) == 0) { @@ -450,7 +595,7 @@ private int DecodeBlockProgressiveAC( { this.eobrun = (1 << r) - 1; - if (r > 0) + if (r != 0) { this.eobrun += this.GetBits(r); } @@ -466,13 +611,13 @@ private int DecodeBlockProgressiveAC( } // Sign bit - if (this.GetBit() > 0) + if (this.GetBit() != 0) { s = bit; } else { - s -= bit; + s = -bit; } } @@ -482,7 +627,7 @@ private int DecodeBlockProgressiveAC( ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k++]); if (p != 0) { - if (this.GetBit() > 0) + if (this.GetBit() != 0) { if ((p & bit) == 0) { @@ -553,18 +698,38 @@ private void GrowBufferUnsafe() { // TODO: EOF int b = this.nomore ? 0 : this.stream.ReadByte(); + + if (b == -1) + { + this.eof = true; + b = 0; + } + 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.IsRestartMarker(this.marker)) + { + this.badMarker = true; + } + return; } } @@ -688,7 +853,6 @@ private void Reset() { this.codeBits = 0; this.codeBuffer = 0; - this.nomore = false; for (int i = 0; i < this.components.Length; i++) { @@ -696,11 +860,14 @@ private void Reset() 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 : 0x7FFFFFFF; + this.todo = this.restartInterval > 0 ? this.restartInterval : int.MaxValue; } } } \ No newline at end of file From c69c3aca1338c6bdf5873a0ce85d93698b3a2d02 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Jul 2018 16:11:31 +1000 Subject: [PATCH 10/32] Split decode method. --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 426 +++++++++--------- 1 file changed, 223 insertions(+), 203 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 6c781dc8d4..8b5ed0c053 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -84,8 +84,7 @@ public ScanDecoder( /// The DC Huffman tables. /// The AC Huffman tables. /// The fast AC decoding tables. - /// The - public int ParseEntropyCodedData( + public void ParseEntropyCodedData( PdfJsFrame frame, PdfJsHuffmanTables dcHuffmanTables, PdfJsHuffmanTables acHuffmanTables, @@ -95,256 +94,272 @@ public int ParseEntropyCodedData( if (!frame.Progressive) { - if (this.componentsLength == 1) + this.ParseBaselineData(frame, dcHuffmanTables, acHuffmanTables, fastACTables); + } + else + { + this.ParseProgressiveData(frame, dcHuffmanTables, acHuffmanTables, fastACTables); + } + + if (this.badMarker) + { + this.stream.Position = this.markerPosition; + } + } + + private void ParseBaselineData( + PdfJsFrame frame, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + FastACTables fastACTables) + { + if (this.componentsLength == 1) + { + PdfJsFrameComponent component = this.components[this.componentIndex]; + + // 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 + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + + int mcu = 0; + for (int j = 0; j < h; j++) { - PdfJsFrameComponent component = this.components[this.componentIndex]; - - // 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 - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); - - int mcu = 0; - for (int j = 0; j < h; j++) + for (int i = 0; i < w; i++) { - for (int i = 0; i < w; i++) + if (this.eof) { - if (this.eof) - { - continue; - } + return; + } - int blockRow = mcu / w; - int blockCol = mcu % w; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); - mcu++; + int blockRow = mcu / w; + int blockCol = mcu % w; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + mcu++; - // Every data block is an MCU, so countdown the restart interval - if (--this.todo <= 0) + // Every data block is an MCU, so countdown the restart interval + if (--this.todo <= 0) + { + if (this.codeBits < 24) { - if (this.codeBits < 24) - { - this.GrowBufferUnsafe(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.IsRestartMarker(this.marker)) - { - return 1; - } + this.GrowBufferUnsafe(); + } - this.Reset(); + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.ContinueOnRestart()) + { + return; } + + this.Reset(); } } } - else + } + else + { + // Interleaved + int mcu = 0; + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = frame.McusPerLine; + for (int j = 0; j < mcusPerColumn; j++) { - // Interleaved - int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; - 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 + for (int k = 0; k < this.componentsLength; k++) { - // Scan an interleaved mcu... process components in order - for (int k = 0; k < this.componentsLength; k++) + PdfJsFrameComponent component = this.components[k]; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + 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++) { - PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); - 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++) { - for (int x = 0; x < h; x++) + if (this.eof) { - if (this.eof) - { - continue; - } - - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; - int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + return; } + + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); } } + } - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - if (--this.todo <= 0) + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + if (--this.todo <= 0) + { + if (this.codeBits < 24) { - if (this.codeBits < 24) - { - this.GrowBufferUnsafe(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.IsRestartMarker(this.marker)) - { - return 1; - } + this.GrowBufferUnsafe(); + } - this.Reset(); + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.ContinueOnRestart()) + { + return; } + + this.Reset(); } } } } - else + } + + private void ParseProgressiveData( + PdfJsFrame frame, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + FastACTables fastACTables) + { + if (this.componentsLength == 1) { - if (this.componentsLength == 1) + PdfJsFrameComponent component = this.components[this.componentIndex]; + + // 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 + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + + int mcu = 0; + for (int j = 0; j < h; j++) { - PdfJsFrameComponent component = this.components[this.componentIndex]; - - // 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 - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); - - int mcu = 0; - for (int j = 0; j < h; j++) + for (int i = 0; i < w; i++) { - for (int i = 0; i < w; i++) + if (this.eof) { - if (this.eof) - { - continue; - } + return; + } - int blockRow = mcu / w; - int blockCol = mcu % w; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); + int blockRow = mcu / w; + int blockCol = mcu % w; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); - if (this.spectralStart == 0) - { - this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); - } - else - { - this.DecodeBlockProgressiveAC(ref Unsafe.Add(ref blockDataRef, offset), ref acHuffmanTable, fastAC); - } + if (this.spectralStart == 0) + { + this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); + } + else + { + this.DecodeBlockProgressiveAC(ref Unsafe.Add(ref blockDataRef, offset), ref acHuffmanTable, fastAC); + } - mcu++; + mcu++; - // Every data block is an MCU, so countdown the restart interval - if (--this.todo <= 0) + // Every data block is an MCU, so countdown the restart interval + if (--this.todo <= 0) + { + if (this.codeBits < 24) { - if (this.codeBits < 24) - { - this.GrowBufferUnsafe(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.IsRestartMarker(this.marker)) - { - return 1; - } + this.GrowBufferUnsafe(); + } - this.Reset(); + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.ContinueOnRestart()) + { + return; } + + this.Reset(); } } } - else + } + else + { + // Interleaved + int mcu = 0; + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = frame.McusPerLine; + for (int j = 0; j < mcusPerColumn; j++) { - // Interleaved - int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; - 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 + for (int k = 0; k < this.componentsLength; k++) { - // Scan an interleaved mcu... process components in order - for (int k = 0; k < this.componentsLength; k++) + PdfJsFrameComponent component = this.components[k]; + ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + 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++) { - PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); - 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++) { - for (int x = 0; x < h; x++) + if (this.eof) { - if (this.eof) - { - continue; - } - - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; - int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); + return; } + + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); } } + } - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - if (--this.todo <= 0) + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + if (--this.todo <= 0) + { + if (this.codeBits < 24) { - if (this.codeBits < 24) - { - this.GrowBufferUnsafe(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.IsRestartMarker(this.marker)) - { - return 1; - } + this.GrowBufferUnsafe(); + } - this.Reset(); + // If it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!this.ContinueOnRestart()) + { + return; } + + this.Reset(); } } } } - - if (this.badMarker) - { - this.stream.Position = this.markerPosition; - } - - return 1; } private int DecodeBlock( @@ -696,7 +711,6 @@ private void GrowBufferUnsafe() { do { - // TODO: EOF int b = this.nomore ? 0 : this.stream.ReadByte(); if (b == -1) @@ -725,7 +739,7 @@ private void GrowBufferUnsafe() { this.marker = (byte)c; this.nomore = true; - if (!this.IsRestartMarker(this.marker)) + if (!this.HasRestart()) { this.badMarker = true; } @@ -831,21 +845,27 @@ private void CheckBits() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int PeekBits() - { - return (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); - } + private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private uint LRot(uint x, int y) + private uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool ContinueOnRestart() { - return (x << y) | (x >> (32 - y)); + if (this.badMarker) + { + this.stream.Position = this.markerPosition; + } + + return this.HasRestart(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsRestartMarker(byte x) + private bool HasRestart() { - return x >= JpegConstants.Markers.RST0 && x <= JpegConstants.Markers.RST7; + byte m = this.marker; + return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 1a79cfb0022544db3da6c645d396932b833a32ad Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Jul 2018 16:26:06 +1000 Subject: [PATCH 11/32] void methods --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 8b5ed0c053..bdf87bac5d 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -362,7 +362,7 @@ private void ParseProgressiveData( } } - private int DecodeBlock( + private void DecodeBlock( PdfJsFrameComponent component, ref short blockDataRef, ref PdfJsHuffmanTable dcTable, @@ -436,11 +436,9 @@ private int DecodeBlock( } } } while (k < 64); - - return 1; } - private int DecodeBlockProgressiveDC( + private void DecodeBlockProgressiveDC( PdfJsFrameComponent component, ref short blockDataRef, ref PdfJsHuffmanTable dcTable) @@ -471,11 +469,9 @@ private int DecodeBlockProgressiveDC( blockDataRef += (short)(1 << this.successiveLow); } } - - return 1; } - private int DecodeBlockProgressiveAC( + private void DecodeBlockProgressiveAC( ref short blockDataRef, ref PdfJsHuffmanTable acTable, ReadOnlySpan fastAc) @@ -494,7 +490,7 @@ private int DecodeBlockProgressiveAC( if (this.eobrun != 0) { this.eobrun--; - return 1; + return; } k = this.spectralStart; @@ -672,8 +668,6 @@ private int DecodeBlockProgressiveAC( while (k <= this.spectralEnd); } } - - return 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From f2e05bfcbc6d50e7404f6cbf23d16be66af03e7d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Jul 2018 16:44:35 +1000 Subject: [PATCH 12/32] Minor perf changes. --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index bdf87bac5d..8a39dab344 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -22,6 +22,14 @@ internal class ScanDecoder private readonly DoubleBufferedStreamReader stream; private readonly PdfJsFrameComponent[] components; private readonly ZigZag dctZigZag; + private readonly int restartInterval; + private readonly int componentIndex; + private readonly int componentsLength; + private readonly int spectralStart; + private readonly int spectralEnd; + private readonly int successiveHigh; + private readonly int successiveLow; + private int codeBits; private uint codeBuffer; private bool nomore; @@ -29,16 +37,8 @@ internal class ScanDecoder private byte marker; private bool badMarker; private long markerPosition; - private int todo; - private int restartInterval; - private int componentIndex; - private int componentsLength; private int eobrun; - private int spectralStart; - private int spectralEnd; - private int successiveHigh; - private int successiveLow; /// /// Initializes a new instance of the class. @@ -126,7 +126,7 @@ private void ParseBaselineData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId)); int mcu = 0; for (int j = 0; j < h; j++) @@ -141,7 +141,7 @@ private void ParseBaselineData( int blockRow = mcu / w; int blockCol = mcu % w; int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); mcu++; // Every data block is an MCU, so countdown the restart interval @@ -181,7 +181,7 @@ private void ParseBaselineData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId)); int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -201,7 +201,7 @@ private void ParseBaselineData( int blockRow = (mcuRow * v) + y; int blockCol = (mcuCol * h) + x; int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, fastAC); + this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); } } } @@ -249,7 +249,7 @@ private void ParseProgressiveData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId)); int mcu = 0; for (int j = 0; j < h; j++) @@ -271,7 +271,7 @@ private void ParseProgressiveData( } else { - this.DecodeBlockProgressiveAC(ref Unsafe.Add(ref blockDataRef, offset), ref acHuffmanTable, fastAC); + this.DecodeBlockProgressiveAC(ref Unsafe.Add(ref blockDataRef, offset), ref acHuffmanTable, ref fastACRef); } mcu++; @@ -367,7 +367,7 @@ private void DecodeBlock( ref short blockDataRef, ref PdfJsHuffmanTable dcTable, ref PdfJsHuffmanTable acTable, - ReadOnlySpan fastAc) + ref short fastACRef) { this.CheckBits(); int t = this.DecodeHuffman(ref dcTable); @@ -391,7 +391,7 @@ private void DecodeBlock( this.CheckBits(); int c = this.PeekBits(); - int r = fastAc[c]; + int r = Unsafe.Add(ref fastACRef, c); if (r != 0) { @@ -474,7 +474,7 @@ private void DecodeBlockProgressiveDC( private void DecodeBlockProgressiveAC( ref short blockDataRef, ref PdfJsHuffmanTable acTable, - ReadOnlySpan fastAc) + ref short fastACRef) { int k; @@ -501,7 +501,7 @@ private void DecodeBlockProgressiveAC( this.CheckBits(); int c = this.PeekBits(); - int r = fastAc[c]; + int r = Unsafe.Add(ref fastACRef, c); if (r != 0) { From d8f4b25c63152e38a4e6b353cded883f5679282a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Jul 2018 17:39:47 +1000 Subject: [PATCH 13/32] Remove unused huffman table code. --- .../PdfJsPort/Components/PdfJsHuffmanTable.cs | 198 ++++-------------- 1 file changed, 41 insertions(+), 157 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 16a60d1874..e843ebbaca 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -50,55 +50,65 @@ public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan cou const int Length = 257; using (IBuffer huffcode = memoryAllocator.Allocate(Length)) { - Span codes = huffcode.GetSpan(); ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan()); - this.GenerateSizeTable(count); - int k = 0; - fixed (short* size = this.Sizes.Data) - fixed (int* delta = this.ValOffset.Data) - fixed (uint* maxcode = this.MaxCode.Data) + // Figure C.1: make table of Huffman code length for each symbol + fixed (short* sizesRef = this.Sizes.Data) { - uint code = 0; - int j; - for (j = 1; j <= 16; j++) + short x = 0; + for (short i = 1; i < 17; i++) { - // Compute delta to add to code to compute symbol id. - delta[j] = (int)(k - code); - if (size[k] == j) + byte l = count[i]; + for (short j = 0; j < l; j++) { - while (size[k] == j) - { - codes[k++] = (short)code++; + sizesRef[x] = i; + x++; + } + } - // Unsafe.Add(ref huffcodeRef, k++) = (short)code++; + sizesRef[x] = 0; - // TODO: Throw if invalid? + // Figure C.2: generate the codes themselves + int k = 0; + fixed (int* valOffsetRef = this.ValOffset.Data) + fixed (uint* maxcodeRef = this.MaxCode.Data) + { + uint code = 0; + int j; + for (j = 1; j < 17; j++) + { + // Compute delta to add to code to compute symbol id. + valOffsetRef[j] = (int)(k - code); + if (sizesRef[k] == j) + { + while (sizesRef[k] == j) + { + Unsafe.Add(ref huffcodeRef, k++) = (short)code++; + } } + + // Figure F.15: generate decoding tables for bit-sequential decoding. + // Compute largest code + 1 for this size. preshifted as neeed later. + maxcodeRef[j] = code << (16 - j); + code <<= 1; } - // Compute largest code + 1 for this size. preshifted as neeed later. - maxcode[j] = code << (16 - j); - code <<= 1; + maxcodeRef[j] = 0xFFFFFFFF; } - maxcode[j] = 0xFFFFFFFF; - } - - fixed (byte* lookaheadRef = this.Lookahead.Data) - { - const int FastBits = ScanDecoder.FastBits; - var fast = new Span(lookaheadRef, 1 << FastBits); - fast.Fill(0xFF); // Flag for non-accelerated - - fixed (short* sizesRef = this.Sizes.Data) + // Generate non-spec lookup tables to speed up decoding. + fixed (byte* lookaheadRef = this.Lookahead.Data) { + const int FastBits = ScanDecoder.FastBits; + var fast = new Span(lookaheadRef, 1 << FastBits); + fast.Fill(0xFF); // Flag for non-accelerated + for (int i = 0; i < k; i++) { int s = sizesRef[i]; if (s <= ScanDecoder.FastBits) { - int c = codes[i] << (FastBits - s); + int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - s); int m = 1 << (FastBits - s); for (int j = 0; j < m; j++) { @@ -108,139 +118,13 @@ public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan cou } } } - - // this.GenerateCodeTable(ref huffcodeRef, Length, out int k); - // this.GenerateDecoderTables(count, ref huffcodeRef); - // this.GenerateLookaheadTables(count, values, ref huffcodeRef, k); } fixed (byte* huffValRef = this.Values.Data) { var huffValSpan = new Span(huffValRef, 256); - values.CopyTo(huffValSpan); } } - - /// - /// Figure C.1: make table of Huffman code length for each symbol - /// - /// The code lengths - private void GenerateSizeTable(ReadOnlySpan lengths) - { - fixed (short* sizesRef = this.Sizes.Data) - { - short k = 0; - for (short i = 1; i < 17; i++) - { - byte l = lengths[i]; - for (short j = 0; j < l; j++) - { - sizesRef[k] = i; - k++; - } - } - - sizesRef[k] = 0; - } - } - - /// - /// Figure C.2: generate the codes themselves - /// - /// The huffman code span ref - /// The length of the huffsize span - /// The length of any valid codes - private void GenerateCodeTable(ref short huffcodeRef, int length, out int k) - { - fixed (short* sizesRef = this.Sizes.Data) - { - k = 0; - short si = sizesRef[0]; - short code = 0; - for (short i = 0; i < length; i++) - { - while (sizesRef[k] == si) - { - Unsafe.Add(ref huffcodeRef, k) = code; - code++; - k++; - } - - code <<= 1; - si++; - } - } - } - - /// - /// Figure F.15: generate decoding tables for bit-sequential decoding - /// - /// The code lengths - /// The huffman code span ref - private void GenerateDecoderTables(ReadOnlySpan lengths, ref short huffcodeRef) - { - fixed (int* valOffsetRef = this.ValOffset.Data) - fixed (uint* maxcodeRef = this.MaxCode.Data) - { - short bitcount = 0; - for (int i = 1; i <= 16; i++) - { - if (lengths[i] != 0) - { - // valOffsetRef[l] = huffcodeRef[] index of 1st symbol of code length i, minus the minimum code of length i - valOffsetRef[i] = (int)(bitcount - Unsafe.Add(ref huffcodeRef, bitcount)); - bitcount += lengths[i]; - maxcodeRef[i] = (uint)Unsafe.Add(ref huffcodeRef, bitcount - 1) << (16 - i); // maximum code of length i preshifted for faster reading later - } - else - { - // maxcodeRef[i] = -1; // -1 if no codes of this length - } - } - - valOffsetRef[17] = 0; - maxcodeRef[17] = 0xFFFFFFFF; - } - } - - /// - /// Generates non-spec lookup tables to speed up decoding - /// - /// The code lengths - /// The huffman value array - /// The huffman code span ref - /// The lengths of any valid codes - private void GenerateLookaheadTables(ReadOnlySpan lengths, ReadOnlySpan huffval, ref short huffcodeRef, int k) - { - // TODO: Rewrite this to match stb_Image - // TODO: This generation code matches the libJpeg code but the lookahead table is not actually used yet. - // To use it we need to implement fast lookup path in PdfJsScanDecoder.DecodeHuffman - // This should yield much faster scan decoding as usually, more than 95% of the Huffman codes - // will be 8 or fewer bits long and can be handled without looping. - fixed (byte* lookaheadRef = this.Lookahead.Data) - { - const int FastBits = ScanDecoder.FastBits; - var lookaheadSpan = new Span(lookaheadRef, 1 << ScanDecoder.FastBits); - - lookaheadSpan.Fill(byte.MaxValue); // Flag for non-accelerated - fixed (short* sizesRef = this.Sizes.Data) - { - for (int i = 0; i < k; ++i) - { - int s = sizesRef[i]; - if (s <= ScanDecoder.FastBits) - { - int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - s); - int m = 1 << (FastBits - s); - for (int j = 0; j < m; ++j) - { - lookaheadRef[c + j] = (byte)i; - } - } - } - } - } - } } } \ No newline at end of file From b5e240177a4cee4e2d87828fd299ee93e907696d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Jul 2018 19:48:16 +1000 Subject: [PATCH 14/32] Delete unused code. --- .../Jpeg/PdfJsPort/Components/FastACTables.cs | 2 +- .../Components/FixedByteBuffer257.cs | 24 - .../Components/FixedInt16Buffer256.cs | 24 - .../Components/PdfJsFrameComponent.cs | 2 +- .../PdfJsPort/Components/PdfJsHuffmanTable.cs | 2 +- .../PdfJsPort/Components/PdfJsScanDecoder.cs | 866 ------------------ .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 16 - 7 files changed, 3 insertions(+), 933 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs index 1e608cf7aa..f936f73426 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { /// - /// The collection of tables used for fast AC entropy scan decoding. + /// The collection of lookup tables used for fast AC entropy scan decoding. /// internal sealed class FastACTables : IDisposable { diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.cs deleted file mode 100644 index 3015243168..0000000000 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer257.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 FixedByteBuffer257 - { - public fixed byte Data[257]; - - public byte this[int idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref byte self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs deleted file mode 100644 index 2c16a918f4..0000000000 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer256.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 FixedInt16Buffer256 - { - public fixed short Data[256]; - - public short this[int idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref short self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index eefe8b97ea..1a10adf883 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -129,7 +129,7 @@ public void Init() this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); } - this.SpectralBlocks = this.memoryAllocator.Allocate2D(blocksPerColumnForMcu, blocksPerLineForMcu + 1, true); + this.SpectralBlocks = this.memoryAllocator.AllocateClean2D(blocksPerColumnForMcu, blocksPerLineForMcu + 1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index e843ebbaca..3babb449a4 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -88,7 +88,7 @@ public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan cou } // Figure F.15: generate decoding tables for bit-sequential decoding. - // Compute largest code + 1 for this size. preshifted as neeed later. + // Compute largest code + 1 for this size. preshifted as need later. maxcodeRef[j] = code << (16 - j); code <<= 1; } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs deleted file mode 100644 index d524fa5d84..0000000000 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs +++ /dev/null @@ -1,866 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -#if DEBUG -using System.Diagnostics; -#endif -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components -{ - /// - /// Provides the means to decode a spectral scan - /// - internal struct PdfJsScanDecoder - { - private ZigZag dctZigZag; - - private byte[] markerBuffer; - - private int mcuToRead; - - private int mcusPerLine; - - private int mcu; - - private int bitsData; - - private int bitsCount; - - private int specStart; - - private int specEnd; - - private int eobrun; - - private int compIndex; - - private int successiveState; - - private int successiveACState; - - private int successiveACNextValue; - - private bool endOfStreamReached; - - private bool unexpectedMarkerReached; - - /// - /// Decodes the spectral scan - /// - /// The image frame - /// The input stream - /// The DC Huffman tables - /// The AC Huffman tables - /// The scan components - /// The component index within the array - /// The length of the components. Different to the array length - /// The reset interval - /// The spectral selection start - /// The spectral selection end - /// The successive approximation bit high end - /// The successive approximation bit low end - public void DecodeScan( - PdfJsFrame frame, - DoubleBufferedStreamReader stream, - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - PdfJsFrameComponent[] components, - int componentIndex, - int componentsLength, - ushort resetInterval, - int spectralStart, - int spectralEnd, - int successivePrev, - int successive) - { - this.dctZigZag = ZigZag.CreateUnzigTable(); - this.markerBuffer = new byte[2]; - this.compIndex = componentIndex; - this.specStart = spectralStart; - this.specEnd = spectralEnd; - this.successiveState = successive; - this.endOfStreamReached = false; - this.unexpectedMarkerReached = false; - - bool progressive = frame.Progressive; - this.mcusPerLine = frame.McusPerLine; - - this.mcu = 0; - int mcuExpected; - if (componentsLength == 1) - { - mcuExpected = components[this.compIndex].WidthInBlocks * components[this.compIndex].HeightInBlocks; - } - else - { - mcuExpected = this.mcusPerLine * frame.McusPerColumn; - } - - while (this.mcu < mcuExpected) - { - // Reset interval stuff - this.mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - this.mcu, resetInterval) : mcuExpected; - for (int i = 0; i < components.Length; i++) - { - PdfJsFrameComponent c = components[i]; - c.DcPredictor = 0; - } - - this.eobrun = 0; - - if (!progressive) - { - this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, stream); - } - else - { - bool isAc = this.specStart != 0; - bool isFirst = successivePrev == 0; - PdfJsHuffmanTables huffmanTables = isAc ? acHuffmanTables : dcHuffmanTables; - this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, stream); - } - - // Reset - // TODO: I do not understand why these values are reset? We should surely be tracking the bits across mcu's? - this.bitsCount = 0; - this.bitsData = 0; - this.unexpectedMarkerReached = false; - - // Some images include more scan blocks than expected, skip past those and - // attempt to find the next valid marker - PdfJsFileMarker fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); - byte marker = fileMarker.Marker; - - // RSTn - We've already read the bytes and altered the position so no need to skip - if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) - { - continue; - } - - if (!fileMarker.Invalid) - { - // We've found a valid marker. - // Rewind the stream to the position of the marker and break - stream.Position = fileMarker.Position; - break; - } - -#if DEBUG - Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); -#endif - } - } - - private void DecodeScanBaseline( - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - PdfJsFrameComponent[] components, - int componentsLength, - DoubleBufferedStreamReader stream) - { - if (componentsLength == 1) - { - PdfJsFrameComponent component = components[this.compIndex]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan())); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - - for (int n = 0; n < this.mcuToRead; n++) - { - if (this.endOfStreamReached || this.unexpectedMarkerReached) - { - continue; - } - - this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, stream); - this.mcu++; - } - } - else - { - for (int n = 0; n < this.mcuToRead; n++) - { - for (int i = 0; i < componentsLength; i++) - { - PdfJsFrameComponent component = components[i]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan())); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - for (int j = 0; j < v; j++) - { - for (int k = 0; k < h; k++) - { - if (this.endOfStreamReached || this.unexpectedMarkerReached) - { - continue; - } - - this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, j, k, stream); - } - } - } - - this.mcu++; - } - } - } - - private void DecodeScanProgressive( - PdfJsHuffmanTables huffmanTables, - bool isAC, - bool isFirst, - PdfJsFrameComponent[] components, - int componentsLength, - DoubleBufferedStreamReader stream) - { - if (componentsLength == 1) - { - PdfJsFrameComponent component = components[this.compIndex]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan())); - ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId]; - - for (int n = 0; n < this.mcuToRead; n++) - { - if (this.endOfStreamReached || this.unexpectedMarkerReached) - { - continue; - } - - if (isAC) - { - if (isFirst) - { - this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, stream); - } - else - { - this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, stream); - } - } - else - { - if (isFirst) - { - this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, stream); - } - else - { - this.DecodeBlockDCSuccessive(component, ref blockDataRef, stream); - } - } - - this.mcu++; - } - } - else - { - for (int n = 0; n < this.mcuToRead; n++) - { - for (int i = 0; i < componentsLength; i++) - { - PdfJsFrameComponent component = components[i]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.GetSpan())); - ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId]; - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - for (int j = 0; j < v; j++) - { - for (int k = 0; k < h; k++) - { - // No need to continue here. - if (this.endOfStreamReached || this.unexpectedMarkerReached) - { - break; - } - - if (isAC) - { - if (isFirst) - { - this.DecodeMcuACFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream); - } - else - { - this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, j, k, stream); - } - } - else - { - if (isFirst) - { - this.DecodeMcuDCFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream); - } - else - { - this.DecodeMcuDCSuccessive(component, ref blockDataRef, j, k, stream); - } - } - } - } - } - - this.mcu++; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) - { - int blockRow = this.mcu / component.WidthInBlocks; - int blockCol = this.mcu % component.WidthInBlocks; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) - { - int mcuRow = this.mcu / this.mcusPerLine; - int mcuCol = this.mcu % this.mcusPerLine; - int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; - int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) - { - int blockRow = this.mcu / component.WidthInBlocks; - int blockCol = this.mcu % component.WidthInBlocks; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) - { - int mcuRow = this.mcu / this.mcusPerLine; - int mcuCol = this.mcu % this.mcusPerLine; - int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; - int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) - { - int blockRow = this.mcu / component.WidthInBlocks; - int blockCol = this.mcu % component.WidthInBlocks; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) - { - int mcuRow = this.mcu / this.mcusPerLine; - int mcuCol = this.mcu % this.mcusPerLine; - int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; - int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) - { - int blockRow = this.mcu / component.WidthInBlocks; - int blockCol = this.mcu % component.WidthInBlocks; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) - { - int mcuRow = this.mcu / this.mcusPerLine; - int mcuCol = this.mcu % this.mcusPerLine; - int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; - int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) - { - int blockRow = this.mcu / component.WidthInBlocks; - int blockCol = this.mcu % component.WidthInBlocks; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) - { - int mcuRow = this.mcu / this.mcusPerLine; - int mcuCol = this.mcu % this.mcusPerLine; - int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; - int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryReadBit(DoubleBufferedStreamReader stream, out int bit) - { - if (this.bitsCount == 0) - { - if (!this.TryFillBits(stream)) - { - bit = 0; - return false; - } - } - - this.bitsCount--; - bit = (this.bitsData >> this.bitsCount) & 1; - return true; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private bool TryFillBits(DoubleBufferedStreamReader stream) - { - // TODO: Read more then 1 byte at a time. - // In LibJpegTurbo this is be 25 bits (32-7) but I cannot get this to work - // for some images, I'm assuming because I am crossing MCU boundaries and not maintining the correct buffer state. - const int MinGetBits = 7; - - if (!this.unexpectedMarkerReached) - { - // Attempt to load to the minimum bit count. - while (this.bitsCount < MinGetBits) - { - int c = stream.ReadByte(); - - switch (c) - { - case -0x1: - - // We've encountered the end of the file stream which means there's no EOI marker in the image. - this.endOfStreamReached = true; - return false; - - case JpegConstants.Markers.XFF: - int nextByte = stream.ReadByte(); - - if (nextByte == -0x1) - { - this.endOfStreamReached = true; - return false; - } - - if (nextByte != 0) - { -#if DEBUG - Debug.WriteLine($"DecodeScan - Unexpected marker {(c << 8) | nextByte:X} at {stream.Position}"); -#endif - - // We've encountered an unexpected marker. Reverse the stream and exit. - this.unexpectedMarkerReached = true; - stream.Position -= 2; - - // TODO: double check we need this. - // Fill buffer with zero bits. - if (this.bitsCount == 0) - { - this.bitsData <<= MinGetBits; - this.bitsCount = MinGetBits; - } - - return true; - } - - break; - } - - // OK, load the next byte into bitsData - this.bitsData = (this.bitsData << 8) | c; - this.bitsCount += 8; - } - } - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int PeekBits(int count) - { - return this.bitsData >> (this.bitsCount - count) & ((1 << count) - 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DropBits(int count) - { - this.bitsCount -= count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryDecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream, out short value) - { - value = -1; - - // TODO: Implement fast Huffman decoding. - // In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream - // Then a LUT is used to avoid the loop when decoding the Huffman value. - // using 3 methods: FillBits, PeekBits, and DropBits. - // The LUT has been ported from LibJpegTurbo as has this code but it doesn't work. - // this.TryFillBits(stream); - // - // const int LookAhead = 8; - // int look = this.PeekBits(LookAhead); - // look = tree.Lookahead[look]; - // int bits = look >> LookAhead; - // - // if (bits <= LookAhead) - // { - // this.DropBits(bits); - // value = (short)(look & ((1 << LookAhead) - 1)); - // return true; - // } - if (!this.TryReadBit(stream, out int bit)) - { - return false; - } - - short code = (short)bit; - - // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 - int i = 1; - - while (code > tree.MaxCode[i]) - { - if (!this.TryReadBit(stream, out bit)) - { - return false; - } - - code <<= 1; - code |= (short)bit; - i++; - } - - int j = tree.ValOffset[i]; - value = tree.Values[(j + code) & 0xFF]; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryReceive(int length, DoubleBufferedStreamReader stream, out int value) - { - value = 0; - while (length > 0) - { - if (!this.TryReadBit(stream, out int bit)) - { - return false; - } - - value = (value << 1) | bit; - length--; - } - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryReceiveAndExtend(int length, DoubleBufferedStreamReader stream, out int value) - { - if (length == 1) - { - if (!this.TryReadBit(stream, out value)) - { - return false; - } - - value = value == 1 ? 1 : -1; - } - else - { - if (!this.TryReceive(length, stream, out value)) - { - return false; - } - - if (value < 1 << (length - 1)) - { - value += (-1 << length) + 1; - } - } - - return true; - } - - private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream) - { - if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t)) - { - return; - } - - int diff = 0; - if (t != 0) - { - if (!this.TryReceiveAndExtend(t, stream, out diff)) - { - return; - } - } - - Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff); - - int k = 1; - while (k < 64) - { - if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs)) - { - return; - } - - int s = rs & 15; - int r = rs >> 4; - - if (s == 0) - { - if (r < 15) - { - break; - } - - k += 16; - continue; - } - - k += r; - - byte z = this.dctZigZag[k]; - - if (!this.TryReceiveAndExtend(s, stream, out int re)) - { - return; - } - - Unsafe.Add(ref blockDataRef, offset + z) = (short)re; - k++; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream) - { - if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t)) - { - return; - } - - int diff = 0; - if (t != 0) - { - if (!this.TryReceiveAndExtend(t, stream, out diff)) - { - return; - } - } - - Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff << this.successiveState); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream) - { - if (!this.TryReadBit(stream, out int bit)) - { - return; - } - - Unsafe.Add(ref blockDataRef, offset) |= (short)(bit << this.successiveState); - } - - private void DecodeACFirst(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream) - { - if (this.eobrun > 0) - { - this.eobrun--; - return; - } - - int k = this.specStart; - int e = this.specEnd; - while (k <= e) - { - if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs)) - { - return; - } - - int s = rs & 15; - int r = rs >> 4; - - if (s == 0) - { - if (r < 15) - { - if (!this.TryReceive(r, stream, out int eob)) - { - return; - } - - this.eobrun = eob + (1 << r) - 1; - break; - } - - k += 16; - continue; - } - - k += r; - - byte z = this.dctZigZag[k]; - - if (!this.TryReceiveAndExtend(s, stream, out int v)) - { - return; - } - - Unsafe.Add(ref blockDataRef, offset + z) = (short)(v * (1 << this.successiveState)); - k++; - } - } - - private void DecodeACSuccessive(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream) - { - int k = this.specStart; - int e = this.specEnd; - int r = 0; - - while (k <= e) - { - int offsetZ = offset + this.dctZigZag[k]; - ref short blockOffsetZRef = ref Unsafe.Add(ref blockDataRef, offsetZ); - int sign = blockOffsetZRef < 0 ? -1 : 1; - - switch (this.successiveACState) - { - case 0: // Initial state - - if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs)) - { - return; - } - - int s = rs & 15; - r = rs >> 4; - if (s == 0) - { - if (r < 15) - { - if (!this.TryReceive(r, stream, out int eob)) - { - return; - } - - this.eobrun = eob + (1 << r); - this.successiveACState = 4; - } - else - { - r = 16; - this.successiveACState = 1; - } - } - else - { - if (s != 1) - { - throw new ImageFormatException("Invalid ACn encoding"); - } - - if (!this.TryReceiveAndExtend(s, stream, out int v)) - { - return; - } - - this.successiveACNextValue = v; - this.successiveACState = r > 0 ? 2 : 3; - } - - continue; - case 1: // Skipping r zero items - case 2: - if (blockOffsetZRef != 0) - { - if (!this.TryReadBit(stream, out int bit)) - { - return; - } - - blockOffsetZRef += (short)(sign * (bit << this.successiveState)); - } - else - { - r--; - if (r == 0) - { - this.successiveACState = this.successiveACState == 2 ? 3 : 0; - } - } - - break; - case 3: // Set value for a zero item - if (blockOffsetZRef != 0) - { - if (!this.TryReadBit(stream, out int bit)) - { - return; - } - - blockOffsetZRef += (short)(sign * (bit << this.successiveState)); - } - else - { - blockOffsetZRef = (short)(this.successiveACNextValue << this.successiveState); - this.successiveACState = 0; - } - - break; - case 4: // Eob - if (blockOffsetZRef != 0) - { - if (!this.TryReadBit(stream, out int bit)) - { - return; - } - - blockOffsetZRef += (short)(sign * (bit << this.successiveState)); - } - - break; - } - - k++; - } - - if (this.successiveACState == 4) - { - this.eobrun--; - if (this.eobrun == 0) - { - this.successiveACState = 0; - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index b71667300a..86ac6b195a 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -816,22 +816,6 @@ private void ProcessStartOfScanMarker() successiveApproximation & 15); sd.ParseEntropyCodedData(this.Frame, this.dcHuffmanTables, this.acHuffmanTables, this.fastACTables); - - // PdfJsScanDecoder scanDecoder = default; - // - // scanDecoder.DecodeScan( - // this.Frame, - // this.InputStream, - // this.dcHuffmanTables, - // this.acHuffmanTables, - // this.Frame.Components, - // componentIndex, - // selectorsCount, - // this.resetInterval, - // spectralStart, - // spectralEnd, - // successiveApproximation >> 4, - // successiveApproximation & 15); } /// From cfe5b5cc8b4f10221136c388c7b688a9e05b6045 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 1 Jul 2018 19:59:14 +1000 Subject: [PATCH 15/32] Update reference images from master --- tests/Images/External | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Images/External b/tests/Images/External index eb40b3c039..d9d93bbdd1 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit eb40b3c039dd8c8ca448cb8073a59ca178901e9f +Subproject commit d9d93bbdd18dd7b818c0d19cc8f967be98045d3c From 43c818fec2f1f294cb15b5d0bcac4f6b377bb7ca Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 10:27:26 +1000 Subject: [PATCH 16/32] Add descriptive comments, remove unused, and make method static. --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 8a39dab344..42cf38f2a8 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -11,12 +10,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { internal class ScanDecoder { + // The number of bits that can be read via a LUT. public const int FastBits = 9; - // bmask[n] = (1 << n) - 1 + // LUT 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 }; - // bias[n] = (-1 << n) + 1 + // 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 DoubleBufferedStreamReader stream; @@ -25,19 +25,44 @@ internal class ScanDecoder private readonly int restartInterval; private readonly int componentIndex; 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, ie. 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; /// @@ -230,6 +255,9 @@ private void ParseBaselineData( } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); + private void ParseProgressiveData( PdfJsFrame frame, PdfJsHuffmanTables dcHuffmanTables, @@ -312,8 +340,6 @@ private void ParseProgressiveData( PdfJsFrameComponent component = this.components[k]; ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ReadOnlySpan fastAC = fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId); int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -678,7 +704,7 @@ private int GetBits(int n) this.GrowBufferUnsafe(); } - uint k = this.LRot(this.codeBuffer, n); + uint k = LRot(this.codeBuffer, n); this.codeBuffer = k & ~Bmask[n]; k &= Bmask[n]; this.codeBits -= n; @@ -822,7 +848,7 @@ private int ExtendReceive(int n) } int sgn = (int)this.codeBuffer >> 31; - uint k = this.LRot(this.codeBuffer, n); + uint k = LRot(this.codeBuffer, n); this.codeBuffer = k & ~Bmask[n]; k &= Bmask[n]; this.codeBits -= n; @@ -841,9 +867,6 @@ private void CheckBits() [MethodImpl(MethodImplOptions.AggressiveInlining)] private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ContinueOnRestart() { From 1e493ab80e63e562512e0ecce05fd73054205cd0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 11:44:50 +1000 Subject: [PATCH 17/32] Split progressive AC method and rename GrowBufferUnsafe --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 215 ++++++++++-------- 1 file changed, 117 insertions(+), 98 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 42cf38f2a8..4fdac53735 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -13,7 +13,7 @@ internal class ScanDecoder // The number of bits that can be read via a LUT. public const int FastBits = 9; - // LUT Bmask[n] = (1 << n) - 1 + // 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 @@ -22,8 +22,14 @@ internal class ScanDecoder 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. @@ -53,7 +59,7 @@ internal class ScanDecoder // The current, if any, marker in the input stream. private byte marker; - // Whether we have a bad marker, ie. one that is not between RST0 and RST7 + // 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. @@ -68,15 +74,15 @@ internal class ScanDecoder /// /// Initializes a new instance of the class. /// - /// The input stream - /// The scan components - /// The component index within the array - /// The length of the components. Different to the array length - /// The reset interval - /// The spectral selection start - /// The spectral selection end - /// The successive approximation bit high end - /// The successive approximation bit low end + /// The input stream. + /// The scan components. + /// The component index within the array. + /// The length of the components. Different to the array length. + /// The reset interval. + /// The spectral selection start. + /// The spectral selection end. + /// The successive approximation bit high end. + /// The successive approximation bit low end. public ScanDecoder( DoubleBufferedStreamReader stream, PdfJsFrameComponent[] components, @@ -174,7 +180,7 @@ private void ParseBaselineData( { if (this.codeBits < 24) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } // If it's NOT a restart, then just bail, so we get corrupt data @@ -238,7 +244,7 @@ private void ParseBaselineData( { if (this.codeBits < 24) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } // If it's NOT a restart, then just bail, so we get corrupt data @@ -309,7 +315,7 @@ private void ParseProgressiveData( { if (this.codeBits < 24) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } // If it's NOT a restart, then just bail, so we get corrupt data @@ -371,7 +377,7 @@ private void ParseProgressiveData( { if (this.codeBits < 24) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } // If it's NOT a restart, then just bail, so we get corrupt data @@ -502,8 +508,6 @@ private void DecodeBlockProgressiveAC( ref PdfJsHuffmanTable acTable, ref short fastACRef) { - int k; - if (this.spectralStart == 0) { throw new ImageFormatException("Can't merge DC and AC."); @@ -511,6 +515,8 @@ private void DecodeBlockProgressiveAC( 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) @@ -519,7 +525,7 @@ private void DecodeBlockProgressiveAC( return; } - k = this.spectralStart; + int k = this.spectralStart; do { int zig; @@ -582,117 +588,125 @@ private void DecodeBlockProgressiveAC( else { // Refinement scan for these AC coefficients - short bit = (short)(1 << this.successiveLow); + this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); + } + } - if (this.eobrun != 0) + 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++) { - this.eobrun--; - for (k = this.spectralStart; k <= this.spectralEnd; k++) + ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k]); + if (p != 0) { - ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k]); - if (p != 0) + if (this.GetBit() != 0) { - if (this.GetBit() != 0) + if ((p & bit) == 0) { - if ((p & bit) == 0) + if (p > 0) { - if (p > 0) - { - p += bit; - } - else - { - p -= bit; - } + p += bit; + } + else + { + p -= bit; } } } } } - else + } + else + { + k = this.spectralStart; + do { - k = this.spectralStart; - do + int rs = this.DecodeHuffman(ref acTable); + if (rs < 0) { - int rs = this.DecodeHuffman(ref acTable); - if (rs < 0) - { - throw new ImageFormatException("Bad Huffman code."); - } + throw new ImageFormatException("Bad Huffman code."); + } - int s = rs & 15; - int r = rs >> 4; + int s = rs & 15; + int r = rs >> 4; - if (s == 0) + 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) { - // 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 = (1 << r) - 1; + this.eobrun += this.GetBits(r); + } - if (r != 0) - { - this.eobrun += this.GetBits(r); - } + r = 64; // Force end of block + } + } + else + { + if (s != 1) + { + throw new ImageFormatException("Bad Huffman code."); + } - r = 64; // Force end of block - } + // Sign bit + if (this.GetBit() != 0) + { + s = bit; } else { - if (s != 1) - { - throw new ImageFormatException("Bad Huffman code."); - } - - // Sign bit - if (this.GetBit() != 0) - { - s = bit; - } - else - { - s = -bit; - } + s = -bit; } + } - // Advance by r - while (k <= this.spectralEnd) + // Advance by r + while (k <= this.spectralEnd) + { + ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k++]); + if (p != 0) { - ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k++]); - if (p != 0) + if (this.GetBit() != 0) { - if (this.GetBit() != 0) + if ((p & bit) == 0) { - if ((p & bit) == 0) + if (p > 0) + { + p += bit; + } + else { - if (p > 0) - { - p += bit; - } - else - { - p -= bit; - } + p -= bit; } } } - else + } + else + { + if (r == 0) { - if (r == 0) - { - p = (short)s; - break; - } - - r--; + p = (short)s; + break; } + + r--; } } - while (k <= this.spectralEnd); } + while (k <= this.spectralEnd); } } @@ -701,7 +715,7 @@ private int GetBits(int n) { if (this.codeBits < n) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } uint k = LRot(this.codeBuffer, n); @@ -716,7 +730,7 @@ private int GetBit() { if (this.codeBits < 1) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } uint k = this.codeBuffer; @@ -727,18 +741,23 @@ private int GetBit() } [MethodImpl(MethodImplOptions.NoInlining)] - private void GrowBufferUnsafe() + 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; @@ -844,7 +863,7 @@ private int ExtendReceive(int n) { if (this.codeBits < n) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } int sgn = (int)this.codeBuffer >> 31; @@ -860,7 +879,7 @@ private void CheckBits() { if (this.codeBits < 16) { - this.GrowBufferUnsafe(); + this.FillBuffer(); } } From 8370d4f8af77277caeb2aaae6406cadadf6f5701 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 11:46:54 +1000 Subject: [PATCH 18/32] Fix updated struct name --- .../{FixedInt64Buffer18.cs => FixedUInt32Buffer18.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/{FixedInt64Buffer18.cs => FixedUInt32Buffer18.cs} (80%) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs similarity index 80% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs index a9266bd6b1..9b076d9daa 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt64Buffer18.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { [StructLayout(LayoutKind.Sequential)] - internal unsafe struct FixedInt64Buffer18 + internal unsafe struct FixedUInt32Buffer18 { public fixed uint Data[18]; @@ -16,7 +16,7 @@ public uint this[int idx] [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - ref uint self = ref Unsafe.As(ref this); + ref uint self = ref Unsafe.As(ref this); return Unsafe.Add(ref self, idx); } } From 6360a8a9a152c325c904781887cfe81d887c3105 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 11:47:18 +1000 Subject: [PATCH 19/32] Update name reference --- .../Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 3babb449a4..a895fd0a48 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -17,7 +17,7 @@ internal unsafe struct PdfJsHuffmanTable /// /// Gets the max code array /// - public FixedInt64Buffer18 MaxCode; + public FixedUInt32Buffer18 MaxCode; /// /// Gets the value offset array From fcabcdd5d5e132e2fbd9fcbe7747946b3bf8f8bf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 12:32:59 +1000 Subject: [PATCH 20/32] Refactor FastACTables and reduce trivial duplication. --- .../Jpeg/PdfJsPort/Components/FastACTables.cs | 18 ++- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 106 +++++++----------- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 19 +++- 3 files changed, 68 insertions(+), 75 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs index f936f73426..6a11f28056 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components @@ -11,24 +12,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal sealed class FastACTables : IDisposable { + private Buffer2D tables; + /// /// Initializes a new instance of the class. /// /// The memory allocator used to allocate memory for image processing operations. public FastACTables(MemoryAllocator memoryAllocator) { - this.Tables = memoryAllocator.AllocateClean2D(512, 4); + this.tables = memoryAllocator.AllocateClean2D(512, 4); } /// - /// Gets the collection of tables. + /// Gets the representing the table at the index in the collection. /// - public Buffer2D Tables { get; } + /// The table index. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetTableSpan(int index) + { + return this.tables.GetRowSpan(index); + } /// public void Dispose() { - this.Tables?.Dispose(); + this.tables?.Dispose(); + this.tables = null; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 4fdac53735..6c01deaa9a 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -4,10 +4,14 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { + /// + /// Decodes the Huffman encoded spectral scan. + /// Originally ported from + /// with additional fixes for both performance and common encoding errors. + /// internal class ScanDecoder { // The number of bits that can be read via a LUT. @@ -157,7 +161,7 @@ private void ParseBaselineData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId)); + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); int mcu = 0; for (int j = 0; j < h; j++) @@ -173,24 +177,12 @@ private void ParseBaselineData( int blockCol = mcu % w; int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); - mcu++; // Every data block is an MCU, so countdown the restart interval - if (--this.todo <= 0) + mcu++; + if (!this.ContinueOnMcuComplete()) { - if (this.codeBits < 24) - { - this.FillBuffer(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.ContinueOnRestart()) - { - return; - } - - this.Reset(); + return; } } } @@ -212,7 +204,7 @@ private void ParseBaselineData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId)); + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -240,21 +232,9 @@ private void ParseBaselineData( // After all interleaved components, that's an interleaved MCU, // so now count down the restart interval mcu++; - if (--this.todo <= 0) + if (!this.ContinueOnMcuComplete()) { - if (this.codeBits < 24) - { - this.FillBuffer(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.ContinueOnRestart()) - { - return; - } - - this.Reset(); + return; } } } @@ -283,7 +263,7 @@ private void ParseProgressiveData( ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.Tables.GetRowSpan(component.ACHuffmanTableId)); + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); int mcu = 0; for (int j = 0; j < h; j++) @@ -308,24 +288,11 @@ private void ParseProgressiveData( this.DecodeBlockProgressiveAC(ref Unsafe.Add(ref blockDataRef, offset), ref acHuffmanTable, ref fastACRef); } - mcu++; - // Every data block is an MCU, so countdown the restart interval - if (--this.todo <= 0) + mcu++; + if (!this.ContinueOnMcuComplete()) { - if (this.codeBits < 24) - { - this.FillBuffer(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.ContinueOnRestart()) - { - return; - } - - this.Reset(); + return; } } } @@ -373,21 +340,9 @@ private void ParseProgressiveData( // After all interleaved components, that's an interleaved MCU, // so now count down the restart interval mcu++; - if (--this.todo <= 0) + if (!this.ContinueOnMcuComplete()) { - if (this.codeBits < 24) - { - this.FillBuffer(); - } - - // If it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!this.ContinueOnRestart()) - { - return; - } - - this.Reset(); + return; } } } @@ -887,14 +842,33 @@ private void CheckBits() private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ContinueOnRestart() + 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; } - return this.HasRestart(); + if (!this.HasRestart()) + { + return false; + } + + this.Reset(); + + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -904,7 +878,7 @@ private bool HasRestart() return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] private void Reset() { this.codeBits = 0; diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 86ac6b195a..fda98e4371 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -842,6 +842,11 @@ private ushort ReadUint16() return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); } + /// + /// Post processes the pixels into the destination image. + /// + /// The pixel format. + /// The . private Image PostProcessIntoImage() where TPixel : struct, IPixel { @@ -853,18 +858,22 @@ private Image PostProcessIntoImage() } } + /// + /// Builds a lookup table for fast AC entropy scan decoding. + /// + /// The table index. private void BuildFastACTable(int index) { const int FastBits = ScanDecoder.FastBits; - Span fastac = this.fastACTables.Tables.GetRowSpan(index); + Span fastAC = this.fastACTables.GetTableSpan(index); ref PdfJsHuffmanTable huffman = ref this.acHuffmanTables[index]; int i; for (i = 0; i < (1 << FastBits); i++) { byte fast = huffman.Lookahead[i]; - fastac[i] = 0; - if (fast < 255) + fastAC[i] = 0; + if (fast < byte.MaxValue) { int rs = huffman.Values[fast]; int run = (rs >> 4) & 15; @@ -881,10 +890,10 @@ private void BuildFastACTable(int index) k += (int)((~0U << magbits) + 1); } - // if the result is small enough, we can fit it in fastac table + // 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)); + fastAC[i] = (short)((k * 256) + (run * 16) + (len + magbits)); } } } From 69f228bf2341bb1255637b0720fcee4d0013eebf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 12:38:23 +1000 Subject: [PATCH 21/32] private static ordering. --- .../Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 6c01deaa9a..cc9d4d470e 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -142,6 +142,9 @@ public void ParseEntropyCodedData( } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); + private void ParseBaselineData( PdfJsFrame frame, PdfJsHuffmanTables dcHuffmanTables, @@ -241,9 +244,6 @@ private void ParseBaselineData( } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); - private void ParseProgressiveData( PdfJsFrame frame, PdfJsHuffmanTables dcHuffmanTables, From 9cb8c155fb6be9fea2c971c1139edbe03de71424 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 22:22:26 +1000 Subject: [PATCH 22/32] Rename struct --- .../{FixedInt16Buffer18.cs => FixedInt32Buffer18.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/{FixedInt16Buffer18.cs => FixedInt32Buffer18.cs} (83%) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs similarity index 83% rename from src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs rename to src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs index b193bf59e6..f8507ec47c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer18.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { [StructLayout(LayoutKind.Sequential)] - internal unsafe struct FixedInt16Buffer18 + internal unsafe struct FixedInt32Buffer18 { public fixed int Data[18]; @@ -16,7 +16,7 @@ public int this[int idx] [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - ref int self = ref Unsafe.As(ref this); + ref int self = ref Unsafe.As(ref this); return Unsafe.Add(ref self, idx); } } From bd79c8471465b281ebc447d62300e6d8a555114c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 22:25:58 +1000 Subject: [PATCH 23/32] Update Huffman table property --- .../Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index a895fd0a48..15ae56331c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -22,7 +22,7 @@ internal unsafe struct PdfJsHuffmanTable /// /// Gets the value offset array /// - public FixedInt16Buffer18 ValOffset; + public FixedInt32Buffer18 ValOffset; /// /// Gets the huffman value array From d4fc8a03be221fdab018bf9592ea356f11aeca60 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 2 Jul 2018 22:45:19 +1000 Subject: [PATCH 24/32] Move method where it belongs. --- .../Jpeg/PdfJsPort/Components/FastACTables.cs | 45 ++++++++++++++++++- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 44 +----------------- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs index 6a11f28056..3e170a92c7 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs @@ -29,11 +29,54 @@ public FastACTables(MemoryAllocator memoryAllocator) /// The table index. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetTableSpan(int index) + public ReadOnlySpan GetTableSpan(int index) { return this.tables.GetRowSpan(index); } + /// + /// Builds a lookup table for fast AC entropy scan decoding. + /// + /// The table index. + /// The collection of AC Huffman tables. + public void BuildACTableLut(int index, PdfJsHuffmanTables acHuffmanTables) + { + const int FastBits = ScanDecoder.FastBits; + Span fastAC = this.tables.GetRowSpan(index); + ref PdfJsHuffmanTable huffman = ref acHuffmanTables[index]; + + int i; + for (i = 0; i < (1 << FastBits); i++) + { + byte fast = huffman.Lookahead[i]; + fastAC[i] = 0; + if (fast < byte.MaxValue) + { + int rs = huffman.Values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = huffman.Sizes[fast]; + + if (magbits > 0 && len + magbits <= FastBits) + { + // Magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits); + int m = 1 << (magbits - 1); + if (k < m) + { + k += (int)((~0U << magbits) + 1); + } + + // if the result is small enough, we can fit it in fastAC table + if (k >= -128 && k <= 127) + { + fastAC[i] = (short)((k * 256) + (run * 16) + (len + magbits)); + } + } + } + } + } + /// public void Dispose() { diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index fda98e4371..cb52fb84b3 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -743,7 +743,7 @@ private void ProcessDefineHuffmanTablesMarker(int remaining) if (huffmanTableSpec >> 4 != 0) { // Build a table that decodes both magnitude and value of small ACs in one go. - this.BuildFastACTable(huffmanTableSpec & 15); + this.fastACTables.BuildACTableLut(huffmanTableSpec & 15, this.acHuffmanTables); } } } @@ -857,47 +857,5 @@ private Image PostProcessIntoImage() return image; } } - - /// - /// Builds a lookup table for fast AC entropy scan decoding. - /// - /// The table index. - private void BuildFastACTable(int index) - { - const int FastBits = ScanDecoder.FastBits; - Span fastAC = this.fastACTables.GetTableSpan(index); - ref PdfJsHuffmanTable huffman = ref this.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)); - } - } - } - } - } } } \ No newline at end of file From 65a050495f25012a24b9c69d6ae4c9cfc9a75ffe Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 00:25:05 +0200 Subject: [PATCH 25/32] add regression test for #624 --- .../Formats/Jpg/JpegDecoderTests.Images.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/External | 2 +- ...Issue624-DhtHasWrongLength-Progressive-N.jpg | Bin 0 -> 30441 bytes 4 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 539ab73195..3c98d5be72 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -38,6 +38,7 @@ public partial class JpegDecoderTests TestImages.Jpeg.Issues.NoEoiProgressive517, TestImages.Jpeg.Issues.BadRstProgressive518, TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, + TestImages.Jpeg.Issues.DhtHasWrongLength624, }; /// diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6d3a76e75f..b0bdad8e5c 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -144,6 +144,7 @@ public static class Issues public const string NoEoiProgressive517 = "Jpg/issues/Issue517-No-EOI-Progressive.jpg"; public const string BadRstProgressive518 = "Jpg/issues/Issue518-Bad-RST-Progressive.jpg"; public const string InvalidCast520 = "Jpg/issues/Issue520-InvalidCast.jpg"; + public const string DhtHasWrongLength624 = "Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg"; } public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); diff --git a/tests/Images/External b/tests/Images/External index d9d93bbdd1..98fb7e2e4d 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit d9d93bbdd18dd7b818c0d19cc8f967be98045d3c +Subproject commit 98fb7e2e4d5935b1c733bd2b206b6145b71ef378 diff --git a/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg b/tests/Images/Input/Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2077b75182ea8feff5e5232fa025f197cc8a483a GIT binary patch literal 30441 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 literal 0 HcmV?d00001 From c21e9712107fa4d806c45f7801ceead28ec0fa24 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 00:41:14 +0200 Subject: [PATCH 26/32] ParseStream -only benchmark --- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs new file mode 100644 index 0000000000..059f312b3e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using System.Drawing; +using System.IO; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + [Config(typeof(Config.ShortClr))] + public class DecodeJpegParseStreamOnly + { + [Params(TestImages.Jpeg.Baseline.Jpeg420Exif)] + public string TestImage { get; set; } + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + private byte[] jpegBytes; + + [GlobalSetup] + public void Setup() + { + this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); + } + + [Benchmark(Baseline = true, Description = "System.Drawing FULL")] + public Size JpegSystemDrawing() + { + using (var memoryStream = new MemoryStream(this.jpegBytes)) + { + using (var image = System.Drawing.Image.FromStream(memoryStream)) + { + return image.Size; + } + } + } + + [Benchmark(Description = "PdfJsJpegDecoderCore.ParseStream")] + public void ParseStreamPdfJs() + { + using (var memoryStream = new MemoryStream(this.jpegBytes)) + { + var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder() { IgnoreMetadata = true }); + decoder.ParseStream(memoryStream); + decoder.Dispose(); + } + } + } +} \ No newline at end of file From d04611ead160777011bbb0232a633640dbec5c01 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 00:52:44 +0200 Subject: [PATCH 27/32] Introduce InliningOptions --- .../Common/Helpers/InliningOptions.cs | 22 ++++++++++++++++ .../Formats/Jpeg/JpegThrowHelper.cs | 26 +++++++++++++++++++ .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 21 +++++++-------- 3 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/InliningOptions.cs create mode 100644 src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs diff --git a/src/ImageSharp/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs new file mode 100644 index 0000000000..d218acdbaa --- /dev/null +++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// Uncomment this for verbose profiler results: +// #define PROFILING +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Global inlining options. Helps temporally disable inling for better profiler output. + /// + internal static class InliningOptions + { +#if PROFILING + public const MethodImplOptions ShortMethod = 0; +#else + public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; +#endif + public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs new file mode 100644 index 0000000000..c7f3666604 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + internal static class JpegThrowHelper + { + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) + { + throw new ImageFormatException(errorMessage); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowBadHuffmanCode() + { + throw new ImageFormatException("Bad Huffman code."); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index cc9d4d470e..74772ec617 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -665,7 +665,7 @@ private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref PdfJsHu } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private int GetBits(int n) { if (this.codeBits < n) @@ -680,7 +680,7 @@ private int GetBits(int n) return (int)k; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private int GetBit() { if (this.codeBits < 1) @@ -695,7 +695,7 @@ private int GetBit() return (int)(k & 0x80000000); } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(InliningOptions.ColdPath)] private void FillBuffer() { // Attempt to load at least the minimum nbumber of required bits into the buffer. @@ -748,7 +748,7 @@ private void FillBuffer() while (this.codeBits <= 24); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private int DecodeHuffman(ref PdfJsHuffmanTable table) { this.CheckBits(); @@ -773,7 +773,7 @@ private int DecodeHuffman(ref PdfJsHuffmanTable table) return this.DecodeHuffmanSlow(ref table); } - [MethodImpl(MethodImplOptions.NoInlining)] + [MethodImpl(InliningOptions.ColdPath)] private int DecodeHuffmanSlow(ref PdfJsHuffmanTable table) { // Naive test is to shift the code_buffer down so k bits are @@ -813,7 +813,7 @@ private int DecodeHuffmanSlow(ref PdfJsHuffmanTable table) return table.Values[c]; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private int ExtendReceive(int n) { if (this.codeBits < n) @@ -829,7 +829,7 @@ private int ExtendReceive(int n) return (int)(k + (Bias[n] & ~sgn)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private void CheckBits() { if (this.codeBits < 16) @@ -838,10 +838,10 @@ private void CheckBits() } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private bool ContinueOnMcuComplete() { if (--this.todo > 0) @@ -871,14 +871,13 @@ private bool ContinueOnMcuComplete() return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private bool HasRestart() { byte m = this.marker; return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7; } - [MethodImpl(MethodImplOptions.NoInlining)] private void Reset() { this.codeBits = 0; From 468797f4fa38dd91bd3e118ab23236860d8a216d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 00:55:55 +0200 Subject: [PATCH 28/32] use JpegThrowHelper --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 74772ec617..d0b6cc9095 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -361,7 +361,7 @@ private void DecodeBlock( if (t < 0) { - throw new ImageFormatException("Bad Huffman code"); + JpegThrowHelper.ThrowBadHuffmanCode(); } int diff = t != 0 ? this.ExtendReceive(t) : 0; @@ -398,7 +398,7 @@ private void DecodeBlock( if (rs < 0) { - throw new ImageFormatException("Bad Huffman code"); + JpegThrowHelper.ThrowBadHuffmanCode(); } s = rs & 15; @@ -432,7 +432,7 @@ private void DecodeBlockProgressiveDC( { if (this.spectralEnd != 0) { - throw new ImageFormatException("Can't merge DC and AC."); + JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); } this.CheckBits(); @@ -465,7 +465,7 @@ private void DecodeBlockProgressiveAC( { if (this.spectralStart == 0) { - throw new ImageFormatException("Can't merge DC and AC."); + JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); } if (this.successiveHigh == 0) @@ -508,7 +508,7 @@ private void DecodeBlockProgressiveAC( if (rs < 0) { - throw new ImageFormatException("Bad Huffman code."); + JpegThrowHelper.ThrowBadHuffmanCode(); } s = rs & 15; @@ -587,7 +587,7 @@ private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref PdfJsHu int rs = this.DecodeHuffman(ref acTable); if (rs < 0) { - throw new ImageFormatException("Bad Huffman code."); + JpegThrowHelper.ThrowBadHuffmanCode(); } int s = rs & 15; @@ -614,7 +614,7 @@ private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref PdfJsHu { if (s != 1) { - throw new ImageFormatException("Bad Huffman code."); + JpegThrowHelper.ThrowBadHuffmanCode(); } // Sign bit From b9a6804fbfff0cbd5c5e39d8cb11c311e6de666b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 01:03:51 +0200 Subject: [PATCH 29/32] separate Interleaved / Non-Interleaved code path --- .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 361 ++++++++++-------- 1 file changed, 208 insertions(+), 153 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index d0b6cc9095..6f88b6e1f2 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -153,203 +153,258 @@ private void ParseBaselineData( { if (this.componentsLength == 1) { - PdfJsFrameComponent component = this.components[this.componentIndex]; - - // 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 - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); - - 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; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); - - // Every data block is an MCU, so countdown the restart interval - mcu++; - if (!this.ContinueOnMcuComplete()) - { - return; - } - } - } + this.ParseBaselineDataNonInterleaved(dcHuffmanTables, acHuffmanTables, fastACTables); } else { - // Interleaved - int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; - for (int j = 0; j < mcusPerColumn; j++) + this.ParseBaselineDataInterleaved(frame, dcHuffmanTables, acHuffmanTables, fastACTables); + } + } + + private void ParseBaselineDataInterleaved( + PdfJsFrame frame, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + FastACTables fastACTables) + { + // Interleaved + int mcu = 0; + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = frame.McusPerLine; + 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 + for (int k = 0; k < this.componentsLength; k++) { - // Scan an interleaved mcu... process components in order - for (int k = 0; k < this.componentsLength; k++) + PdfJsFrameComponent component = this.components[k]; + ref short blockDataRef = ref MemoryMarshal.GetReference( + MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ref short fastACRef = + ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); + 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++) { - PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); - 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++) { - for (int x = 0; x < h; x++) + if (this.eof) { - if (this.eof) - { - return; - } - - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; - int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlock(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); + return; } + + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockDataRef, offset), + 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; - } + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + if (!this.ContinueOnMcuComplete()) + { + return; } } } } - private void ParseProgressiveData( - PdfJsFrame frame, + /// + /// Non-interleaved data, we just need to process one block at a ti + /// in trivial scanline order + /// number of blocks to do just depends on how many actual "pixels" + /// component has, independent of interleaved MCU blocking and such + /// + private void ParseBaselineDataNonInterleaved( PdfJsHuffmanTables dcHuffmanTables, PdfJsHuffmanTables acHuffmanTables, FastACTables fastACTables) { - if (this.componentsLength == 1) + PdfJsFrameComponent component = this.components[this.componentIndex]; + + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + ref short blockDataRef = + ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); + + int mcu = 0; + for (int j = 0; j < h; j++) { - PdfJsFrameComponent component = this.components[this.componentIndex]; - - // 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 - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); - - int mcu = 0; - for (int j = 0; j < h; j++) + for (int i = 0; i < w; i++) { - for (int i = 0; i < w; i++) + if (this.eof) { - if (this.eof) - { - return; - } - - int blockRow = mcu / w; - int blockCol = mcu % w; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - - if (this.spectralStart == 0) - { - this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); - } - else - { - this.DecodeBlockProgressiveAC(ref Unsafe.Add(ref blockDataRef, offset), ref acHuffmanTable, ref fastACRef); - } + return; + } - // Every data block is an MCU, so countdown the restart interval - mcu++; - if (!this.ContinueOnMcuComplete()) - { - return; - } + int blockRow = mcu / w; + int blockCol = mcu % w; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockDataRef, offset), + 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( + PdfJsFrame frame, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + FastACTables fastACTables) + { + if (this.componentsLength == 1) + { + this.ParseProgressiveDataNonInterleaved(dcHuffmanTables, acHuffmanTables, fastACTables); + } else { - // Interleaved - int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; - for (int j = 0; j < mcusPerColumn; j++) + this.ParseProgressiveDataInterleaved(frame, dcHuffmanTables); + } + } + + private void ParseProgressiveDataInterleaved(PdfJsFrame frame, PdfJsHuffmanTables dcHuffmanTables) + { + // Interleaved + int mcu = 0; + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = frame.McusPerLine; + 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 + for (int k = 0; k < this.componentsLength; k++) { - // Scan an interleaved mcu... process components in order - for (int k = 0; k < this.componentsLength; k++) + PdfJsFrameComponent component = this.components[k]; + ref short blockDataRef = ref MemoryMarshal.GetReference( + MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref 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++) { - PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref 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++) { - for (int x = 0; x < h; x++) + if (this.eof) { - if (this.eof) - { - return; - } - - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - int blockRow = (mcuRow * v) + y; - int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); - this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); + return; } + + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + int blockRow = (mcuRow * v) + y; + int blockCol = (mcuCol * h) + x; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockProgressiveDC( + component, + ref Unsafe.Add(ref blockDataRef, offset), + ref dcHuffmanTable); } } + } - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - if (!this.ContinueOnMcuComplete()) - { - return; - } + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + if (!this.ContinueOnMcuComplete()) + { + return; + } + } + } + } + + /// + /// Non-interleaved data, we just need to process one block at a time, + /// in trivial scanline order + /// number of blocks to do just depends on how many actual "pixels" this + /// component has, independent of interleaved MCU blocking and such + /// + private void ParseProgressiveDataNonInterleaved( + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + FastACTables fastACTables) + { + PdfJsFrameComponent component = this.components[this.componentIndex]; + + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; + ref short blockDataRef = + ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; + ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); + + 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; + int offset = component.GetBlockBufferOffset(blockRow, blockCol); + + if (this.spectralStart == 0) + { + this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); + } + else + { + this.DecodeBlockProgressiveAC( + ref Unsafe.Add(ref blockDataRef, offset), + ref acHuffmanTable, + ref fastACRef); + } + + // Every data block is an MCU, so countdown the restart interval + mcu++; + if (!this.ContinueOnMcuComplete()) + { + return; } } } } - private void DecodeBlock( + private void DecodeBlockBaseline( PdfJsFrameComponent component, ref short blockDataRef, ref PdfJsHuffmanTable dcTable, From ff1a24c02a5f258497dac5d5d7d4c605210836c6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 01:14:33 +0200 Subject: [PATCH 30/32] simplify + uniformize blockDataRef retrieval --- .../Components/PdfJsFrameComponent.cs | 8 +++ .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 49 ++++++++++++------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index 1a10adf883..7501b0d83c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -144,5 +144,13 @@ public int GetBlockBufferOffset(int row, int col) { return 64 * (((this.WidthInBlocks + 1) * row) + col); } + + // TODO: we need consistence in (row, col) VS (col, row) ordering + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref short GetBlockDataReference(int row, int col) + { + ref Block8x8 blockRef = ref this.GetBlockReference(col, row); + return ref Unsafe.As(ref blockRef); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 6f88b6e1f2..18cfc6c3fe 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -179,8 +179,7 @@ private void ParseBaselineDataInterleaved( for (int k = 0; k < this.componentsLength; k++) { PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference( - MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; ref short fastACRef = @@ -203,10 +202,11 @@ private void ParseBaselineDataInterleaved( int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * v) + y; int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockBaseline( component, - ref Unsafe.Add(ref blockDataRef, offset), + blockRow, + blockCol, ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); @@ -240,8 +240,7 @@ private void ParseBaselineDataNonInterleaved( int w = component.WidthInBlocks; int h = component.HeightInBlocks; - ref short blockDataRef = - ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); @@ -258,10 +257,11 @@ private void ParseBaselineDataNonInterleaved( int blockRow = mcu / w; int blockCol = mcu % w; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockBaseline( component, - ref Unsafe.Add(ref blockDataRef, offset), + blockRow, + blockCol, ref dcHuffmanTable, ref acHuffmanTable, ref fastACRef); @@ -330,7 +330,8 @@ private void ParseProgressiveDataInterleaved(PdfJsFrame frame, PdfJsHuffmanTable int offset = component.GetBlockBufferOffset(blockRow, blockCol); this.DecodeBlockProgressiveDC( component, - ref Unsafe.Add(ref blockDataRef, offset), + blockRow, + blockCol, ref dcHuffmanTable); } } @@ -362,8 +363,7 @@ private void ParseProgressiveDataNonInterleaved( int w = component.WidthInBlocks; int h = component.HeightInBlocks; - ref short blockDataRef = - ref MemoryMarshal.GetReference(MemoryMarshal.Cast(component.SpectralBlocks.Span)); + ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); @@ -380,16 +380,21 @@ private void ParseProgressiveDataNonInterleaved( int blockRow = mcu / w; int blockCol = mcu % w; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); if (this.spectralStart == 0) { - this.DecodeBlockProgressiveDC(component, ref Unsafe.Add(ref blockDataRef, offset), ref dcHuffmanTable); + this.DecodeBlockProgressiveDC( + component, + blockRow, + blockCol, + ref dcHuffmanTable); } else { this.DecodeBlockProgressiveAC( - ref Unsafe.Add(ref blockDataRef, offset), + component, + blockRow, + blockCol, ref acHuffmanTable, ref fastACRef); } @@ -406,7 +411,8 @@ ref Unsafe.Add(ref blockDataRef, offset), private void DecodeBlockBaseline( PdfJsFrameComponent component, - ref short blockDataRef, + int row, + int col, ref PdfJsHuffmanTable dcTable, ref PdfJsHuffmanTable acTable, ref short fastACRef) @@ -419,6 +425,8 @@ private void DecodeBlockBaseline( 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; @@ -482,7 +490,8 @@ private void DecodeBlockBaseline( private void DecodeBlockProgressiveDC( PdfJsFrameComponent component, - ref short blockDataRef, + int row, + int col, ref PdfJsHuffmanTable dcTable) { if (this.spectralEnd != 0) @@ -492,6 +501,8 @@ private void DecodeBlockProgressiveDC( this.CheckBits(); + ref short blockDataRef = ref component.GetBlockDataReference(row, col); + if (this.successiveHigh == 0) { // First scan for DC coefficient, must be first @@ -514,7 +525,9 @@ private void DecodeBlockProgressiveDC( } private void DecodeBlockProgressiveAC( - ref short blockDataRef, + PdfJsFrameComponent component, + int row, + int col, ref PdfJsHuffmanTable acTable, ref short fastACRef) { @@ -523,6 +536,8 @@ private void DecodeBlockProgressiveAC( 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, From df557c9312b219112fe6cf1294abf524ba000762 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 01:29:04 +0200 Subject: [PATCH 31/32] ScanDecoder: refactor parameters to members --- .../Jpeg/PdfJsPort/Components/FastACTables.cs | 9 ++ .../Jpeg/PdfJsPort/Components/ScanDecoder.cs | 108 ++++++++---------- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 7 +- 3 files changed, 61 insertions(+), 63 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs index 3e170a92c7..6cb0d6dfe5 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs @@ -34,6 +34,15 @@ public ReadOnlySpan GetTableSpan(int index) return this.tables.GetRowSpan(index); } + /// + /// Gets a reference to the first element of the AC table indexed by + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref short GetAcTableReference(PdfJsFrameComponent component) + { + return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0]; + } + /// /// Builds a lookup table for fast AC entropy scan decoding. /// diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs index 18cfc6c3fe..8575bac69e 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs @@ -23,6 +23,11 @@ internal class ScanDecoder // 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; @@ -79,7 +84,10 @@ internal class ScanDecoder /// Initializes a new instance of the class. /// /// The input stream. - /// The scan components. + /// The image frame. + /// The DC Huffman tables. + /// The AC Huffman tables. + /// The fast AC decoding tables. /// The component index within the array. /// The length of the components. Different to the array length. /// The reset interval. @@ -89,7 +97,10 @@ internal class ScanDecoder /// The successive approximation bit low end. public ScanDecoder( DoubleBufferedStreamReader stream, - PdfJsFrameComponent[] components, + PdfJsFrame frame, + PdfJsHuffmanTables dcHuffmanTables, + PdfJsHuffmanTables acHuffmanTables, + FastACTables fastACTables, int componentIndex, int componentsLength, int restartInterval, @@ -100,7 +111,11 @@ public ScanDecoder( { this.dctZigZag = ZigZag.CreateUnzigTable(); this.stream = stream; - this.components = components; + 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; @@ -115,25 +130,17 @@ public ScanDecoder( /// /// Decodes the entropy coded data. /// - /// The image frame. - /// The DC Huffman tables. - /// The AC Huffman tables. - /// The fast AC decoding tables. - public void ParseEntropyCodedData( - PdfJsFrame frame, - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - FastACTables fastACTables) + public void ParseEntropyCodedData() { this.Reset(); - if (!frame.Progressive) + if (!this.frame.Progressive) { - this.ParseBaselineData(frame, dcHuffmanTables, acHuffmanTables, fastACTables); + this.ParseBaselineData(); } else { - this.ParseProgressiveData(frame, dcHuffmanTables, acHuffmanTables, fastACTables); + this.ParseProgressiveData(); } if (this.badMarker) @@ -145,32 +152,24 @@ public void ParseEntropyCodedData( [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); - private void ParseBaselineData( - PdfJsFrame frame, - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - FastACTables fastACTables) + private void ParseBaselineData() { if (this.componentsLength == 1) { - this.ParseBaselineDataNonInterleaved(dcHuffmanTables, acHuffmanTables, fastACTables); + this.ParseBaselineDataNonInterleaved(); } else { - this.ParseBaselineDataInterleaved(frame, dcHuffmanTables, acHuffmanTables, fastACTables); + this.ParseBaselineDataInterleaved(); } } - private void ParseBaselineDataInterleaved( - PdfJsFrame frame, - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - FastACTables fastACTables) + private void ParseBaselineDataInterleaved() { // Interleaved int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; for (int j = 0; j < mcusPerColumn; j++) { for (int i = 0; i < mcusPerLine; i++) @@ -180,10 +179,9 @@ private void ParseBaselineDataInterleaved( { PdfJsFrameComponent component = this.components[k]; - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = - ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); + 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; @@ -231,19 +229,16 @@ private void ParseBaselineDataInterleaved( /// number of blocks to do just depends on how many actual "pixels" /// component has, independent of interleaved MCU blocking and such /// - private void ParseBaselineDataNonInterleaved( - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - FastACTables fastACTables) + private void ParseBaselineDataNonInterleaved() { PdfJsFrameComponent component = this.components[this.componentIndex]; int w = component.WidthInBlocks; int h = component.HeightInBlocks; - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); + 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++) @@ -276,28 +271,24 @@ private void ParseBaselineDataNonInterleaved( } } - private void ParseProgressiveData( - PdfJsFrame frame, - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - FastACTables fastACTables) + private void ParseProgressiveData() { if (this.componentsLength == 1) { - this.ParseProgressiveDataNonInterleaved(dcHuffmanTables, acHuffmanTables, fastACTables); + this.ParseProgressiveDataNonInterleaved(); } else { - this.ParseProgressiveDataInterleaved(frame, dcHuffmanTables); + this.ParseProgressiveDataInterleaved(); } } - private void ParseProgressiveDataInterleaved(PdfJsFrame frame, PdfJsHuffmanTables dcHuffmanTables) + private void ParseProgressiveDataInterleaved() { // Interleaved int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; for (int j = 0; j < mcusPerColumn; j++) { for (int i = 0; i < mcusPerLine; i++) @@ -306,9 +297,7 @@ private void ParseProgressiveDataInterleaved(PdfJsFrame frame, PdfJsHuffmanTable for (int k = 0; k < this.componentsLength; k++) { PdfJsFrameComponent component = this.components[k]; - ref short blockDataRef = ref MemoryMarshal.GetReference( - MemoryMarshal.Cast(component.SpectralBlocks.Span)); - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; + ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -327,7 +316,7 @@ private void ParseProgressiveDataInterleaved(PdfJsFrame frame, PdfJsHuffmanTable int mcuCol = mcu % mcusPerLine; int blockRow = (mcuRow * v) + y; int blockCol = (mcuCol * h) + x; - int offset = component.GetBlockBufferOffset(blockRow, blockCol); + this.DecodeBlockProgressiveDC( component, blockRow, @@ -354,19 +343,16 @@ private void ParseProgressiveDataInterleaved(PdfJsFrame frame, PdfJsHuffmanTable /// number of blocks to do just depends on how many actual "pixels" this /// component has, independent of interleaved MCU blocking and such /// - private void ParseProgressiveDataNonInterleaved( - PdfJsHuffmanTables dcHuffmanTables, - PdfJsHuffmanTables acHuffmanTables, - FastACTables fastACTables) + private void ParseProgressiveDataNonInterleaved() { PdfJsFrameComponent component = this.components[this.componentIndex]; int w = component.WidthInBlocks; int h = component.HeightInBlocks; - ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; - ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; - ref short fastACRef = ref MemoryMarshal.GetReference(fastACTables.GetTableSpan(component.ACHuffmanTableId)); + 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++) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index cb52fb84b3..a360d54771 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -806,7 +806,10 @@ private void ProcessStartOfScanMarker() var sd = new ScanDecoder( this.InputStream, - this.Frame.Components, + this.Frame, + this.dcHuffmanTables, + this.acHuffmanTables, + this.fastACTables, componentIndex, selectorsCount, this.resetInterval, @@ -815,7 +818,7 @@ private void ProcessStartOfScanMarker() successiveApproximation >> 4, successiveApproximation & 15); - sd.ParseEntropyCodedData(this.Frame, this.dcHuffmanTables, this.acHuffmanTables, this.fastACTables); + sd.ParseEntropyCodedData(); } /// From 227789628b61b7c3f4c25dcb846f75485de1dac4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 3 Jul 2018 01:34:15 +0200 Subject: [PATCH 32/32] temporal vortex attacked again --- src/ImageSharp/Common/Helpers/InliningOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs index d218acdbaa..e1d51da8d4 100644 --- a/src/ImageSharp/Common/Helpers/InliningOptions.cs +++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp { /// - /// Global inlining options. Helps temporally disable inling for better profiler output. + /// Global inlining options. Helps temporarily disable inling for better profiler output. /// internal static class InliningOptions {