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 @@ - - + +