diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
index 638687ae55..6f5cabb09b 100644
--- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
+++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
@@ -39,7 +39,7 @@
-
+
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs
deleted file mode 100644
index dc78a89ddb..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTable.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System.Runtime.CompilerServices;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
-{
- internal unsafe struct FastACTable
- {
- ///
- /// Gets the lookahead array.
- ///
- public fixed short Lookahead[512];
-
- ///
- /// Derives a lookup table for fast AC entropy scan decoding.
- /// This can happen multiple times during progressive decoding but always outside mcu loops.
- ///
- /// The AC Huffman table.
- public void Derive(ref HuffmanTable huffmanTable)
- {
- const int FastBits = ScanDecoder.FastBits;
- ref short fastACRef = ref this.Lookahead[0];
- ref byte huffmanLookaheadRef = ref huffmanTable.Lookahead[0];
- ref byte huffmanValuesRef = ref huffmanTable.Values[0];
- ref short huffmanSizesRef = ref huffmanTable.Sizes[0];
-
- int i;
- for (i = 0; i < (1 << FastBits); i++)
- {
- byte fast = Unsafe.Add(ref huffmanLookaheadRef, i);
- Unsafe.Add(ref fastACRef, i) = 0;
-
- if (fast < byte.MaxValue)
- {
- int rs = Unsafe.Add(ref huffmanValuesRef, fast);
- int run = (rs >> 4) & 15;
- int magbits = rs & 15;
- int len = Unsafe.Add(ref huffmanSizesRef, fast);
-
- if (magbits != 0 && len + magbits <= FastBits)
- {
- // Magnitude code followed by receive_extend code
- int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits);
- int m = 1 << (magbits - 1);
- if (k < m)
- {
- k += (int)((~0U << magbits) + 1);
- }
-
- // If the result is small enough, we can fit it in fastAC table
- if (k >= -128 && k <= 127)
- {
- Unsafe.Add(ref fastACRef, i) = (short)((k << 8) + (run << 4) + (len + magbits));
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
new file mode 100644
index 0000000000..72bfa38646
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
@@ -0,0 +1,188 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.IO;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ ///
+ /// Used to buffer and track the bits read from the Huffman entropy encoded data.
+ ///
+ internal struct HuffmanScanBuffer
+ {
+ private readonly DoubleBufferedStreamReader stream;
+
+ // The entropy encoded code buffer.
+ private ulong data;
+
+ // The number of valid bits left to read in the buffer.
+ private int remain;
+
+ // Whether there is more data to pull from the stream for the current mcu.
+ private bool noMore;
+
+ public HuffmanScanBuffer(DoubleBufferedStreamReader stream)
+ {
+ this.stream = stream;
+ this.data = 0ul;
+ this.remain = 0;
+ this.Marker = JpegConstants.Markers.XFF;
+ this.MarkerPosition = 0;
+ this.BadMarker = false;
+ this.noMore = false;
+ this.Eof = false;
+ }
+
+ ///
+ /// Gets or sets the current, if any, marker in the input stream.
+ ///
+ public byte Marker { get; set; }
+
+ ///
+ /// Gets or sets the opening position of an identified marker.
+ ///
+ public long MarkerPosition { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether we have a bad marker, I.E. One that is not between RST0 and RST7
+ ///
+ public bool BadMarker { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether we have prematurely reached the end of the file.
+ ///
+ public bool Eof { get; set; }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void CheckBits()
+ {
+ if (this.remain < 16)
+ {
+ this.FillBuffer();
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Reset()
+ {
+ this.data = 0ul;
+ this.remain = 0;
+ this.Marker = JpegConstants.Markers.XFF;
+ this.MarkerPosition = 0;
+ this.BadMarker = false;
+ this.noMore = false;
+ this.Eof = false;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool HasRestart()
+ {
+ byte m = this.Marker;
+ return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void FillBuffer()
+ {
+ // Attempt to load at least the minimum number 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.
+ this.remain += 48;
+ this.data = (this.data << 48) | this.GetBytes();
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public unsafe int DecodeHuffman(ref HuffmanTable h)
+ {
+ this.CheckBits();
+ int v = this.PeekBits(JpegConstants.Huffman.LookupBits);
+ int symbol = h.LookaheadValue[v];
+ int size = h.LookaheadSize[v];
+
+ if (size == JpegConstants.Huffman.SlowBits)
+ {
+ ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remain);
+ while (x > h.MaxCode[size])
+ {
+ size++;
+ }
+
+ v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size));
+ symbol = h.Values[h.ValOffset[size] + v];
+ }
+
+ this.remain -= size;
+
+ return symbol;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public int Receive(int nbits)
+ {
+ this.CheckBits();
+ return Extend(this.GetBits(nbits), nbits);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remain -= nbits, nbits);
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remain - nbits, nbits);
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1));
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private ulong GetBytes()
+ {
+ ulong temp = 0;
+ for (int i = 0; i < 6; i++)
+ {
+ int b = this.noMore ? 0 : this.stream.ReadByte();
+
+ if (b == -1)
+ {
+ // We've encountered the end of the file stream which means there's no EOI marker in the image
+ // or the SOS marker has the wrong dimensions set.
+ this.Eof = true;
+ b = 0;
+ }
+
+ // Found a marker.
+ if (b == JpegConstants.Markers.XFF)
+ {
+ this.MarkerPosition = this.stream.Position - 1;
+ int c = this.stream.ReadByte();
+ while (c == JpegConstants.Markers.XFF)
+ {
+ c = this.stream.ReadByte();
+
+ if (c == -1)
+ {
+ this.Eof = true;
+ c = 0;
+ break;
+ }
+ }
+
+ if (c != 0)
+ {
+ this.Marker = (byte)c;
+ this.noMore = true;
+ if (!this.HasRestart())
+ {
+ this.BadMarker = true;
+ }
+ }
+ }
+
+ temp = (temp << 8) | (ulong)(long)b;
+ }
+
+ return temp;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
new file mode 100644
index 0000000000..76fea92976
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
@@ -0,0 +1,694 @@
+// 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.IO;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ ///
+ /// Decodes the Huffman encoded spectral scan.
+ /// Originally ported from
+ /// with additional fixes for both performance and common encoding errors.
+ ///
+ internal class HuffmanScanDecoder
+ {
+ private readonly JpegFrame frame;
+ private readonly HuffmanTable[] dcHuffmanTables;
+ private readonly HuffmanTable[] acHuffmanTables;
+ private readonly DoubleBufferedStreamReader stream;
+ private readonly JpegComponent[] components;
+
+ // The restart interval.
+ private readonly int restartInterval;
+
+ // The number of interleaved components.
+ private readonly int componentsLength;
+
+ // The spectral selection start.
+ private readonly int spectralStart;
+
+ // The spectral selection end.
+ private readonly int spectralEnd;
+
+ // The successive approximation high bit end.
+ private readonly int successiveHigh;
+
+ // The successive approximation low bit end.
+ private readonly int successiveLow;
+
+ // 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;
+
+ // The unzig data.
+ private ZigZag dctZigZag;
+
+ private HuffmanScanBuffer scanBuffer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input stream.
+ /// The image frame.
+ /// The DC Huffman tables.
+ /// The AC Huffman tables.
+ /// 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 HuffmanScanDecoder(
+ DoubleBufferedStreamReader stream,
+ JpegFrame frame,
+ HuffmanTable[] dcHuffmanTables,
+ HuffmanTable[] acHuffmanTables,
+ int componentsLength,
+ int restartInterval,
+ int spectralStart,
+ int spectralEnd,
+ int successiveHigh,
+ int successiveLow)
+ {
+ this.dctZigZag = ZigZag.CreateUnzigTable();
+ this.stream = stream;
+ this.scanBuffer = new HuffmanScanBuffer(stream);
+ this.frame = frame;
+ this.dcHuffmanTables = dcHuffmanTables;
+ this.acHuffmanTables = acHuffmanTables;
+ this.components = frame.Components;
+ this.componentsLength = componentsLength;
+ this.restartInterval = restartInterval;
+ this.todo = restartInterval;
+ this.spectralStart = spectralStart;
+ this.spectralEnd = spectralEnd;
+ this.successiveHigh = successiveHigh;
+ this.successiveLow = successiveLow;
+ }
+
+ ///
+ /// Decodes the entropy coded data.
+ ///
+ public void ParseEntropyCodedData()
+ {
+ if (!this.frame.Progressive)
+ {
+ this.ParseBaselineData();
+ }
+ else
+ {
+ this.ParseProgressiveData();
+ }
+
+ if (this.scanBuffer.BadMarker)
+ {
+ this.stream.Position = this.scanBuffer.MarkerPosition;
+ }
+ }
+
+ private void ParseBaselineData()
+ {
+ if (this.componentsLength == 1)
+ {
+ this.ParseBaselineDataNonInterleaved();
+ }
+ else
+ {
+ this.ParseBaselineDataInterleaved();
+ }
+ }
+
+ private unsafe void ParseBaselineDataInterleaved()
+ {
+ // Interleaved
+ int mcu = 0;
+ int mcusPerColumn = this.frame.McusPerColumn;
+ int mcusPerLine = this.frame.McusPerLine;
+
+ // Pre-derive the huffman table to avoid in-loop checks.
+ for (int i = 0; i < this.componentsLength; i++)
+ {
+ int order = this.frame.ComponentOrder[i];
+ JpegComponent component = this.components[order];
+
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ dcHuffmanTable.Configure();
+ acHuffmanTable.Configure();
+ }
+
+ for (int j = 0; j < mcusPerColumn; j++)
+ {
+ for (int i = 0; i < mcusPerLine; i++)
+ {
+ // Scan an interleaved mcu... process components in order
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ for (int k = 0; k < this.componentsLength; k++)
+ {
+ int order = this.frame.ComponentOrder[k];
+ JpegComponent component = this.components[order];
+
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[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 blockRow = (mcuRow * v) + y;
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int x = 0; x < h; x++)
+ {
+ int blockCol = (mcuCol * h) + x;
+
+ this.DecodeBlockBaseline(
+ component,
+ ref Unsafe.Add(ref blockRef, blockCol),
+ ref dcHuffmanTable,
+ ref acHuffmanTable);
+ }
+ }
+ }
+
+ // After all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ mcu++;
+ this.HandleRestart();
+ }
+ }
+ }
+
+ private unsafe void ParseBaselineDataNonInterleaved()
+ {
+ JpegComponent component = this.components[this.frame.ComponentOrder[0]];
+
+ int w = component.WidthInBlocks;
+ int h = component.HeightInBlocks;
+
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ dcHuffmanTable.Configure();
+ acHuffmanTable.Configure();
+
+ int mcu = 0;
+ for (int j = 0; j < h; j++)
+ {
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int i = 0; i < w; i++)
+ {
+ this.DecodeBlockBaseline(
+ component,
+ ref Unsafe.Add(ref blockRef, i),
+ ref dcHuffmanTable,
+ ref acHuffmanTable);
+
+ // Every data block is an MCU, so countdown the restart interval
+ mcu++;
+
+ this.HandleRestart();
+ }
+ }
+ }
+
+ private void CheckProgressiveData()
+ {
+ // Validate successive scan parameters.
+ // Logic has been adapted from libjpeg.
+ // See Table B.3 – Scan header parameter size and values. itu-t81.pdf
+ bool invalid = false;
+ if (this.spectralStart == 0)
+ {
+ if (this.spectralEnd != 0)
+ {
+ invalid = true;
+ }
+ }
+ else
+ {
+ // Need not check Ss/Se < 0 since they came from unsigned bytes.
+ if (this.spectralEnd < this.spectralStart || this.spectralEnd > 63)
+ {
+ invalid = true;
+ }
+
+ // AC scans may have only one component.
+ if (this.componentsLength != 1)
+ {
+ invalid = true;
+ }
+ }
+
+ if (this.successiveHigh != 0)
+ {
+ // Successive approximation refinement scan: must have Al = Ah-1.
+ if (this.successiveHigh - 1 != this.successiveLow)
+ {
+ invalid = true;
+ }
+ }
+
+ // TODO: How does this affect 12bit jpegs.
+ // According to libjpeg the range covers 8bit only?
+ if (this.successiveLow > 13)
+ {
+ invalid = true;
+ }
+
+ if (invalid)
+ {
+ JpegThrowHelper.ThrowBadProgressiveScan(this.spectralStart, this.spectralEnd, this.successiveHigh, this.successiveLow);
+ }
+ }
+
+ private void ParseProgressiveData()
+ {
+ this.CheckProgressiveData();
+
+ if (this.componentsLength == 1)
+ {
+ this.ParseProgressiveDataNonInterleaved();
+ }
+ else
+ {
+ this.ParseProgressiveDataInterleaved();
+ }
+ }
+
+ private void ParseProgressiveDataInterleaved()
+ {
+ // Interleaved
+ int mcu = 0;
+ int mcusPerColumn = this.frame.McusPerColumn;
+ int mcusPerLine = this.frame.McusPerLine;
+
+ // Pre-derive the huffman table to avoid in-loop checks.
+ for (int k = 0; k < this.componentsLength; k++)
+ {
+ int order = this.frame.ComponentOrder[k];
+ JpegComponent component = this.components[order];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ dcHuffmanTable.Configure();
+ }
+
+ for (int j = 0; j < mcusPerColumn; j++)
+ {
+ for (int i = 0; i < mcusPerLine; i++)
+ {
+ // Scan an interleaved mcu... process components in order
+ int mcuRow = mcu / mcusPerLine;
+ int mcuCol = mcu % mcusPerLine;
+ for (int k = 0; k < this.componentsLength; k++)
+ {
+ int order = this.frame.ComponentOrder[k];
+ JpegComponent component = this.components[order];
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+
+ 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 blockRow = (mcuRow * v) + y;
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int x = 0; x < h; x++)
+ {
+ if (buffer.Eof)
+ {
+ return;
+ }
+
+ int blockCol = (mcuCol * h) + x;
+
+ this.DecodeBlockProgressiveDC(
+ component,
+ ref Unsafe.Add(ref blockRef, blockCol),
+ ref dcHuffmanTable);
+ }
+ }
+ }
+
+ // After all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ mcu++;
+ this.HandleRestart();
+ }
+ }
+ }
+
+ private unsafe void ParseProgressiveDataNonInterleaved()
+ {
+ JpegComponent component = this.components[this.frame.ComponentOrder[0]];
+ ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+
+ int w = component.WidthInBlocks;
+ int h = component.HeightInBlocks;
+
+ if (this.spectralStart == 0)
+ {
+ ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
+ dcHuffmanTable.Configure();
+
+ int mcu = 0;
+ for (int j = 0; j < h; j++)
+ {
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int i = 0; i < w; i++)
+ {
+ if (buffer.Eof)
+ {
+ return;
+ }
+
+ this.DecodeBlockProgressiveDC(
+ component,
+ ref Unsafe.Add(ref blockRef, i),
+ ref dcHuffmanTable);
+
+ // Every data block is an MCU, so countdown the restart interval
+ mcu++;
+ this.HandleRestart();
+ }
+ }
+ }
+ else
+ {
+ ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
+ acHuffmanTable.Configure();
+
+ int mcu = 0;
+ for (int j = 0; j < h; j++)
+ {
+ Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
+ ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
+
+ for (int i = 0; i < w; i++)
+ {
+ if (buffer.Eof)
+ {
+ return;
+ }
+
+ this.DecodeBlockProgressiveAC(
+ ref Unsafe.Add(ref blockRef, i),
+ ref acHuffmanTable);
+
+ // Every data block is an MCU, so countdown the restart interval
+ mcu++;
+ this.HandleRestart();
+ }
+ }
+ }
+ }
+
+ private void DecodeBlockBaseline(
+ JpegComponent component,
+ ref Block8x8 block,
+ ref HuffmanTable dcTable,
+ ref HuffmanTable acTable)
+ {
+ ref short blockDataRef = ref Unsafe.As(ref block);
+ ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref ZigZag zigzag = ref this.dctZigZag;
+
+ // DC
+ int t = buffer.DecodeHuffman(ref dcTable);
+ if (t != 0)
+ {
+ t = buffer.Receive(t);
+ }
+
+ t += component.DcPredictor;
+ component.DcPredictor = t;
+ blockDataRef = (short)t;
+
+ // AC
+ for (int i = 1; i < 64;)
+ {
+ int s = buffer.DecodeHuffman(ref acTable);
+
+ int r = s >> 4;
+ s &= 15;
+
+ if (s != 0)
+ {
+ i += r;
+ s = buffer.Receive(s);
+ Unsafe.Add(ref blockDataRef, zigzag[i++]) = (short)s;
+ }
+ else
+ {
+ if (r == 0)
+ {
+ break;
+ }
+
+ i += 16;
+ }
+ }
+ }
+
+ private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable)
+ {
+ ref short blockDataRef = ref Unsafe.As(ref block);
+ ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+
+ if (this.successiveHigh == 0)
+ {
+ // First scan for DC coefficient, must be first
+ int s = buffer.DecodeHuffman(ref dcTable);
+ if (s != 0)
+ {
+ s = buffer.Receive(s);
+ }
+
+ s += component.DcPredictor;
+ component.DcPredictor = s;
+ blockDataRef = (short)(s << this.successiveLow);
+ }
+ else
+ {
+ // Refinement scan for DC coefficient
+ buffer.CheckBits();
+ blockDataRef |= (short)(buffer.GetBits(1) << this.successiveLow);
+ }
+ }
+
+ private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable)
+ {
+ ref short blockDataRef = ref Unsafe.As(ref block);
+ if (this.successiveHigh == 0)
+ {
+ // MCU decoding for AC initial scan (either spectral selection,
+ // or first pass of successive approximation).
+ if (this.eobrun != 0)
+ {
+ --this.eobrun;
+ return;
+ }
+
+ ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref ZigZag zigzag = ref this.dctZigZag;
+ int start = this.spectralStart;
+ int end = this.spectralEnd;
+ int low = this.successiveLow;
+
+ for (int i = start; i <= end; ++i)
+ {
+ int s = buffer.DecodeHuffman(ref acTable);
+ int r = s >> 4;
+ s &= 15;
+
+ i += r;
+
+ if (s != 0)
+ {
+ s = buffer.Receive(s);
+ Unsafe.Add(ref blockDataRef, zigzag[i]) = (short)(s << low);
+ }
+ else
+ {
+ if (r != 15)
+ {
+ this.eobrun = 1 << r;
+ if (r != 0)
+ {
+ buffer.CheckBits();
+ this.eobrun += buffer.GetBits(r);
+ }
+
+ --this.eobrun;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Refinement scan for these AC coefficients
+ this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable);
+ }
+ }
+
+ private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable)
+ {
+ // Refinement scan for these AC coefficients
+ ref HuffmanScanBuffer buffer = ref this.scanBuffer;
+ ref ZigZag zigzag = ref this.dctZigZag;
+ int start = this.spectralStart;
+ int end = this.spectralEnd;
+
+ int p1 = 1 << this.successiveLow;
+ int m1 = (-1) << this.successiveLow;
+
+ int k = start;
+
+ if (this.eobrun == 0)
+ {
+ for (; k <= end; k++)
+ {
+ int s = buffer.DecodeHuffman(ref acTable);
+ int r = s >> 4;
+ s &= 15;
+
+ if (s != 0)
+ {
+ buffer.CheckBits();
+ if (buffer.GetBits(1) != 0)
+ {
+ s = p1;
+ }
+ else
+ {
+ s = m1;
+ }
+ }
+ else
+ {
+ if (r != 15)
+ {
+ this.eobrun = 1 << r;
+
+ if (r != 0)
+ {
+ buffer.CheckBits();
+ this.eobrun += buffer.GetBits(r);
+ }
+
+ break;
+ }
+ }
+
+ do
+ {
+ ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]);
+ if (coef != 0)
+ {
+ buffer.CheckBits();
+ if (buffer.GetBits(1) != 0)
+ {
+ if ((coef & p1) == 0)
+ {
+ coef += (short)(coef >= 0 ? p1 : m1);
+ }
+ }
+ }
+ else
+ {
+ if (--r < 0)
+ {
+ break;
+ }
+ }
+
+ k++;
+ }
+ while (k <= end);
+
+ if ((s != 0) && (k < 64))
+ {
+ Unsafe.Add(ref blockDataRef, zigzag[k]) = (short)s;
+ }
+ }
+ }
+
+ if (this.eobrun > 0)
+ {
+ for (; k <= end; k++)
+ {
+ ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]);
+
+ if (coef != 0)
+ {
+ buffer.CheckBits();
+ if (buffer.GetBits(1) != 0)
+ {
+ if ((coef & p1) == 0)
+ {
+ coef += (short)(coef >= 0 ? p1 : m1);
+ }
+ }
+ }
+ }
+
+ --this.eobrun;
+ }
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void Reset()
+ {
+ for (int i = 0; i < this.components.Length; i++)
+ {
+ this.components[i].DcPredictor = 0;
+ }
+
+ this.eobrun = 0;
+ this.scanBuffer.Reset();
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private bool HandleRestart()
+ {
+ if (this.restartInterval > 0 && (--this.todo) == 0)
+ {
+ this.todo = this.restartInterval;
+
+ if (this.scanBuffer.HasRestart())
+ {
+ this.Reset();
+ return true;
+ }
+
+ if (this.scanBuffer.Marker != JpegConstants.Markers.XFF)
+ {
+ this.stream.Position = this.scanBuffer.MarkerPosition;
+ this.Reset();
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
index 7e025e271e..21ed5018f3 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
@@ -2,53 +2,60 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
-using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
///
- /// Represents a Huffman Table
+ /// Represents a Huffman coding table containing basic coding data plus tables for accellerated computation.
///
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct HuffmanTable
{
- private bool isDerived;
-
private readonly MemoryAllocator memoryAllocator;
+ private bool isConfigured;
-#pragma warning disable IDE0044 // Add readonly modifier
- private fixed byte codeLengths[17];
-#pragma warning restore IDE0044 // Add readonly modifier
+ ///
+ /// Derived from the DHT marker. Sizes[k] = # of symbols with codes of length k bits; Sizes[0] is unused.
+ ///
+ public fixed byte Sizes[17];
///
- /// Gets the max code array.
+ /// Derived from the DHT marker. Contains the symbols, in order of incremental code length.
///
- public fixed uint MaxCode[18];
+ public fixed byte Values[256];
///
- /// Gets the value offset array.
+ /// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to
+ /// ensure terminates.
///
- public fixed int ValOffset[18];
+ public fixed ulong MaxCode[18];
///
- /// Gets the huffman value array.
+ /// Values[] offset for codes of length k ValOffset[k] = Values[] index of 1st symbol of code length
+ /// k, less the smallest code of length k; so given a code of length k, the corresponding symbol is
+ /// Values[code + ValOffset[k]].
///
- public fixed byte Values[256];
+ public fixed int ValOffset[19];
///
- /// Gets the lookahead array.
+ /// Contains the length of bits for the given k value.
///
- public fixed byte Lookahead[512];
+ public fixed byte LookaheadSize[JpegConstants.Huffman.LookupSize];
///
- /// Gets the sizes array
+ /// Lookahead table: indexed by the next bits of
+ /// the input data stream. If the next Huffman code is no more
+ /// than bits long, we can obtain its length and
+ /// the corresponding symbol directly from this tables.
+ ///
+ /// The lower 8 bits of each table entry contain the number of
+ /// bits in the corresponding Huffman code, or + 1
+ /// if too long. The next 8 bits of each entry contain the symbol.
///
- public fixed short Sizes[257];
+ public fixed byte LookaheadValue[JpegConstants.Huffman.LookupSize];
///
/// Initializes a new instance of the struct.
@@ -58,91 +65,104 @@ internal unsafe struct HuffmanTable
/// The huffman values
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan codeLengths, ReadOnlySpan values)
{
- this.isDerived = false;
+ this.isConfigured = false;
this.memoryAllocator = memoryAllocator;
- Unsafe.CopyBlockUnaligned(ref this.codeLengths[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length);
+ Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length);
Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length);
}
///
- /// Expands the HuffmanTable into its derived form.
+ /// Expands the HuffmanTable into its readable form.
///
- public void Derive()
+ public void Configure()
{
- if (this.isDerived)
+ if (this.isConfigured)
{
return;
}
- const int Length = 257;
- using (IMemoryOwner huffcode = this.memoryAllocator.Allocate(Length))
- {
- ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan());
- ref byte codeLengthsRef = ref this.codeLengths[0];
+ int p, si;
+ Span huffsize = stackalloc char[257];
+ Span huffcode = stackalloc uint[257];
+ uint code;
- // Figure C.1: make table of Huffman code length for each symbol
- ref short sizesRef = ref this.Sizes[0];
- short x = 0;
- for (short i = 1; i < 17; i++)
+ // Figure C.1: make table of Huffman code length for each symbol
+ p = 0;
+ for (int l = 1; l <= 16; l++)
+ {
+ int i = this.Sizes[l];
+ while (i-- != 0)
{
- byte length = Unsafe.Add(ref codeLengthsRef, i);
- for (short j = 0; j < length; j++)
- {
- Unsafe.Add(ref sizesRef, x++) = i;
- }
+ huffsize[p++] = (char)l;
}
+ }
- Unsafe.Add(ref sizesRef, x) = 0;
-
- // Figure C.2: generate the codes themselves
- int si = 0;
- ref int valOffsetRef = ref this.ValOffset[0];
- ref uint maxcodeRef = ref this.MaxCode[0];
+ huffsize[p] = (char)0;
- uint code = 0;
- int k;
- for (k = 1; k < 17; k++)
+ // Figure C.2: generate the codes themselves
+ code = 0;
+ si = huffsize[0];
+ p = 0;
+ while (huffsize[p] != 0)
+ {
+ while (huffsize[p] == si)
{
- // Compute delta to add to code to compute symbol id.
- Unsafe.Add(ref valOffsetRef, k) = (int)(si - code);
- if (Unsafe.Add(ref sizesRef, si) == k)
- {
- while (Unsafe.Add(ref sizesRef, si) == k)
- {
- Unsafe.Add(ref huffcodeRef, si++) = (short)code++;
- }
- }
+ huffcode[p++] = code;
+ code++;
+ }
+
+ code <<= 1;
+ si++;
+ }
- // Figure F.15: generate decoding tables for bit-sequential decoding.
- // Compute largest code + 1 for this size. preshifted as we need it later.
- Unsafe.Add(ref maxcodeRef, k) = code << (16 - k);
- code <<= 1;
+ // Figure F.15: generate decoding tables for bit-sequential decoding
+ p = 0;
+ for (int l = 1; l <= 16; l++)
+ {
+ if (this.Sizes[l] != 0)
+ {
+ int offset = p - (int)huffcode[p];
+ this.ValOffset[l] = offset;
+ p += this.Sizes[l];
+ this.MaxCode[l] = huffcode[p - 1]; // Maximum code of length l
+ this.MaxCode[l] <<= 64 - l; // Left justify
+ this.MaxCode[l] |= (1ul << (64 - l)) - 1;
+ }
+ else
+ {
+ this.MaxCode[l] = 0;
}
+ }
- Unsafe.Add(ref maxcodeRef, k) = 0xFFFFFFFF;
+ this.ValOffset[18] = 0;
+ this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates
- // Generate non-spec lookup tables to speed up decoding.
- const int FastBits = ScanDecoder.FastBits;
- ref byte lookaheadRef = ref this.Lookahead[0];
- Unsafe.InitBlockUnaligned(ref lookaheadRef, 0xFF, 1 << FastBits); // Flag for non-accelerated
+ // Compute lookahead tables to speed up decoding.
+ // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long";
+ // then we iterate through the Huffman codes that are short enough and
+ // fill in all the entries that correspond to bit sequences starting
+ // with that code.
+ ref byte lookupSizeRef = ref this.LookaheadSize[0];
+ Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize);
- for (int i = 0; i < si; i++)
+ p = 0;
+ for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++)
+ {
+ for (int i = 1; i <= this.Sizes[length]; i++, p++)
{
- int size = Unsafe.Add(ref sizesRef, i);
- if (size <= FastBits)
+ // length = current code's length, p = its index in huffcode[] & huffval[].
+ // Generate left-justified code followed by all possible bit sequences
+ int lookbits = (int)(huffcode[p] << (JpegConstants.Huffman.LookupBits - length));
+ for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--)
{
- int fastOffset = FastBits - size;
- int fastCode = Unsafe.Add(ref huffcodeRef, i) << fastOffset;
- int fastMax = 1 << fastOffset;
- for (int left = 0; left < fastMax; left++)
- {
- Unsafe.Add(ref lookaheadRef, fastCode + left) = (byte)i;
- }
+ this.LookaheadSize[lookbits] = (byte)length;
+ this.LookaheadValue[lookbits] = this.Values[p];
+ lookbits++;
}
}
}
- this.isDerived = true;
+ this.isConfigured = true;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
deleted file mode 100644
index ef4b359ff2..0000000000
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
+++ /dev/null
@@ -1,1058 +0,0 @@
-// 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.IO;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
-{
- ///
- /// Decodes the Huffman encoded spectral scan.
- /// Originally ported from
- /// with additional fixes for both performance and common encoding errors.
- ///
- internal class ScanDecoder
- {
- // The number of bits that can be read via a LUT.
- public const int FastBits = 9;
-
- // LUT mask for n rightmost bits. Bmask[n] = (1 << n) - 1
- private static readonly uint[] Bmask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 };
-
- // LUT Bias[n] = (-1 << n) + 1
- private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 };
-
- private readonly JpegFrame frame;
- private readonly HuffmanTable[] dcHuffmanTables;
- private readonly HuffmanTable[] acHuffmanTables;
- private readonly FastACTable[] fastACTables;
-
- private readonly DoubleBufferedStreamReader stream;
- private readonly JpegComponent[] components;
- private readonly ZigZag dctZigZag;
-
- // The restart interval.
- private readonly int restartInterval;
-
- // The number of interleaved components.
- private readonly int componentsLength;
-
- // The spectral selection start.
- private readonly int spectralStart;
-
- // The spectral selection end.
- private readonly int spectralEnd;
-
- // The successive approximation high bit end.
- private readonly int successiveHigh;
-
- // The successive approximation low bit end.
- private readonly int successiveLow;
-
- // The number of valid bits left to read in the buffer.
- private int codeBits;
-
- // The entropy encoded code buffer.
- private uint codeBuffer;
-
- // Whether there is more data to pull from the stream for the current mcu.
- private bool nomore;
-
- // Whether we have prematurely reached the end of the file.
- private bool eof;
-
- // The current, if any, marker in the input stream.
- private byte marker;
-
- // Whether we have a bad marker, I.E. One that is not between RST0 and RST7
- private bool badMarker;
-
- // The opening position of an identified marker.
- private long markerPosition;
-
- // How many mcu's are left to do.
- private int todo;
-
- // The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
- private int eobrun;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The input stream.
- /// The image frame.
- /// The DC Huffman tables.
- /// The AC Huffman tables.
- /// The fast AC decoding tables.
- /// The 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,
- JpegFrame frame,
- HuffmanTable[] dcHuffmanTables,
- HuffmanTable[] acHuffmanTables,
- FastACTable[] fastACTables,
- int componentsLength,
- int restartInterval,
- int spectralStart,
- int spectralEnd,
- int successiveHigh,
- int successiveLow)
- {
- this.dctZigZag = ZigZag.CreateUnzigTable();
- this.stream = stream;
- this.frame = frame;
- this.dcHuffmanTables = dcHuffmanTables;
- this.acHuffmanTables = acHuffmanTables;
- this.fastACTables = fastACTables;
- this.components = frame.Components;
- this.marker = JpegConstants.Markers.XFF;
- this.markerPosition = 0;
- this.componentsLength = componentsLength;
- this.restartInterval = restartInterval;
- this.spectralStart = spectralStart;
- this.spectralEnd = spectralEnd;
- this.successiveHigh = successiveHigh;
- this.successiveLow = successiveLow;
- }
-
- ///
- /// Decodes the entropy coded data.
- ///
- public void ParseEntropyCodedData()
- {
- this.Reset();
-
- if (!this.frame.Progressive)
- {
- this.ParseBaselineData();
- }
- else
- {
- this.ParseProgressiveData();
- }
-
- if (this.badMarker)
- {
- this.stream.Position = this.markerPosition;
- }
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y));
-
- private void ParseBaselineData()
- {
- if (this.componentsLength == 1)
- {
- this.ParseBaselineDataNonInterleaved();
- }
- else
- {
- this.ParseBaselineDataInterleaved();
- }
- }
-
- private unsafe void ParseBaselineDataInterleaved()
- {
- // Interleaved
- int mcu = 0;
- int mcusPerColumn = this.frame.McusPerColumn;
- int mcusPerLine = this.frame.McusPerLine;
-
- // Pre-derive the huffman table to avoid in-loop checks.
- for (int i = 0; i < this.componentsLength; i++)
- {
- int order = this.frame.ComponentOrder[i];
- JpegComponent component = this.components[order];
-
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
- dcHuffmanTable.Derive();
- acHuffmanTable.Derive();
-
- ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
- fastAcTable.Derive(ref acHuffmanTable);
- }
-
- for (int j = 0; j < mcusPerColumn; j++)
- {
- for (int i = 0; i < mcusPerLine; i++)
- {
- // Scan an interleaved mcu... process components in order
- int mcuRow = mcu / mcusPerLine;
- int mcuCol = mcu % mcusPerLine;
- for (int k = 0; k < this.componentsLength; k++)
- {
- int order = this.frame.ComponentOrder[k];
- JpegComponent component = this.components[order];
-
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
- ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
- ref short fastACRef = ref fastAcTable.Lookahead[0];
-
- 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 blockRow = (mcuRow * v) + y;
- Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
- ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
-
- for (int x = 0; x < h; x++)
- {
- if (this.eof)
- {
- return;
- }
-
- int blockCol = (mcuCol * h) + x;
-
- this.DecodeBlockBaseline(
- component,
- ref Unsafe.Add(ref blockRef, blockCol),
- ref dcHuffmanTable,
- ref acHuffmanTable,
- ref fastACRef);
- }
- }
- }
-
- // After all interleaved components, that's an interleaved MCU,
- // so now count down the restart interval
- mcu++;
- if (!this.ContinueOnMcuComplete())
- {
- return;
- }
- }
- }
- }
-
- ///
- /// Non-interleaved data, we just need to process one block at a time in trivial scanline order
- /// number of blocks to do just depends on how many actual "pixels" each component has,
- /// independent of interleaved MCU blocking and such.
- ///
- private unsafe void ParseBaselineDataNonInterleaved()
- {
- JpegComponent component = this.components[this.frame.ComponentOrder[0]];
-
- int w = component.WidthInBlocks;
- int h = component.HeightInBlocks;
-
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
- dcHuffmanTable.Derive();
- acHuffmanTable.Derive();
-
- ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
- fastAcTable.Derive(ref acHuffmanTable);
- ref short fastACRef = ref fastAcTable.Lookahead[0];
-
- int mcu = 0;
- for (int j = 0; j < h; j++)
- {
- int blockRow = j;
- Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
- ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
-
- for (int i = 0; i < w; i++)
- {
- if (this.eof)
- {
- return;
- }
-
- this.DecodeBlockBaseline(
- component,
- ref Unsafe.Add(ref blockRef, i),
- ref dcHuffmanTable,
- ref acHuffmanTable,
- ref fastACRef);
-
- // Every data block is an MCU, so countdown the restart interval
- mcu++;
- if (!this.ContinueOnMcuComplete())
- {
- return;
- }
- }
- }
- }
-
- private void CheckProgressiveData()
- {
- // Validate successive scan parameters.
- // Logic has been adapted from libjpeg.
- // See Table B.3 – Scan header parameter size and values. itu-t81.pdf
- bool invalid = false;
- if (this.spectralStart == 0)
- {
- if (this.spectralEnd != 0)
- {
- invalid = true;
- }
- }
- else
- {
- // Need not check Ss/Se < 0 since they came from unsigned bytes.
- if (this.spectralEnd < this.spectralStart || this.spectralEnd > 63)
- {
- invalid = true;
- }
-
- // AC scans may have only one component.
- if (this.componentsLength != 1)
- {
- invalid = true;
- }
- }
-
- if (this.successiveHigh != 0)
- {
- // Successive approximation refinement scan: must have Al = Ah-1.
- if (this.successiveHigh - 1 != this.successiveLow)
- {
- invalid = true;
- }
- }
-
- // TODO: How does this affect 12bit jpegs.
- // According to libjpeg the range covers 8bit only?
- if (this.successiveLow > 13)
- {
- invalid = true;
- }
-
- if (invalid)
- {
- JpegThrowHelper.ThrowBadProgressiveScan(this.spectralStart, this.spectralEnd, this.successiveHigh, this.successiveLow);
- }
- }
-
- private void ParseProgressiveData()
- {
- this.CheckProgressiveData();
-
- if (this.componentsLength == 1)
- {
- this.ParseProgressiveDataNonInterleaved();
- }
- else
- {
- this.ParseProgressiveDataInterleaved();
- }
- }
-
- private void ParseProgressiveDataInterleaved()
- {
- // Interleaved
- int mcu = 0;
- int mcusPerColumn = this.frame.McusPerColumn;
- int mcusPerLine = this.frame.McusPerLine;
-
- // Pre-derive the huffman table to avoid in-loop checks.
- for (int k = 0; k < this.componentsLength; k++)
- {
- int order = this.frame.ComponentOrder[k];
- JpegComponent component = this.components[order];
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- dcHuffmanTable.Derive();
- }
-
- for (int j = 0; j < mcusPerColumn; j++)
- {
- for (int i = 0; i < mcusPerLine; i++)
- {
- // Scan an interleaved mcu... process components in order
- int mcuRow = mcu / mcusPerLine;
- int mcuCol = mcu % mcusPerLine;
- for (int k = 0; k < this.componentsLength; k++)
- {
- int order = this.frame.ComponentOrder[k];
- JpegComponent component = this.components[order];
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
-
- int h = component.HorizontalSamplingFactor;
- int v = component.VerticalSamplingFactor;
-
- // Scan out an mcu's worth of this component; that's just determined
- // by the basic H and V specified for the component
- for (int y = 0; y < v; y++)
- {
- int blockRow = (mcuRow * v) + y;
- Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
- ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
-
- for (int x = 0; x < h; x++)
- {
- if (this.eof)
- {
- return;
- }
-
- int blockCol = (mcuCol * h) + x;
-
- this.DecodeBlockProgressiveDC(
- component,
- ref Unsafe.Add(ref blockRef, blockCol),
- ref dcHuffmanTable);
- }
- }
- }
-
- // After all interleaved components, that's an interleaved MCU,
- // so now count down the restart interval
- mcu++;
- if (!this.ContinueOnMcuComplete())
- {
- return;
- }
- }
- }
- }
-
- ///
- /// Non-interleaved data, we just need to process one block at a time,
- /// in trivial scanline order
- /// number of blocks to do just depends on how many actual "pixels" this
- /// component has, independent of interleaved MCU blocking and such
- ///
- private unsafe void ParseProgressiveDataNonInterleaved()
- {
- JpegComponent component = this.components[this.frame.ComponentOrder[0]];
-
- int w = component.WidthInBlocks;
- int h = component.HeightInBlocks;
-
- if (this.spectralStart == 0)
- {
- ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
- dcHuffmanTable.Derive();
-
- int mcu = 0;
- for (int j = 0; j < h; j++)
- {
- Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
- ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
-
- for (int i = 0; i < w; i++)
- {
- if (this.eof)
- {
- return;
- }
-
- this.DecodeBlockProgressiveDC(
- component,
- ref Unsafe.Add(ref blockRef, i),
- ref dcHuffmanTable);
-
- // Every data block is an MCU, so countdown the restart interval
- mcu++;
- if (!this.ContinueOnMcuComplete())
- {
- return;
- }
- }
- }
- }
- else
- {
- ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
- acHuffmanTable.Derive();
-
- ref FastACTable fastAcTable = ref this.fastACTables[component.ACHuffmanTableId];
- fastAcTable.Derive(ref acHuffmanTable);
- ref short fastACRef = ref fastAcTable.Lookahead[0];
-
- int mcu = 0;
- for (int j = 0; j < h; j++)
- {
- Span blockSpan = component.SpectralBlocks.GetRowSpan(j);
- ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
-
- for (int i = 0; i < w; i++)
- {
- if (this.eof)
- {
- return;
- }
-
- this.DecodeBlockProgressiveAC(
- ref Unsafe.Add(ref blockRef, i),
- ref acHuffmanTable,
- ref fastACRef);
-
- // Every data block is an MCU, so countdown the restart interval
- mcu++;
- if (!this.ContinueOnMcuComplete())
- {
- return;
- }
- }
- }
- }
- }
-
- private void DecodeBlockBaseline(
- JpegComponent component,
- ref Block8x8 block,
- ref HuffmanTable dcTable,
- ref HuffmanTable acTable,
- ref short fastACRef)
- {
- this.CheckBits();
- int t = this.DecodeHuffman(ref dcTable);
-
- if (t < 0)
- {
- JpegThrowHelper.ThrowBadHuffmanCode();
- }
-
- ref short blockDataRef = ref Unsafe.As(ref block);
-
- int diff = t != 0 ? this.ExtendReceive(t) : 0;
- int dc = component.DcPredictor + diff;
- component.DcPredictor = dc;
- blockDataRef = (short)dc;
-
- // Decode AC Components, See Jpeg Spec
- int k = 1;
- do
- {
- int zig;
- int s;
-
- this.CheckBits();
- int c = this.PeekBits();
- int r = Unsafe.Add(ref fastACRef, c);
-
- if (r != 0)
- {
- // Fast AC path
- k += (r >> 4) & 15; // Run
- s = r & 15; // Combined Length
- this.codeBuffer <<= s;
- this.codeBits -= s;
-
- // Decode into unzigzag location
- zig = this.dctZigZag[k++];
- Unsafe.Add(ref blockDataRef, zig) = (short)(r >> 8);
- }
- else
- {
- int rs = this.DecodeHuffman(ref acTable);
-
- if (rs < 0)
- {
- JpegThrowHelper.ThrowBadHuffmanCode();
- }
-
- s = rs & 15;
- r = rs >> 4;
-
- if (s == 0)
- {
- if (rs != 0xF0)
- {
- break; // End block
- }
-
- k += 16;
- }
- else
- {
- k += r;
-
- // Decode into unzigzag location
- zig = this.dctZigZag[k++];
- Unsafe.Add(ref blockDataRef, zig) = (short)this.ExtendReceive(s);
- }
- }
- }
- while (k < 64);
- }
-
- private void DecodeBlockProgressiveDC(
- JpegComponent component,
- ref Block8x8 block,
- ref HuffmanTable dcTable)
- {
- this.CheckBits();
-
- ref short blockDataRef = ref Unsafe.As(ref block);
-
- if (this.successiveHigh == 0)
- {
- // First scan for DC coefficient, must be first
- int t = this.DecodeHuffman(ref dcTable);
- int diff = t != 0 ? this.ExtendReceive(t) : 0;
-
- int dc = component.DcPredictor + diff;
- component.DcPredictor = dc;
-
- blockDataRef = (short)(dc << this.successiveLow);
- }
- else
- {
- // Refinement scan for DC coefficient
- if (this.GetBit() != 0)
- {
- blockDataRef += (short)(1 << this.successiveLow);
- }
- }
- }
-
- private void DecodeBlockProgressiveAC(
- ref Block8x8 block,
- ref HuffmanTable acTable,
- ref short fastACRef)
- {
- ref short blockDataRef = ref Unsafe.As(ref block);
-
- if (this.successiveHigh == 0)
- {
- // MCU decoding for AC initial scan (either spectral selection,
- // or first pass of successive approximation).
- int shift = this.successiveLow;
-
- if (this.eobrun != 0)
- {
- this.eobrun--;
- return;
- }
-
- int k = this.spectralStart;
- do
- {
- int zig;
- int s;
-
- this.CheckBits();
- int c = this.PeekBits();
- int r = Unsafe.Add(ref fastACRef, c);
-
- if (r != 0)
- {
- // Fast AC path
- k += (r >> 4) & 15; // Run
- s = r & 15; // Combined length
- this.codeBuffer <<= s;
- this.codeBits -= s;
-
- // Decode into unzigzag location
- zig = this.dctZigZag[k++];
- Unsafe.Add(ref blockDataRef, zig) = (short)((r >> 8) << shift);
- }
- else
- {
- int rs = this.DecodeHuffman(ref acTable);
-
- if (rs < 0)
- {
- JpegThrowHelper.ThrowBadHuffmanCode();
- }
-
- s = rs & 15;
- r = rs >> 4;
-
- if (s == 0)
- {
- if (r < 15)
- {
- this.eobrun = 1 << r;
- if (r != 0)
- {
- this.eobrun += this.GetBits(r);
- }
-
- this.eobrun--;
- break;
- }
-
- k += 16;
- }
- else
- {
- k += r;
- zig = this.dctZigZag[k++];
- Unsafe.Add(ref blockDataRef, zig) = (short)(this.ExtendReceive(s) << shift);
- }
- }
- }
- while (k <= this.spectralEnd);
- }
- else
- {
- // Refinement scan for these AC coefficients
- this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable);
- }
- }
-
- private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable)
- {
- int k;
-
- // Refinement scan for these AC coefficients
- short bit = (short)(1 << this.successiveLow);
-
- if (this.eobrun != 0)
- {
- this.eobrun--;
- for (k = this.spectralStart; k <= this.spectralEnd; k++)
- {
- ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k]);
- if (p != 0)
- {
- if (this.GetBit() != 0)
- {
- if ((p & bit) == 0)
- {
- if (p > 0)
- {
- p += bit;
- }
- else
- {
- p -= bit;
- }
- }
- }
- }
- }
- }
- else
- {
- k = this.spectralStart;
- do
- {
- int rs = this.DecodeHuffman(ref acTable);
- if (rs < 0)
- {
- JpegThrowHelper.ThrowBadHuffmanCode();
- }
-
- int s = rs & 15;
- int r = rs >> 4;
-
- if (s == 0)
- {
- // r=15 s=0 should write 16 0s, so we just do
- // a run of 15 0s and then write s (which is 0),
- // so we don't have to do anything special here
- if (r < 15)
- {
- this.eobrun = (1 << r) - 1;
-
- if (r != 0)
- {
- this.eobrun += this.GetBits(r);
- }
-
- r = 64; // Force end of block
- }
- }
- else
- {
- if (s != 1)
- {
- JpegThrowHelper.ThrowBadHuffmanCode();
- }
-
- // Sign bit
- if (this.GetBit() != 0)
- {
- s = bit;
- }
- else
- {
- s = -bit;
- }
- }
-
- // Advance by r
- while (k <= this.spectralEnd)
- {
- ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k++]);
- if (p != 0)
- {
- if (this.GetBit() != 0)
- {
- if ((p & bit) == 0)
- {
- if (p > 0)
- {
- p += bit;
- }
- else
- {
- p -= bit;
- }
- }
- }
- }
- else
- {
- if (r == 0)
- {
- p = (short)s;
- break;
- }
-
- r--;
- }
- }
- }
- while (k <= this.spectralEnd);
- }
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private int GetBits(int n)
- {
- if (this.codeBits < n)
- {
- this.FillBuffer();
- }
-
- uint k = LRot(this.codeBuffer, n);
- uint mask = Bmask[n];
- this.codeBuffer = k & ~mask;
- k &= mask;
- this.codeBits -= n;
- return (int)k;
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private int GetBit()
- {
- if (this.codeBits < 1)
- {
- this.FillBuffer();
- }
-
- uint k = this.codeBuffer;
- this.codeBuffer <<= 1;
- this.codeBits--;
-
- return (int)(k & 0x80000000);
- }
-
- [MethodImpl(InliningOptions.ColdPath)]
- private void FillBuffer()
- {
- // Attempt to load at least the minimum number of required bits into the buffer.
- // We fail to do so only if we hit a marker or reach the end of the input stream.
- do
- {
- int b = this.nomore ? 0 : this.stream.ReadByte();
-
- if (b == -1)
- {
- // We've encountered the end of the file stream which means there's no EOI marker in the image
- // or the SOS marker has the wrong dimensions set.
- this.eof = true;
- b = 0;
- }
-
- // Found a marker.
- if (b == JpegConstants.Markers.XFF)
- {
- this.markerPosition = this.stream.Position - 1;
- int c = this.stream.ReadByte();
- while (c == JpegConstants.Markers.XFF)
- {
- c = this.stream.ReadByte();
-
- if (c == -1)
- {
- this.eof = true;
- c = 0;
- break;
- }
- }
-
- if (c != 0)
- {
- this.marker = (byte)c;
- this.nomore = true;
- if (!this.HasRestart())
- {
- this.badMarker = true;
- }
-
- return;
- }
- }
-
- this.codeBuffer |= (uint)b << (24 - this.codeBits);
- this.codeBits += 8;
- }
- while (this.codeBits <= 24);
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private unsafe int DecodeHuffman(ref HuffmanTable table)
- {
- this.CheckBits();
-
- // Look at the top FastBits and determine what symbol ID it is,
- // if the code is <= FastBits.
- int c = this.PeekBits();
- int k = table.Lookahead[c];
- if (k < 0xFF)
- {
- int s = table.Sizes[k];
- if (s > this.codeBits)
- {
- return -1;
- }
-
- this.codeBuffer <<= s;
- this.codeBits -= s;
- return table.Values[k];
- }
-
- return this.DecodeHuffmanSlow(ref table);
- }
-
- [MethodImpl(InliningOptions.ColdPath)]
- private unsafe int DecodeHuffmanSlow(ref HuffmanTable table)
- {
- // Naive test is to shift the code_buffer down so k bits are
- // valid, then test against MaxCode. To speed this up, we've
- // preshifted maxcode left so that it has (16-k) 0s at the
- // end; in other words, regardless of the number of bits, it
- // wants to be compared against something shifted to have 16;
- // that way we don't need to shift inside the loop.
- uint temp = this.codeBuffer >> 16;
- int k;
- for (k = FastBits + 1; ; ++k)
- {
- if (temp < table.MaxCode[k])
- {
- break;
- }
- }
-
- if (k == 17)
- {
- // Error! code not found
- this.codeBits -= 16;
- return -1;
- }
-
- if (k > this.codeBits)
- {
- return -1;
- }
-
- // Convert the huffman code to the symbol id
- int c = (int)(((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]);
-
- // Convert the id to a symbol
- this.codeBits -= k;
- this.codeBuffer <<= k;
- return table.Values[c];
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private int ExtendReceive(int n)
- {
- if (this.codeBits < n)
- {
- this.FillBuffer();
- }
-
- int sgn = (int)this.codeBuffer >> 31;
- uint k = LRot(this.codeBuffer, n);
- this.codeBuffer = k & ~Bmask[n];
- k &= Bmask[n];
- this.codeBits -= n;
- return (int)(k + (Bias[n] & ~sgn));
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private void CheckBits()
- {
- if (this.codeBits < 16)
- {
- this.FillBuffer();
- }
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1));
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private bool ContinueOnMcuComplete()
- {
- if (--this.todo > 0)
- {
- return true;
- }
-
- if (this.codeBits < 24)
- {
- this.FillBuffer();
- }
-
- // If it's NOT a restart, then just bail, so we get corrupt data rather than no data.
- // Reset the stream to before any bad markers to ensure we can read successive segments.
- if (this.badMarker)
- {
- this.stream.Position = this.markerPosition;
- }
-
- if (!this.HasRestart())
- {
- return false;
- }
-
- this.Reset();
-
- return true;
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private bool HasRestart()
- {
- byte m = this.marker;
- return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7;
- }
-
- private void Reset()
- {
- this.codeBits = 0;
- this.codeBuffer = 0;
-
- for (int i = 0; i < this.components.Length; i++)
- {
- JpegComponent c = this.components[i];
- c.DcPredictor = 0;
- }
-
- this.nomore = false;
- this.marker = JpegConstants.Markers.XFF;
- this.markerPosition = 0;
- this.badMarker = false;
- this.eobrun = 0;
-
- // No more than 1<<31 MCUs if no restartInterval? that's plenty safe since we don't even allow 1<<30 pixels
- this.todo = this.restartInterval > 0 ? this.restartInterval : int.MaxValue;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
index 49e3b41704..a39480e126 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
@@ -26,7 +26,7 @@ internal static class JpegConstants
public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" };
///
- /// Contains marker specific constants
+ /// Contains marker specific constants.
///
// ReSharper disable InconsistentNaming
internal static class Markers
@@ -219,7 +219,7 @@ internal static class Markers
}
///
- /// Contains Adobe specific constants
+ /// Contains Adobe specific constants.
///
internal static class Adobe
{
@@ -238,5 +238,32 @@ internal static class Adobe
///
public const byte ColorTransformYcck = 2;
}
+
+ ///
+ /// Contains Huffman specific constants.
+ ///
+ internal static class Huffman
+ {
+ ///
+ /// The size of the huffman decoder register.
+ ///
+ public const int RegisterSize = 64;
+
+ ///
+ /// If the next Huffman code is no more than this number of bits, we can obtain its length
+ /// and the corresponding symbol directly from this tables.
+ ///
+ public const int LookupBits = 8;
+
+ ///
+ /// If a Huffman code is this number of bits we cannot use the lookup table to determine its value.
+ ///
+ public const int SlowBits = LookupBits + 1;
+
+ ///
+ /// The size of the lookup table.
+ ///
+ public const int LookupSize = 1 << LookupBits;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 6d6983f191..8bd6514e04 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -59,11 +59,6 @@ internal sealed class JpegDecoderCore : IRawJpegData
///
private HuffmanTable[] acHuffmanTables;
- ///
- /// The fast AC tables used for entropy decoding
- ///
- private FastACTable[] fastACTables;
-
///
/// The reset interval determined by RST markers
///
@@ -269,7 +264,6 @@ public void ParseStream(Stream stream, bool metadataOnly = false)
const int maxTables = 4;
this.dcHuffmanTables = new HuffmanTable[maxTables];
this.acHuffmanTables = new HuffmanTable[maxTables];
- this.fastACTables = new FastACTable[maxTables];
}
// Break only when we discover a valid EOI marker.
@@ -385,7 +379,6 @@ public void Dispose()
this.Frame = null;
this.dcHuffmanTables = null;
this.acHuffmanTables = null;
- this.fastACTables = null;
}
///
@@ -936,12 +929,11 @@ private void ProcessStartOfScanMarker()
int spectralEnd = this.temp[1];
int successiveApproximation = this.temp[2];
- var sd = new ScanDecoder(
+ var sd = new HuffmanScanDecoder(
this.InputStream,
this.Frame,
this.dcHuffmanTables,
this.acHuffmanTables,
- this.fastACTables,
selectorsCount,
this.resetInterval,
spectralStart,
diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
index b30da28c45..7e8384dcff 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
@@ -20,9 +20,6 @@ internal static class JpegThrowHelper
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTable() => throw new ImageFormatException("Bad Quantization Table index.");
- [MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowBadHuffmanCode() => throw new ImageFormatException("Bad Huffman code.");
-
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadSampling() => throw new ImageFormatException("Bad sampling factor.");
diff --git a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs
index 6d5673d5a4..07f8928068 100644
--- a/src/ImageSharp/IO/DoubleBufferedStreamReader.cs
+++ b/src/ImageSharp/IO/DoubleBufferedStreamReader.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
@@ -13,24 +14,28 @@ namespace SixLabors.ImageSharp.IO
/// A stream reader that add a secondary level buffer in addition to native stream buffered reading
/// to reduce the overhead of small incremental reads.
///
- internal sealed class DoubleBufferedStreamReader : IDisposable
+ internal sealed unsafe class DoubleBufferedStreamReader : IDisposable
{
///
/// The length, in bytes, of the buffering chunk.
///
- public const int ChunkLength = 4096;
+ public const int ChunkLength = 8192;
- private const int ChunkLengthMinusOne = ChunkLength - 1;
+ private const int MaxChunkIndex = ChunkLength - 1;
private readonly Stream stream;
private readonly IManagedByteBuffer managedBuffer;
+ private MemoryHandle handle;
+
+ private readonly byte* pinnedChunk;
+
private readonly byte[] bufferChunk;
private readonly int length;
- private int bytesRead;
+ private int chunkIndex;
private int position;
@@ -44,8 +49,11 @@ public DoubleBufferedStreamReader(MemoryAllocator memoryAllocator, Stream stream
this.stream = stream;
this.Position = (int)stream.Position;
this.length = (int)stream.Length;
- this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength, AllocationOptions.Clean);
+ this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength);
this.bufferChunk = this.managedBuffer.Array;
+ this.handle = this.managedBuffer.Memory.Pin();
+ this.pinnedChunk = (byte*)this.handle.Pointer;
+ this.chunkIndex = ChunkLength;
}
///
@@ -62,10 +70,20 @@ public long Position
set
{
- // Reset everything. It's easier than tracking.
- this.position = (int)value;
- this.stream.Seek(this.position, SeekOrigin.Begin);
- this.bytesRead = ChunkLength;
+ // Only reset chunkIndex if we are out of bounds of our working chunk
+ // otherwise we should simply move the value by the diff.
+ int v = (int)value;
+ if (this.IsInChunk(v, out int index))
+ {
+ this.chunkIndex = index;
+ this.position = v;
+ }
+ else
+ {
+ this.position = v;
+ this.stream.Seek(value, SeekOrigin.Begin);
+ this.chunkIndex = ChunkLength;
+ }
}
}
@@ -74,7 +92,7 @@ public long Position
/// byte, or returns -1 if at the end of the stream.
///
/// The unsigned byte cast to an , or -1 if at the end of the stream.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public int ReadByte()
{
if (this.position >= this.length)
@@ -82,24 +100,21 @@ public int ReadByte()
return -1;
}
- if (this.position == 0 || this.bytesRead > ChunkLengthMinusOne)
+ if (this.chunkIndex > MaxChunkIndex)
{
- return this.ReadByteSlow();
+ this.FillChunk();
}
this.position++;
- return this.bufferChunk[this.bytesRead++];
+ return this.pinnedChunk[this.chunkIndex++];
}
///
/// Skips the number of bytes in the stream
///
- /// The number of bytes to skip
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Skip(int count)
- {
- this.Position += count;
- }
+ /// The number of bytes to skip.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Skip(int count) => this.Position += count;
///
/// Reads a sequence of bytes from the current stream and advances the position within the stream
@@ -120,36 +135,46 @@ public void Skip(int count)
/// of bytes requested if that many bytes are not currently available, or zero (0)
/// if the end of the stream has been reached.
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public int Read(byte[] buffer, int offset, int count)
{
- if (buffer.Length > ChunkLength)
+ if (count > ChunkLength)
{
return this.ReadToBufferSlow(buffer, offset, count);
}
- if (this.position == 0 || count + this.bytesRead > ChunkLength)
+ if (count + this.chunkIndex > ChunkLength)
{
return this.ReadToChunkSlow(buffer, offset, count);
}
- int n = this.GetCount(count);
+ int n = this.GetCopyCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
- this.bytesRead += n;
-
+ this.chunkIndex += n;
return n;
}
///
public void Dispose()
{
+ this.handle.Dispose();
this.managedBuffer?.Dispose();
}
- [MethodImpl(MethodImplOptions.NoInlining)]
- private int ReadByteSlow()
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int GetPositionDifference(int p) => p - this.position;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private bool IsInChunk(int p, out int index)
+ {
+ index = this.GetPositionDifference(p) + this.chunkIndex;
+ return index > -1 && index < ChunkLength;
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private void FillChunk()
{
if (this.position != this.stream.Position)
{
@@ -157,34 +182,25 @@ private int ReadByteSlow()
}
this.stream.Read(this.bufferChunk, 0, ChunkLength);
- this.bytesRead = 0;
-
- this.position++;
- return this.bufferChunk[this.bytesRead++];
+ this.chunkIndex = 0;
}
- [MethodImpl(MethodImplOptions.NoInlining)]
+ [MethodImpl(InliningOptions.ColdPath)]
private int ReadToChunkSlow(byte[] buffer, int offset, int count)
{
// Refill our buffer then copy.
- if (this.position != this.stream.Position)
- {
- this.stream.Seek(this.position, SeekOrigin.Begin);
- }
+ this.FillChunk();
- this.stream.Read(this.bufferChunk, 0, ChunkLength);
- this.bytesRead = 0;
-
- int n = this.GetCount(count);
+ int n = this.GetCopyCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
- this.bytesRead += n;
+ this.chunkIndex += n;
return n;
}
- [MethodImpl(MethodImplOptions.NoInlining)]
+ [MethodImpl(InliningOptions.ColdPath)]
private int ReadToBufferSlow(byte[] buffer, int offset, int count)
{
// Read to target but don't copy to our chunk.
@@ -195,11 +211,12 @@ private int ReadToBufferSlow(byte[] buffer, int offset, int count)
int n = this.stream.Read(buffer, offset, count);
this.Position += n;
+
return n;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int GetCount(int count)
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int GetCopyCount(int count)
{
int n = this.length - this.position;
if (n > count)
@@ -215,23 +232,23 @@ private int GetCount(int count)
return n;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
private void CopyBytes(byte[] buffer, int offset, int count)
{
if (count < 9)
{
int byteCount = count;
- int read = this.bytesRead;
- byte[] chunk = this.bufferChunk;
+ int read = this.chunkIndex;
+ byte* pinned = this.pinnedChunk;
while (--byteCount > -1)
{
- buffer[offset + byteCount] = chunk[read + byteCount];
+ buffer[offset + byteCount] = pinned[read + byteCount];
}
}
else
{
- Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, count);
+ Buffer.BlockCopy(this.bufferChunk, this.chunkIndex, buffer, offset, count);
}
}
}
diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj
index f7f9e1d5f4..a124ddcacd 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -38,8 +38,8 @@
-
-
+
+
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
index 5958b9f79d..5a4a9ab17c 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
@@ -47,5 +47,22 @@ public void ParseStreamPdfJs()
decoder.Dispose();
}
}
+
+ // RESULTS (2019 April 23):
+ //
+ // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5)
+ // Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
+ // .NET Core SDK=2.2.202
+ // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0
+ // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ //
+ // | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
+ // |---------------------------- |----- |-------- |--------------------- |---------:|---------:|----------:|------:|--------:|---------:|------:|------:|----------:|
+ // | 'System.Drawing FULL' | Clr | Clr | Jpg/b(...)f.jpg [28] | 18.69 ms | 8.273 ms | 0.4535 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.89 KB |
+ // | JpegDecoderCore.ParseStream | Clr | Clr | Jpg/b(...)f.jpg [28] | 15.76 ms | 4.266 ms | 0.2339 ms | 0.84 | 0.03 | - | - | - | 11.83 KB |
+ // | | | | | | | | | | | | | |
+ // | 'System.Drawing FULL' | Core | Core | Jpg/b(...)f.jpg [28] | 17.68 ms | 2.711 ms | 0.1486 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB |
+ // | JpegDecoderCore.ParseStream | Core | Core | Jpg/b(...)f.jpg [28] | 14.27 ms | 3.671 ms | 0.2012 ms | 0.81 | 0.00 | - | - | - | 11.76 KB |
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
index 8247ba42b5..62742f729b 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
@@ -35,7 +35,7 @@ public class ShortClr : Benchmarks.Config
public ShortClr()
{
this.Add(
- //Job.Clr.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3),
+ // Job.Clr.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3),
Job.Core.WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)
);
}
@@ -45,7 +45,7 @@ public ShortClr()
private byte[] jpegBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
-
+
[Params(
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr,
@@ -87,7 +87,7 @@ public CoreSize JpegImageSharp()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
- using (var image = Image.Load(memoryStream, new JpegDecoder(){ IgnoreMetadata = true}))
+ using (var image = Image.Load(memoryStream, new JpegDecoder() { IgnoreMetadata = true }))
{
return new CoreSize(image.Width, image.Height);
}
@@ -115,5 +115,24 @@ public CoreSize JpegImageSharp()
// | | | | | | | | | | |
// 'Decode Jpeg - System.Drawing' | Jpg/issues/issue750-exif-tranform.jpg | 95.192 ms | 3.1762 ms | 0.1795 ms | 1.00 | 0.00 | 1750.0000 | - | - | 5492.63 KB |
// 'Decode Jpeg - ImageSharp' | Jpg/issues/issue750-exif-tranform.jpg | 230.158 ms | 48.8128 ms | 2.7580 ms | 2.42 | 0.02 | 312.5000 | 312.5000 | 312.5000 | 58834.66 KB |
+
+ // RESULTS (2019 April 23):
+ //
+ //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5)
+ //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
+ //.NET Core SDK=2.2.202
+ // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ //
+ //| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
+ //|------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:|
+ //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB |
+ //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB |
+ //| | | | | | | | | | | |
+ //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB |
+ //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB |
+ //| | | | | | | | | | | |
+ //| 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB |
+ //| 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB |
}
}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
index d4cbe81e16..b0834eb52d 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
@@ -19,8 +19,8 @@ public class DoubleBufferedStreams
private MemoryStream stream2;
private MemoryStream stream3;
private MemoryStream stream4;
- DoubleBufferedStreamReader reader1;
- DoubleBufferedStreamReader reader2;
+ private DoubleBufferedStreamReader reader1;
+ private DoubleBufferedStreamReader reader2;
[GlobalSetup]
public void CreateStreams()
@@ -102,6 +102,19 @@ public int DoubleBufferedStreamRead()
return r;
}
+ [Benchmark]
+ public int SimpleReadByte()
+ {
+ byte[] b = this.buffer;
+ int r = 0;
+ for (int i = 0; i < b.Length; i++)
+ {
+ r += b[i];
+ }
+
+ return r;
+ }
+
private static byte[] CreateTestBytes()
{
byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
@@ -111,4 +124,29 @@ private static byte[] CreateTestBytes()
return buffer;
}
}
+
+ // RESULTS (2019 April 24):
+ //
+ //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5)
+ //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
+ //.NET Core SDK=2.2.202
+ // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0
+ // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT
+ //
+ //IterationCount=3 LaunchCount=1 WarmupCount=3
+ //
+ //| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
+ //|----------------------------- |----- |-------- |---------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:|
+ //| StandardStreamReadByte | Clr | Clr | 96.71 us | 5.9950 us | 0.3286 us | 1.00 | 0.00 | - | - | - | - |
+ //| StandardStreamRead | Clr | Clr | 77.73 us | 5.2284 us | 0.2866 us | 0.80 | 0.00 | - | - | - | - |
+ //| DoubleBufferedStreamReadByte | Clr | Clr | 23.17 us | 26.2354 us | 1.4381 us | 0.24 | 0.01 | - | - | - | - |
+ //| DoubleBufferedStreamRead | Clr | Clr | 33.35 us | 3.4071 us | 0.1868 us | 0.34 | 0.00 | - | - | - | - |
+ //| SimpleReadByte | Clr | Clr | 10.85 us | 0.4927 us | 0.0270 us | 0.11 | 0.00 | - | - | - | - |
+ //| | | | | | | | | | | | |
+ //| StandardStreamReadByte | Core | Core | 75.35 us | 12.9789 us | 0.7114 us | 1.00 | 0.00 | - | - | - | - |
+ //| StandardStreamRead | Core | Core | 55.36 us | 1.4432 us | 0.0791 us | 0.73 | 0.01 | - | - | - | - |
+ //| DoubleBufferedStreamReadByte | Core | Core | 21.47 us | 29.7076 us | 1.6284 us | 0.28 | 0.02 | - | - | - | - |
+ //| DoubleBufferedStreamRead | Core | Core | 29.67 us | 2.5988 us | 0.1424 us | 0.39 | 0.00 | - | - | - | - |
+ //| SimpleReadByte | Core | Core | 10.84 us | 0.7567 us | 0.0415 us | 0.14 | 0.00 | - | - | - | - |
}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs
index 75bbf21a33..84e2d06167 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs
@@ -254,7 +254,7 @@ protected void ForEachSystemDrawingImage(Func ope
protected void ForEachSystemDrawingImage(Func operation)
{
- using (MemoryStream workStream = new MemoryStream())
+ using (var workStream = new MemoryStream())
{
this.ForEachSystemDrawingImage(
diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
index e600af7851..9af4d57cff 100644
--- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
+++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
@@ -1,6 +1,6 @@
- netcoreapp2.1;net461
+ netcoreapp2.1;net472
Exe
True
SixLabors.ImageSharp.Benchmarks
diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
index 73f97bab38..0b727f30ce 100644
--- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
+++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
@@ -27,8 +27,8 @@
-
-
+
+