mirror of https://github.com/SixLabors/ImageSharp
Browse Source
* Read from underlying stream less often * Update benchmark dependencies * Experimental mango port Currently broken * Populate table, 64byte buffer Still broken. * Baseline, non RST works * 15/19 baseline tests pass now. * Optimize position change. * 18/19 pass * 19/19 baseline decoded * Can now decode all images. * Now faster and much cleaner. * Cleanup * Fix reader, update benchmarks * Update dependencies * Remove unused method * No need to clean initial buffer * Remove bounds check on ReadByte() * Refactor from feedbackaf/merge-core
committed by
GitHub
17 changed files with 1160 additions and 1270 deletions
@ -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 |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Gets the lookahead array.
|
|
||||
/// </summary>
|
|
||||
public fixed short Lookahead[512]; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Derives a lookup table for fast AC entropy scan decoding.
|
|
||||
/// This can happen multiple times during progressive decoding but always outside mcu loops.
|
|
||||
/// </summary>
|
|
||||
/// <param name="huffmanTable">The AC Huffman table.</param>
|
|
||||
public void Derive(ref HuffmanTable huffmanTable) |
|
||||
{ |
|
||||
const int FastBits = ScanDecoder.FastBits; |
|
||||
ref short fastACRef = ref this.Lookahead[0]; |
|
||||
ref byte huffmanLookaheadRef = ref huffmanTable.Lookahead[0]; |
|
||||
ref byte huffmanValuesRef = ref huffmanTable.Values[0]; |
|
||||
ref short huffmanSizesRef = ref huffmanTable.Sizes[0]; |
|
||||
|
|
||||
int i; |
|
||||
for (i = 0; i < (1 << FastBits); i++) |
|
||||
{ |
|
||||
byte fast = Unsafe.Add(ref huffmanLookaheadRef, i); |
|
||||
Unsafe.Add(ref fastACRef, i) = 0; |
|
||||
|
|
||||
if (fast < byte.MaxValue) |
|
||||
{ |
|
||||
int rs = Unsafe.Add(ref huffmanValuesRef, fast); |
|
||||
int run = (rs >> 4) & 15; |
|
||||
int magbits = rs & 15; |
|
||||
int len = Unsafe.Add(ref huffmanSizesRef, fast); |
|
||||
|
|
||||
if (magbits != 0 && len + magbits <= FastBits) |
|
||||
{ |
|
||||
// Magnitude code followed by receive_extend code
|
|
||||
int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits); |
|
||||
int m = 1 << (magbits - 1); |
|
||||
if (k < m) |
|
||||
{ |
|
||||
k += (int)((~0U << magbits) + 1); |
|
||||
} |
|
||||
|
|
||||
// If the result is small enough, we can fit it in fastAC table
|
|
||||
if (k >= -128 && k <= 127) |
|
||||
{ |
|
||||
Unsafe.Add(ref fastACRef, i) = (short)((k << 8) + (run << 4) + (len + magbits)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Used to buffer and track the bits read from the Huffman entropy encoded data.
|
||||
|
/// </summary>
|
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the current, if any, marker in the input stream.
|
||||
|
/// </summary>
|
||||
|
public byte Marker { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the opening position of an identified marker.
|
||||
|
/// </summary>
|
||||
|
public long MarkerPosition { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether we have a bad marker, I.E. One that is not between RST0 and RST7
|
||||
|
/// </summary>
|
||||
|
public bool BadMarker { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether we have prematurely reached the end of the file.
|
||||
|
/// </summary>
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Decodes the Huffman encoded spectral scan.
|
||||
|
/// Originally ported from <see href="https://github.com/t0rakka/mango"/>
|
||||
|
/// with additional fixes for both performance and common encoding errors.
|
||||
|
/// </summary>
|
||||
|
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; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The input stream.</param>
|
||||
|
/// <param name="frame">The image frame.</param>
|
||||
|
/// <param name="dcHuffmanTables">The DC Huffman tables.</param>
|
||||
|
/// <param name="acHuffmanTables">The AC Huffman tables.</param>
|
||||
|
/// <param name="componentsLength">The length of the components. Different to the array length.</param>
|
||||
|
/// <param name="restartInterval">The reset interval.</param>
|
||||
|
/// <param name="spectralStart">The spectral selection start.</param>
|
||||
|
/// <param name="spectralEnd">The spectral selection end.</param>
|
||||
|
/// <param name="successiveHigh">The successive approximation bit high end.</param>
|
||||
|
/// <param name="successiveLow">The successive approximation bit low end.</param>
|
||||
|
public 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; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes the entropy coded data.
|
||||
|
/// </summary>
|
||||
|
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<Block8x8> 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<Block8x8> 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<Block8x8> 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<Block8x8> 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<Block8x8> 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<Block8x8, short>(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<Block8x8, short>(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<Block8x8, short>(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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue