mirror of https://github.com/SixLabors/ImageSharp
Browse Source
# Conflicts: # tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cspull/616/head
375 changed files with 1916 additions and 1743 deletions
@ -1,8 +1,11 @@ |
|||||
using System.Diagnostics; |
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// A struct that defines a single color stop.
|
/// A struct that defines a single color stop.
|
||||
@ -1,9 +1,12 @@ |
|||||
using System; |
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.Primitives; |
using SixLabors.Primitives; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Gradient Brush with elliptic shape.
|
/// Gradient Brush with elliptic shape.
|
||||
@ -1,11 +1,14 @@ |
|||||
using System; |
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
using System.Numerics; |
using System.Numerics; |
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
||||
using SixLabors.Primitives; |
using SixLabors.Primitives; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Base class for Gradient brushes
|
/// Base class for Gradient brushes
|
||||
@ -1,4 +1,7 @@ |
|||||
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Modes to repeat a gradient.
|
/// Modes to repeat a gradient.
|
||||
@ -1,9 +1,12 @@ |
|||||
using System; |
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.Primitives; |
using SixLabors.Primitives; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Provides an implementation of a brush for painting linear gradients within areas.
|
/// Provides an implementation of a brush for painting linear gradients within areas.
|
||||
@ -1,9 +1,12 @@ |
|||||
using System; |
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.Primitives; |
using SixLabors.Primitives; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// A Circular Gradient Brush, defined by center point and radius.
|
/// A Circular Gradient Brush, defined by center point and radius.
|
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
// Uncomment this for verbose profiler results:
|
||||
|
// #define PROFILING
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Global inlining options. Helps temporarily disable inling for better profiler output.
|
||||
|
/// </summary>
|
||||
|
internal static class InliningOptions |
||||
|
{ |
||||
|
#if PROFILING
|
||||
|
public const MethodImplOptions ShortMethod = 0; |
||||
|
#else
|
||||
|
public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; |
||||
|
#endif
|
||||
|
public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg |
||||
|
{ |
||||
|
internal static class JpegThrowHelper |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
|
||||
|
/// </summary>
|
||||
|
/// <param name="errorMessage">The error message for the exception</param>
|
||||
|
[MethodImpl(MethodImplOptions.NoInlining)] |
||||
|
public static void ThrowImageFormatException(string errorMessage) |
||||
|
{ |
||||
|
throw new ImageFormatException(errorMessage); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.NoInlining)] |
||||
|
public static void ThrowBadHuffmanCode() |
||||
|
{ |
||||
|
throw new ImageFormatException("Bad Huffman code."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The collection of lookup tables used for fast AC entropy scan decoding.
|
||||
|
/// </summary>
|
||||
|
internal sealed class FastACTables : IDisposable |
||||
|
{ |
||||
|
private Buffer2D<short> tables; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="FastACTables"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="memoryAllocator">The memory allocator used to allocate memory for image processing operations.</param>
|
||||
|
public FastACTables(MemoryAllocator memoryAllocator) |
||||
|
{ |
||||
|
this.tables = memoryAllocator.AllocateClean2D<short>(512, 4); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="Span{Int16}"/> representing the table at the index in the collection.
|
||||
|
/// </summary>
|
||||
|
/// <param name="index">The table index.</param>
|
||||
|
/// <returns><see cref="Span{Int16}"/></returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public ReadOnlySpan<short> GetTableSpan(int index) |
||||
|
{ |
||||
|
return this.tables.GetRowSpan(index); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a reference to the first element of the AC table indexed by <see cref="PdfJsFrameComponent.ACHuffmanTableId"/>
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public ref short GetAcTableReference(PdfJsFrameComponent component) |
||||
|
{ |
||||
|
return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Builds a lookup table for fast AC entropy scan decoding.
|
||||
|
/// </summary>
|
||||
|
/// <param name="index">The table index.</param>
|
||||
|
/// <param name="acHuffmanTables">The collection of AC Huffman tables.</param>
|
||||
|
public void BuildACTableLut(int index, PdfJsHuffmanTables acHuffmanTables) |
||||
|
{ |
||||
|
const int FastBits = ScanDecoder.FastBits; |
||||
|
Span<short> fastAC = this.tables.GetRowSpan(index); |
||||
|
ref PdfJsHuffmanTable huffman = ref acHuffmanTables[index]; |
||||
|
|
||||
|
int i; |
||||
|
for (i = 0; i < (1 << FastBits); i++) |
||||
|
{ |
||||
|
byte fast = huffman.Lookahead[i]; |
||||
|
fastAC[i] = 0; |
||||
|
if (fast < byte.MaxValue) |
||||
|
{ |
||||
|
int rs = huffman.Values[fast]; |
||||
|
int run = (rs >> 4) & 15; |
||||
|
int magbits = rs & 15; |
||||
|
int len = huffman.Sizes[fast]; |
||||
|
|
||||
|
if (magbits > 0 && len + magbits <= FastBits) |
||||
|
{ |
||||
|
// Magnitude code followed by receive_extend code
|
||||
|
int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits); |
||||
|
int m = 1 << (magbits - 1); |
||||
|
if (k < m) |
||||
|
{ |
||||
|
k += (int)((~0U << magbits) + 1); |
||||
|
} |
||||
|
|
||||
|
// if the result is small enough, we can fit it in fastAC table
|
||||
|
if (k >= -128 && k <= 127) |
||||
|
{ |
||||
|
fastAC[i] = (short)((k * 256) + (run * 16) + (len + magbits)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
this.tables?.Dispose(); |
||||
|
this.tables = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components |
||||
|
{ |
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
internal unsafe struct FixedByteBuffer512 |
||||
|
{ |
||||
|
public fixed byte Data[1 << ScanDecoder.FastBits]; |
||||
|
|
||||
|
public byte this[int idx] |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
get |
||||
|
{ |
||||
|
ref byte self = ref Unsafe.As<FixedByteBuffer512, byte>(ref this); |
||||
|
return Unsafe.Add(ref self, idx); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,866 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
|
|
||||
#if DEBUG
|
|
||||
using System.Diagnostics; |
|
||||
#endif
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|
||||
using SixLabors.Memory; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides the means to decode a spectral scan
|
|
||||
/// </summary>
|
|
||||
internal struct PdfJsScanDecoder |
|
||||
{ |
|
||||
private ZigZag dctZigZag; |
|
||||
|
|
||||
private byte[] markerBuffer; |
|
||||
|
|
||||
private int mcuToRead; |
|
||||
|
|
||||
private int mcusPerLine; |
|
||||
|
|
||||
private int mcu; |
|
||||
|
|
||||
private int bitsData; |
|
||||
|
|
||||
private int bitsCount; |
|
||||
|
|
||||
private int specStart; |
|
||||
|
|
||||
private int specEnd; |
|
||||
|
|
||||
private int eobrun; |
|
||||
|
|
||||
private int compIndex; |
|
||||
|
|
||||
private int successiveState; |
|
||||
|
|
||||
private int successiveACState; |
|
||||
|
|
||||
private int successiveACNextValue; |
|
||||
|
|
||||
private bool endOfStreamReached; |
|
||||
|
|
||||
private bool unexpectedMarkerReached; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Decodes the spectral scan
|
|
||||
/// </summary>
|
|
||||
/// <param name="frame">The image frame</param>
|
|
||||
/// <param name="stream">The input stream</param>
|
|
||||
/// <param name="dcHuffmanTables">The DC Huffman tables</param>
|
|
||||
/// <param name="acHuffmanTables">The AC Huffman tables</param>
|
|
||||
/// <param name="components">The scan components</param>
|
|
||||
/// <param name="componentIndex">The component index within the array</param>
|
|
||||
/// <param name="componentsLength">The length of the components. Different to the array length</param>
|
|
||||
/// <param name="resetInterval">The reset interval</param>
|
|
||||
/// <param name="spectralStart">The spectral selection start</param>
|
|
||||
/// <param name="spectralEnd">The spectral selection end</param>
|
|
||||
/// <param name="successivePrev">The successive approximation bit high end</param>
|
|
||||
/// <param name="successive">The successive approximation bit low end</param>
|
|
||||
public void DecodeScan( |
|
||||
PdfJsFrame frame, |
|
||||
DoubleBufferedStreamReader stream, |
|
||||
PdfJsHuffmanTables dcHuffmanTables, |
|
||||
PdfJsHuffmanTables acHuffmanTables, |
|
||||
PdfJsFrameComponent[] components, |
|
||||
int componentIndex, |
|
||||
int componentsLength, |
|
||||
ushort resetInterval, |
|
||||
int spectralStart, |
|
||||
int spectralEnd, |
|
||||
int successivePrev, |
|
||||
int successive) |
|
||||
{ |
|
||||
this.dctZigZag = ZigZag.CreateUnzigTable(); |
|
||||
this.markerBuffer = new byte[2]; |
|
||||
this.compIndex = componentIndex; |
|
||||
this.specStart = spectralStart; |
|
||||
this.specEnd = spectralEnd; |
|
||||
this.successiveState = successive; |
|
||||
this.endOfStreamReached = false; |
|
||||
this.unexpectedMarkerReached = false; |
|
||||
|
|
||||
bool progressive = frame.Progressive; |
|
||||
this.mcusPerLine = frame.McusPerLine; |
|
||||
|
|
||||
this.mcu = 0; |
|
||||
int mcuExpected; |
|
||||
if (componentsLength == 1) |
|
||||
{ |
|
||||
mcuExpected = components[this.compIndex].WidthInBlocks * components[this.compIndex].HeightInBlocks; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
mcuExpected = this.mcusPerLine * frame.McusPerColumn; |
|
||||
} |
|
||||
|
|
||||
while (this.mcu < mcuExpected) |
|
||||
{ |
|
||||
// Reset interval stuff
|
|
||||
this.mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - this.mcu, resetInterval) : mcuExpected; |
|
||||
for (int i = 0; i < components.Length; i++) |
|
||||
{ |
|
||||
PdfJsFrameComponent c = components[i]; |
|
||||
c.DcPredictor = 0; |
|
||||
} |
|
||||
|
|
||||
this.eobrun = 0; |
|
||||
|
|
||||
if (!progressive) |
|
||||
{ |
|
||||
this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, stream); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
bool isAc = this.specStart != 0; |
|
||||
bool isFirst = successivePrev == 0; |
|
||||
PdfJsHuffmanTables huffmanTables = isAc ? acHuffmanTables : dcHuffmanTables; |
|
||||
this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, stream); |
|
||||
} |
|
||||
|
|
||||
// Reset
|
|
||||
// TODO: I do not understand why these values are reset? We should surely be tracking the bits across mcu's?
|
|
||||
this.bitsCount = 0; |
|
||||
this.bitsData = 0; |
|
||||
this.unexpectedMarkerReached = false; |
|
||||
|
|
||||
// Some images include more scan blocks than expected, skip past those and
|
|
||||
// attempt to find the next valid marker
|
|
||||
PdfJsFileMarker fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); |
|
||||
byte marker = fileMarker.Marker; |
|
||||
|
|
||||
// RSTn - We've already read the bytes and altered the position so no need to skip
|
|
||||
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
if (!fileMarker.Invalid) |
|
||||
{ |
|
||||
// We've found a valid marker.
|
|
||||
// Rewind the stream to the position of the marker and break
|
|
||||
stream.Position = fileMarker.Position; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
#if DEBUG
|
|
||||
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); |
|
||||
#endif
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void DecodeScanBaseline( |
|
||||
PdfJsHuffmanTables dcHuffmanTables, |
|
||||
PdfJsHuffmanTables acHuffmanTables, |
|
||||
PdfJsFrameComponent[] components, |
|
||||
int componentsLength, |
|
||||
DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
if (componentsLength == 1) |
|
||||
{ |
|
||||
PdfJsFrameComponent component = components[this.compIndex]; |
|
||||
ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Block8x8, short>(component.SpectralBlocks.GetSpan())); |
|
||||
ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; |
|
||||
ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|
||||
|
|
||||
for (int n = 0; n < this.mcuToRead; n++) |
|
||||
{ |
|
||||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, stream); |
|
||||
this.mcu++; |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
for (int n = 0; n < this.mcuToRead; n++) |
|
||||
{ |
|
||||
for (int i = 0; i < componentsLength; i++) |
|
||||
{ |
|
||||
PdfJsFrameComponent component = components[i]; |
|
||||
ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Block8x8, short>(component.SpectralBlocks.GetSpan())); |
|
||||
ref PdfJsHuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; |
|
||||
ref PdfJsHuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|
||||
int h = component.HorizontalSamplingFactor; |
|
||||
int v = component.VerticalSamplingFactor; |
|
||||
|
|
||||
for (int j = 0; j < v; j++) |
|
||||
{ |
|
||||
for (int k = 0; k < h; k++) |
|
||||
{ |
|
||||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, component, ref blockDataRef, j, k, stream); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.mcu++; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void DecodeScanProgressive( |
|
||||
PdfJsHuffmanTables huffmanTables, |
|
||||
bool isAC, |
|
||||
bool isFirst, |
|
||||
PdfJsFrameComponent[] components, |
|
||||
int componentsLength, |
|
||||
DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
if (componentsLength == 1) |
|
||||
{ |
|
||||
PdfJsFrameComponent component = components[this.compIndex]; |
|
||||
ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Block8x8, short>(component.SpectralBlocks.GetSpan())); |
|
||||
ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId]; |
|
||||
|
|
||||
for (int n = 0; n < this.mcuToRead; n++) |
|
||||
{ |
|
||||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
if (isAC) |
|
||||
{ |
|
||||
if (isFirst) |
|
||||
{ |
|
||||
this.DecodeBlockACFirst(ref huffmanTable, component, ref blockDataRef, stream); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
this.DecodeBlockACSuccessive(ref huffmanTable, component, ref blockDataRef, stream); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (isFirst) |
|
||||
{ |
|
||||
this.DecodeBlockDCFirst(ref huffmanTable, component, ref blockDataRef, stream); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
this.DecodeBlockDCSuccessive(component, ref blockDataRef, stream); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.mcu++; |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
for (int n = 0; n < this.mcuToRead; n++) |
|
||||
{ |
|
||||
for (int i = 0; i < componentsLength; i++) |
|
||||
{ |
|
||||
PdfJsFrameComponent component = components[i]; |
|
||||
ref short blockDataRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Block8x8, short>(component.SpectralBlocks.GetSpan())); |
|
||||
ref PdfJsHuffmanTable huffmanTable = ref huffmanTables[isAC ? component.ACHuffmanTableId : component.DCHuffmanTableId]; |
|
||||
int h = component.HorizontalSamplingFactor; |
|
||||
int v = component.VerticalSamplingFactor; |
|
||||
|
|
||||
for (int j = 0; j < v; j++) |
|
||||
{ |
|
||||
for (int k = 0; k < h; k++) |
|
||||
{ |
|
||||
// No need to continue here.
|
|
||||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|
||||
{ |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
if (isAC) |
|
||||
{ |
|
||||
if (isFirst) |
|
||||
{ |
|
||||
this.DecodeMcuACFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
this.DecodeMcuACSuccessive(ref huffmanTable, component, ref blockDataRef, j, k, stream); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (isFirst) |
|
||||
{ |
|
||||
this.DecodeMcuDCFirst(ref huffmanTable, component, ref blockDataRef, j, k, stream); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
this.DecodeMcuDCSuccessive(component, ref blockDataRef, j, k, stream); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.mcu++; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeBlockBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int blockRow = this.mcu / component.WidthInBlocks; |
|
||||
int blockCol = this.mcu % component.WidthInBlocks; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeMcuBaseline(ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int mcuRow = this.mcu / this.mcusPerLine; |
|
||||
int mcuCol = this.mcu % this.mcusPerLine; |
|
||||
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; |
|
||||
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeBaseline(component, ref blockDataRef, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeBlockDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int blockRow = this.mcu / component.WidthInBlocks; |
|
||||
int blockCol = this.mcu % component.WidthInBlocks; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeMcuDCFirst(ref PdfJsHuffmanTable dcHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int mcuRow = this.mcu / this.mcusPerLine; |
|
||||
int mcuCol = this.mcu % this.mcusPerLine; |
|
||||
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; |
|
||||
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeDCFirst(component, ref blockDataRef, offset, ref dcHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeBlockDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int blockRow = this.mcu / component.WidthInBlocks; |
|
||||
int blockCol = this.mcu % component.WidthInBlocks; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeMcuDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int mcuRow = this.mcu / this.mcusPerLine; |
|
||||
int mcuCol = this.mcu % this.mcusPerLine; |
|
||||
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; |
|
||||
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeDCSuccessive(component, ref blockDataRef, offset, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeBlockACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int blockRow = this.mcu / component.WidthInBlocks; |
|
||||
int blockCol = this.mcu % component.WidthInBlocks; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeMcuACFirst(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int mcuRow = this.mcu / this.mcusPerLine; |
|
||||
int mcuCol = this.mcu % this.mcusPerLine; |
|
||||
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; |
|
||||
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeACFirst(ref blockDataRef, offset, ref acHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeBlockACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int blockRow = this.mcu / component.WidthInBlocks; |
|
||||
int blockCol = this.mcu % component.WidthInBlocks; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeMcuACSuccessive(ref PdfJsHuffmanTable acHuffmanTable, PdfJsFrameComponent component, ref short blockDataRef, int row, int col, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int mcuRow = this.mcu / this.mcusPerLine; |
|
||||
int mcuCol = this.mcu % this.mcusPerLine; |
|
||||
int blockRow = (mcuRow * component.VerticalSamplingFactor) + row; |
|
||||
int blockCol = (mcuCol * component.HorizontalSamplingFactor) + col; |
|
||||
int offset = component.GetBlockBufferOffset(blockRow, blockCol); |
|
||||
this.DecodeACSuccessive(ref blockDataRef, offset, ref acHuffmanTable, stream); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private bool TryReadBit(DoubleBufferedStreamReader stream, out int bit) |
|
||||
{ |
|
||||
if (this.bitsCount == 0) |
|
||||
{ |
|
||||
if (!this.TryFillBits(stream)) |
|
||||
{ |
|
||||
bit = 0; |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.bitsCount--; |
|
||||
bit = (this.bitsData >> this.bitsCount) & 1; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.NoInlining)] |
|
||||
private bool TryFillBits(DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
// TODO: Read more then 1 byte at a time.
|
|
||||
// In LibJpegTurbo this is be 25 bits (32-7) but I cannot get this to work
|
|
||||
// for some images, I'm assuming because I am crossing MCU boundaries and not maintining the correct buffer state.
|
|
||||
const int MinGetBits = 7; |
|
||||
|
|
||||
if (!this.unexpectedMarkerReached) |
|
||||
{ |
|
||||
// Attempt to load to the minimum bit count.
|
|
||||
while (this.bitsCount < MinGetBits) |
|
||||
{ |
|
||||
int c = stream.ReadByte(); |
|
||||
|
|
||||
switch (c) |
|
||||
{ |
|
||||
case -0x1: |
|
||||
|
|
||||
// We've encountered the end of the file stream which means there's no EOI marker in the image.
|
|
||||
this.endOfStreamReached = true; |
|
||||
return false; |
|
||||
|
|
||||
case JpegConstants.Markers.XFF: |
|
||||
int nextByte = stream.ReadByte(); |
|
||||
|
|
||||
if (nextByte == -0x1) |
|
||||
{ |
|
||||
this.endOfStreamReached = true; |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (nextByte != 0) |
|
||||
{ |
|
||||
#if DEBUG
|
|
||||
Debug.WriteLine($"DecodeScan - Unexpected marker {(c << 8) | nextByte:X} at {stream.Position}"); |
|
||||
#endif
|
|
||||
|
|
||||
// We've encountered an unexpected marker. Reverse the stream and exit.
|
|
||||
this.unexpectedMarkerReached = true; |
|
||||
stream.Position -= 2; |
|
||||
|
|
||||
// TODO: double check we need this.
|
|
||||
// Fill buffer with zero bits.
|
|
||||
if (this.bitsCount == 0) |
|
||||
{ |
|
||||
this.bitsData <<= MinGetBits; |
|
||||
this.bitsCount = MinGetBits; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
// OK, load the next byte into bitsData
|
|
||||
this.bitsData = (this.bitsData << 8) | c; |
|
||||
this.bitsCount += 8; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private int PeekBits(int count) |
|
||||
{ |
|
||||
return this.bitsData >> (this.bitsCount - count) & ((1 << count) - 1); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DropBits(int count) |
|
||||
{ |
|
||||
this.bitsCount -= count; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private bool TryDecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream, out short value) |
|
||||
{ |
|
||||
value = -1; |
|
||||
|
|
||||
// TODO: Implement fast Huffman decoding.
|
|
||||
// In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream
|
|
||||
// Then a LUT is used to avoid the loop when decoding the Huffman value.
|
|
||||
// using 3 methods: FillBits, PeekBits, and DropBits.
|
|
||||
// The LUT has been ported from LibJpegTurbo as has this code but it doesn't work.
|
|
||||
// this.TryFillBits(stream);
|
|
||||
//
|
|
||||
// const int LookAhead = 8;
|
|
||||
// int look = this.PeekBits(LookAhead);
|
|
||||
// look = tree.Lookahead[look];
|
|
||||
// int bits = look >> LookAhead;
|
|
||||
//
|
|
||||
// if (bits <= LookAhead)
|
|
||||
// {
|
|
||||
// this.DropBits(bits);
|
|
||||
// value = (short)(look & ((1 << LookAhead) - 1));
|
|
||||
// return true;
|
|
||||
// }
|
|
||||
if (!this.TryReadBit(stream, out int bit)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
short code = (short)bit; |
|
||||
|
|
||||
// "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
|
|
||||
int i = 1; |
|
||||
|
|
||||
while (code > tree.MaxCode[i]) |
|
||||
{ |
|
||||
if (!this.TryReadBit(stream, out bit)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
code <<= 1; |
|
||||
code |= (short)bit; |
|
||||
i++; |
|
||||
} |
|
||||
|
|
||||
int j = tree.ValOffset[i]; |
|
||||
value = tree.HuffVal[(j + code) & 0xFF]; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private bool TryReceive(int length, DoubleBufferedStreamReader stream, out int value) |
|
||||
{ |
|
||||
value = 0; |
|
||||
while (length > 0) |
|
||||
{ |
|
||||
if (!this.TryReadBit(stream, out int bit)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
value = (value << 1) | bit; |
|
||||
length--; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private bool TryReceiveAndExtend(int length, DoubleBufferedStreamReader stream, out int value) |
|
||||
{ |
|
||||
if (length == 1) |
|
||||
{ |
|
||||
if (!this.TryReadBit(stream, out value)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
value = value == 1 ? 1 : -1; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (!this.TryReceive(length, stream, out value)) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (value < 1 << (length - 1)) |
|
||||
{ |
|
||||
value += (-1 << length) + 1; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int diff = 0; |
|
||||
if (t != 0) |
|
||||
{ |
|
||||
if (!this.TryReceiveAndExtend(t, stream, out diff)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff); |
|
||||
|
|
||||
int k = 1; |
|
||||
while (k < 64) |
|
||||
{ |
|
||||
if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int s = rs & 15; |
|
||||
int r = rs >> 4; |
|
||||
|
|
||||
if (s == 0) |
|
||||
{ |
|
||||
if (r < 15) |
|
||||
{ |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
k += 16; |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
k += r; |
|
||||
|
|
||||
byte z = this.dctZigZag[k]; |
|
||||
|
|
||||
if (!this.TryReceiveAndExtend(s, stream, out int re)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
Unsafe.Add(ref blockDataRef, offset + z) = (short)re; |
|
||||
k++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int diff = 0; |
|
||||
if (t != 0) |
|
||||
{ |
|
||||
if (!this.TryReceiveAndExtend(t, stream, out diff)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Unsafe.Add(ref blockDataRef, offset) = (short)(component.DcPredictor += diff << this.successiveState); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
if (!this.TryReadBit(stream, out int bit)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
Unsafe.Add(ref blockDataRef, offset) |= (short)(bit << this.successiveState); |
|
||||
} |
|
||||
|
|
||||
private void DecodeACFirst(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
if (this.eobrun > 0) |
|
||||
{ |
|
||||
this.eobrun--; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int k = this.specStart; |
|
||||
int e = this.specEnd; |
|
||||
while (k <= e) |
|
||||
{ |
|
||||
if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int s = rs & 15; |
|
||||
int r = rs >> 4; |
|
||||
|
|
||||
if (s == 0) |
|
||||
{ |
|
||||
if (r < 15) |
|
||||
{ |
|
||||
if (!this.TryReceive(r, stream, out int eob)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.eobrun = eob + (1 << r) - 1; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
k += 16; |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
k += r; |
|
||||
|
|
||||
byte z = this.dctZigZag[k]; |
|
||||
|
|
||||
if (!this.TryReceiveAndExtend(s, stream, out int v)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
Unsafe.Add(ref blockDataRef, offset + z) = (short)(v * (1 << this.successiveState)); |
|
||||
k++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void DecodeACSuccessive(ref short blockDataRef, int offset, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream) |
|
||||
{ |
|
||||
int k = this.specStart; |
|
||||
int e = this.specEnd; |
|
||||
int r = 0; |
|
||||
|
|
||||
while (k <= e) |
|
||||
{ |
|
||||
int offsetZ = offset + this.dctZigZag[k]; |
|
||||
ref short blockOffsetZRef = ref Unsafe.Add(ref blockDataRef, offsetZ); |
|
||||
int sign = blockOffsetZRef < 0 ? -1 : 1; |
|
||||
|
|
||||
switch (this.successiveACState) |
|
||||
{ |
|
||||
case 0: // Initial state
|
|
||||
|
|
||||
if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int s = rs & 15; |
|
||||
r = rs >> 4; |
|
||||
if (s == 0) |
|
||||
{ |
|
||||
if (r < 15) |
|
||||
{ |
|
||||
if (!this.TryReceive(r, stream, out int eob)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.eobrun = eob + (1 << r); |
|
||||
this.successiveACState = 4; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
r = 16; |
|
||||
this.successiveACState = 1; |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (s != 1) |
|
||||
{ |
|
||||
throw new ImageFormatException("Invalid ACn encoding"); |
|
||||
} |
|
||||
|
|
||||
if (!this.TryReceiveAndExtend(s, stream, out int v)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
this.successiveACNextValue = v; |
|
||||
this.successiveACState = r > 0 ? 2 : 3; |
|
||||
} |
|
||||
|
|
||||
continue; |
|
||||
case 1: // Skipping r zero items
|
|
||||
case 2: |
|
||||
if (blockOffsetZRef != 0) |
|
||||
{ |
|
||||
if (!this.TryReadBit(stream, out int bit)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
blockOffsetZRef += (short)(sign * (bit << this.successiveState)); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
r--; |
|
||||
if (r == 0) |
|
||||
{ |
|
||||
this.successiveACState = this.successiveACState == 2 ? 3 : 0; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
break; |
|
||||
case 3: // Set value for a zero item
|
|
||||
if (blockOffsetZRef != 0) |
|
||||
{ |
|
||||
if (!this.TryReadBit(stream, out int bit)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
blockOffsetZRef += (short)(sign * (bit << this.successiveState)); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
blockOffsetZRef = (short)(this.successiveACNextValue << this.successiveState); |
|
||||
this.successiveACState = 0; |
|
||||
} |
|
||||
|
|
||||
break; |
|
||||
case 4: // Eob
|
|
||||
if (blockOffsetZRef != 0) |
|
||||
{ |
|
||||
if (!this.TryReadBit(stream, out int bit)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
blockOffsetZRef += (short)(sign * (bit << this.successiveState)); |
|
||||
} |
|
||||
|
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
k++; |
|
||||
} |
|
||||
|
|
||||
if (this.successiveACState == 4) |
|
||||
{ |
|
||||
this.eobrun--; |
|
||||
if (this.eobrun == 0) |
|
||||
{ |
|
||||
this.successiveACState = 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,958 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Decodes the Huffman encoded spectral scan.
|
||||
|
/// Originally ported from <see href="https://github.com/rds1983/StbSharp"/>
|
||||
|
/// with additional fixes for both performance and common encoding errors.
|
||||
|
/// </summary>
|
||||
|
internal class ScanDecoder |
||||
|
{ |
||||
|
// The number of bits that can be read via a LUT.
|
||||
|
public const int FastBits = 9; |
||||
|
|
||||
|
// LUT mask for n rightmost bits. Bmask[n] = (1 << n) - 1
|
||||
|
private static readonly uint[] Bmask = { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 }; |
||||
|
|
||||
|
// LUT Bias[n] = (-1 << n) + 1
|
||||
|
private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 }; |
||||
|
|
||||
|
private readonly PdfJsFrame frame; |
||||
|
private readonly PdfJsHuffmanTables dcHuffmanTables; |
||||
|
private readonly PdfJsHuffmanTables acHuffmanTables; |
||||
|
private readonly FastACTables fastACTables; |
||||
|
|
||||
|
private readonly DoubleBufferedStreamReader stream; |
||||
|
private readonly PdfJsFrameComponent[] components; |
||||
|
private readonly ZigZag dctZigZag; |
||||
|
|
||||
|
// The restart interval.
|
||||
|
private readonly int restartInterval; |
||||
|
|
||||
|
// The current component index.
|
||||
|
private readonly int componentIndex; |
||||
|
|
||||
|
// The number of interleaved components.
|
||||
|
private readonly int componentsLength; |
||||
|
|
||||
|
// The spectral selection start.
|
||||
|
private readonly int spectralStart; |
||||
|
|
||||
|
// The spectral selection end.
|
||||
|
private readonly int spectralEnd; |
||||
|
|
||||
|
// The successive approximation high bit end.
|
||||
|
private readonly int successiveHigh; |
||||
|
|
||||
|
// The successive approximation low bit end.
|
||||
|
private readonly int successiveLow; |
||||
|
|
||||
|
// The number of valid bits left to read in the buffer.
|
||||
|
private int codeBits; |
||||
|
|
||||
|
// The entropy encoded code buffer.
|
||||
|
private uint codeBuffer; |
||||
|
|
||||
|
// Whether there is more data to pull from the stream for the current mcu.
|
||||
|
private bool nomore; |
||||
|
|
||||
|
// Whether we have prematurely reached the end of the file.
|
||||
|
private bool eof; |
||||
|
|
||||
|
// The current, if any, marker in the input stream.
|
||||
|
private byte marker; |
||||
|
|
||||
|
// Whether we have a bad marker, I.E. One that is not between RST0 and RST7
|
||||
|
private bool badMarker; |
||||
|
|
||||
|
// The opening position of an identified marker.
|
||||
|
private long markerPosition; |
||||
|
|
||||
|
// How many mcu's are left to do.
|
||||
|
private int todo; |
||||
|
|
||||
|
// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
|
||||
|
private int eobrun; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ScanDecoder"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">The input stream.</param>
|
||||
|
/// <param name="frame">The image frame.</param>
|
||||
|
/// <param name="dcHuffmanTables">The DC Huffman tables.</param>
|
||||
|
/// <param name="acHuffmanTables">The AC Huffman tables.</param>
|
||||
|
/// <param name="fastACTables">The fast AC decoding tables.</param>
|
||||
|
/// <param name="componentIndex">The component index within the array.</param>
|
||||
|
/// <param name="componentsLength">The length of the components. Different to the array length.</param>
|
||||
|
/// <param name="restartInterval">The reset interval.</param>
|
||||
|
/// <param name="spectralStart">The spectral selection start.</param>
|
||||
|
/// <param name="spectralEnd">The spectral selection end.</param>
|
||||
|
/// <param name="successiveHigh">The successive approximation bit high end.</param>
|
||||
|
/// <param name="successiveLow">The successive approximation bit low end.</param>
|
||||
|
public ScanDecoder( |
||||
|
DoubleBufferedStreamReader stream, |
||||
|
PdfJsFrame frame, |
||||
|
PdfJsHuffmanTables dcHuffmanTables, |
||||
|
PdfJsHuffmanTables acHuffmanTables, |
||||
|
FastACTables fastACTables, |
||||
|
int componentIndex, |
||||
|
int componentsLength, |
||||
|
int restartInterval, |
||||
|
int spectralStart, |
||||
|
int spectralEnd, |
||||
|
int successiveHigh, |
||||
|
int successiveLow) |
||||
|
{ |
||||
|
this.dctZigZag = ZigZag.CreateUnzigTable(); |
||||
|
this.stream = stream; |
||||
|
this.frame = frame; |
||||
|
this.dcHuffmanTables = dcHuffmanTables; |
||||
|
this.acHuffmanTables = acHuffmanTables; |
||||
|
this.fastACTables = fastACTables; |
||||
|
this.components = frame.Components; |
||||
|
this.marker = JpegConstants.Markers.XFF; |
||||
|
this.markerPosition = 0; |
||||
|
this.componentIndex = componentIndex; |
||||
|
this.componentsLength = componentsLength; |
||||
|
this.restartInterval = restartInterval; |
||||
|
this.spectralStart = spectralStart; |
||||
|
this.spectralEnd = spectralEnd; |
||||
|
this.successiveHigh = successiveHigh; |
||||
|
this.successiveLow = successiveLow; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Decodes the entropy coded data.
|
||||
|
/// </summary>
|
||||
|
public void ParseEntropyCodedData() |
||||
|
{ |
||||
|
this.Reset(); |
||||
|
|
||||
|
if (!this.frame.Progressive) |
||||
|
{ |
||||
|
this.ParseBaselineData(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.ParseProgressiveData(); |
||||
|
} |
||||
|
|
||||
|
if (this.badMarker) |
||||
|
{ |
||||
|
this.stream.Position = this.markerPosition; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static uint LRot(uint x, int y) => (x << y) | (x >> (32 - y)); |
||||
|
|
||||
|
private void ParseBaselineData() |
||||
|
{ |
||||
|
if (this.componentsLength == 1) |
||||
|
{ |
||||
|
this.ParseBaselineDataNonInterleaved(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.ParseBaselineDataInterleaved(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ParseBaselineDataInterleaved() |
||||
|
{ |
||||
|
// Interleaved
|
||||
|
int mcu = 0; |
||||
|
int mcusPerColumn = this.frame.McusPerColumn; |
||||
|
int mcusPerLine = this.frame.McusPerLine; |
||||
|
for (int j = 0; j < mcusPerColumn; j++) |
||||
|
{ |
||||
|
for (int i = 0; i < mcusPerLine; i++) |
||||
|
{ |
||||
|
// Scan an interleaved mcu... process components in order
|
||||
|
for (int k = 0; k < this.componentsLength; k++) |
||||
|
{ |
||||
|
PdfJsFrameComponent component = this.components[k]; |
||||
|
|
||||
|
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; |
||||
|
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; |
||||
|
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); |
||||
|
int h = component.HorizontalSamplingFactor; |
||||
|
int v = component.VerticalSamplingFactor; |
||||
|
|
||||
|
// Scan out an mcu's worth of this component; that's just determined
|
||||
|
// by the basic H and V specified for the component
|
||||
|
for (int y = 0; y < v; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < h; x++) |
||||
|
{ |
||||
|
if (this.eof) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int mcuRow = mcu / mcusPerLine; |
||||
|
int mcuCol = mcu % mcusPerLine; |
||||
|
int blockRow = (mcuRow * v) + y; |
||||
|
int blockCol = (mcuCol * h) + x; |
||||
|
|
||||
|
this.DecodeBlockBaseline( |
||||
|
component, |
||||
|
blockRow, |
||||
|
blockCol, |
||||
|
ref dcHuffmanTable, |
||||
|
ref acHuffmanTable, |
||||
|
ref fastACRef); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// After all interleaved components, that's an interleaved MCU,
|
||||
|
// so now count down the restart interval
|
||||
|
mcu++; |
||||
|
if (!this.ContinueOnMcuComplete()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Non-interleaved data, we just need to process one block at a ti
|
||||
|
/// in trivial scanline order
|
||||
|
/// number of blocks to do just depends on how many actual "pixels"
|
||||
|
/// component has, independent of interleaved MCU blocking and such
|
||||
|
/// </summary>
|
||||
|
private void ParseBaselineDataNonInterleaved() |
||||
|
{ |
||||
|
PdfJsFrameComponent component = this.components[this.componentIndex]; |
||||
|
|
||||
|
int w = component.WidthInBlocks; |
||||
|
int h = component.HeightInBlocks; |
||||
|
|
||||
|
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; |
||||
|
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; |
||||
|
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); |
||||
|
|
||||
|
int mcu = 0; |
||||
|
for (int j = 0; j < h; j++) |
||||
|
{ |
||||
|
for (int i = 0; i < w; i++) |
||||
|
{ |
||||
|
if (this.eof) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int blockRow = mcu / w; |
||||
|
int blockCol = mcu % w; |
||||
|
|
||||
|
this.DecodeBlockBaseline( |
||||
|
component, |
||||
|
blockRow, |
||||
|
blockCol, |
||||
|
ref dcHuffmanTable, |
||||
|
ref acHuffmanTable, |
||||
|
ref fastACRef); |
||||
|
|
||||
|
// Every data block is an MCU, so countdown the restart interval
|
||||
|
mcu++; |
||||
|
if (!this.ContinueOnMcuComplete()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ParseProgressiveData() |
||||
|
{ |
||||
|
if (this.componentsLength == 1) |
||||
|
{ |
||||
|
this.ParseProgressiveDataNonInterleaved(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.ParseProgressiveDataInterleaved(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void ParseProgressiveDataInterleaved() |
||||
|
{ |
||||
|
// Interleaved
|
||||
|
int mcu = 0; |
||||
|
int mcusPerColumn = this.frame.McusPerColumn; |
||||
|
int mcusPerLine = this.frame.McusPerLine; |
||||
|
for (int j = 0; j < mcusPerColumn; j++) |
||||
|
{ |
||||
|
for (int i = 0; i < mcusPerLine; i++) |
||||
|
{ |
||||
|
// Scan an interleaved mcu... process components in order
|
||||
|
for (int k = 0; k < this.componentsLength; k++) |
||||
|
{ |
||||
|
PdfJsFrameComponent component = this.components[k]; |
||||
|
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; |
||||
|
int h = component.HorizontalSamplingFactor; |
||||
|
int v = component.VerticalSamplingFactor; |
||||
|
|
||||
|
// Scan out an mcu's worth of this component; that's just determined
|
||||
|
// by the basic H and V specified for the component
|
||||
|
for (int y = 0; y < v; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < h; x++) |
||||
|
{ |
||||
|
if (this.eof) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int mcuRow = mcu / mcusPerLine; |
||||
|
int mcuCol = mcu % mcusPerLine; |
||||
|
int blockRow = (mcuRow * v) + y; |
||||
|
int blockCol = (mcuCol * h) + x; |
||||
|
|
||||
|
this.DecodeBlockProgressiveDC( |
||||
|
component, |
||||
|
blockRow, |
||||
|
blockCol, |
||||
|
ref dcHuffmanTable); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// After all interleaved components, that's an interleaved MCU,
|
||||
|
// so now count down the restart interval
|
||||
|
mcu++; |
||||
|
if (!this.ContinueOnMcuComplete()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Non-interleaved data, we just need to process one block at a time,
|
||||
|
/// in trivial scanline order
|
||||
|
/// number of blocks to do just depends on how many actual "pixels" this
|
||||
|
/// component has, independent of interleaved MCU blocking and such
|
||||
|
/// </summary>
|
||||
|
private void ParseProgressiveDataNonInterleaved() |
||||
|
{ |
||||
|
PdfJsFrameComponent component = this.components[this.componentIndex]; |
||||
|
|
||||
|
int w = component.WidthInBlocks; |
||||
|
int h = component.HeightInBlocks; |
||||
|
|
||||
|
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; |
||||
|
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; |
||||
|
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); |
||||
|
|
||||
|
int mcu = 0; |
||||
|
for (int j = 0; j < h; j++) |
||||
|
{ |
||||
|
for (int i = 0; i < w; i++) |
||||
|
{ |
||||
|
if (this.eof) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int blockRow = mcu / w; |
||||
|
int blockCol = mcu % w; |
||||
|
|
||||
|
if (this.spectralStart == 0) |
||||
|
{ |
||||
|
this.DecodeBlockProgressiveDC( |
||||
|
component, |
||||
|
blockRow, |
||||
|
blockCol, |
||||
|
ref dcHuffmanTable); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.DecodeBlockProgressiveAC( |
||||
|
component, |
||||
|
blockRow, |
||||
|
blockCol, |
||||
|
ref acHuffmanTable, |
||||
|
ref fastACRef); |
||||
|
} |
||||
|
|
||||
|
// Every data block is an MCU, so countdown the restart interval
|
||||
|
mcu++; |
||||
|
if (!this.ContinueOnMcuComplete()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void DecodeBlockBaseline( |
||||
|
PdfJsFrameComponent component, |
||||
|
int row, |
||||
|
int col, |
||||
|
ref PdfJsHuffmanTable dcTable, |
||||
|
ref PdfJsHuffmanTable acTable, |
||||
|
ref short fastACRef) |
||||
|
{ |
||||
|
this.CheckBits(); |
||||
|
int t = this.DecodeHuffman(ref dcTable); |
||||
|
|
||||
|
if (t < 0) |
||||
|
{ |
||||
|
JpegThrowHelper.ThrowBadHuffmanCode(); |
||||
|
} |
||||
|
|
||||
|
ref short blockDataRef = ref component.GetBlockDataReference(row, col); |
||||
|
|
||||
|
int diff = t != 0 ? this.ExtendReceive(t) : 0; |
||||
|
int dc = component.DcPredictor + diff; |
||||
|
component.DcPredictor = dc; |
||||
|
blockDataRef = (short)dc; |
||||
|
|
||||
|
// Decode AC Components, See Jpeg Spec
|
||||
|
int k = 1; |
||||
|
do |
||||
|
{ |
||||
|
int zig; |
||||
|
int s; |
||||
|
|
||||
|
this.CheckBits(); |
||||
|
int c = this.PeekBits(); |
||||
|
int r = Unsafe.Add(ref fastACRef, c); |
||||
|
|
||||
|
if (r != 0) |
||||
|
{ |
||||
|
// Fast AC path
|
||||
|
k += (r >> 4) & 15; // Run
|
||||
|
s = r & 15; // Combined Length
|
||||
|
this.codeBuffer <<= s; |
||||
|
this.codeBits -= s; |
||||
|
|
||||
|
// Decode into unzigzag location
|
||||
|
zig = this.dctZigZag[k++]; |
||||
|
Unsafe.Add(ref blockDataRef, zig) = (short)(r >> 8); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int rs = this.DecodeHuffman(ref acTable); |
||||
|
|
||||
|
if (rs < 0) |
||||
|
{ |
||||
|
JpegThrowHelper.ThrowBadHuffmanCode(); |
||||
|
} |
||||
|
|
||||
|
s = rs & 15; |
||||
|
r = rs >> 4; |
||||
|
|
||||
|
if (s == 0) |
||||
|
{ |
||||
|
if (rs != 0xF0) |
||||
|
{ |
||||
|
break; // End block
|
||||
|
} |
||||
|
|
||||
|
k += 16; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
k += r; |
||||
|
|
||||
|
// Decode into unzigzag location
|
||||
|
zig = this.dctZigZag[k++]; |
||||
|
Unsafe.Add(ref blockDataRef, zig) = (short)this.ExtendReceive(s); |
||||
|
} |
||||
|
} |
||||
|
} while (k < 64); |
||||
|
} |
||||
|
|
||||
|
private void DecodeBlockProgressiveDC( |
||||
|
PdfJsFrameComponent component, |
||||
|
int row, |
||||
|
int col, |
||||
|
ref PdfJsHuffmanTable dcTable) |
||||
|
{ |
||||
|
if (this.spectralEnd != 0) |
||||
|
{ |
||||
|
JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); |
||||
|
} |
||||
|
|
||||
|
this.CheckBits(); |
||||
|
|
||||
|
ref short blockDataRef = ref component.GetBlockDataReference(row, col); |
||||
|
|
||||
|
if (this.successiveHigh == 0) |
||||
|
{ |
||||
|
// First scan for DC coefficient, must be first
|
||||
|
int t = this.DecodeHuffman(ref dcTable); |
||||
|
int diff = t != 0 ? this.ExtendReceive(t) : 0; |
||||
|
|
||||
|
int dc = component.DcPredictor + diff; |
||||
|
component.DcPredictor = dc; |
||||
|
|
||||
|
blockDataRef = (short)(dc << this.successiveLow); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Refinement scan for DC coefficient
|
||||
|
if (this.GetBit() != 0) |
||||
|
{ |
||||
|
blockDataRef += (short)(1 << this.successiveLow); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void DecodeBlockProgressiveAC( |
||||
|
PdfJsFrameComponent component, |
||||
|
int row, |
||||
|
int col, |
||||
|
ref PdfJsHuffmanTable acTable, |
||||
|
ref short fastACRef) |
||||
|
{ |
||||
|
if (this.spectralStart == 0) |
||||
|
{ |
||||
|
JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); |
||||
|
} |
||||
|
|
||||
|
ref short blockDataRef = ref component.GetBlockDataReference(row, col); |
||||
|
|
||||
|
if (this.successiveHigh == 0) |
||||
|
{ |
||||
|
// MCU decoding for AC initial scan (either spectral selection,
|
||||
|
// or first pass of successive approximation).
|
||||
|
int shift = this.successiveLow; |
||||
|
|
||||
|
if (this.eobrun != 0) |
||||
|
{ |
||||
|
this.eobrun--; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int k = this.spectralStart; |
||||
|
do |
||||
|
{ |
||||
|
int zig; |
||||
|
int s; |
||||
|
|
||||
|
this.CheckBits(); |
||||
|
int c = this.PeekBits(); |
||||
|
int r = Unsafe.Add(ref fastACRef, c); |
||||
|
|
||||
|
if (r != 0) |
||||
|
{ |
||||
|
// Fast AC path
|
||||
|
k += (r >> 4) & 15; // Run
|
||||
|
s = r & 15; // Combined length
|
||||
|
this.codeBuffer <<= s; |
||||
|
this.codeBits -= s; |
||||
|
|
||||
|
// Decode into unzigzag location
|
||||
|
zig = this.dctZigZag[k++]; |
||||
|
Unsafe.Add(ref blockDataRef, zig) = (short)((r >> 8) << shift); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int rs = this.DecodeHuffman(ref acTable); |
||||
|
|
||||
|
if (rs < 0) |
||||
|
{ |
||||
|
JpegThrowHelper.ThrowBadHuffmanCode(); |
||||
|
} |
||||
|
|
||||
|
s = rs & 15; |
||||
|
r = rs >> 4; |
||||
|
|
||||
|
if (s == 0) |
||||
|
{ |
||||
|
if (r < 15) |
||||
|
{ |
||||
|
this.eobrun = 1 << r; |
||||
|
if (r != 0) |
||||
|
{ |
||||
|
this.eobrun += this.GetBits(r); |
||||
|
} |
||||
|
|
||||
|
this.eobrun--; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
k += 16; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
k += r; |
||||
|
zig = this.dctZigZag[k++]; |
||||
|
Unsafe.Add(ref blockDataRef, zig) = (short)(this.ExtendReceive(s) << shift); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
while (k <= this.spectralEnd); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Refinement scan for these AC coefficients
|
||||
|
this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref PdfJsHuffmanTable acTable) |
||||
|
{ |
||||
|
int k; |
||||
|
|
||||
|
// Refinement scan for these AC coefficients
|
||||
|
short bit = (short)(1 << this.successiveLow); |
||||
|
|
||||
|
if (this.eobrun != 0) |
||||
|
{ |
||||
|
this.eobrun--; |
||||
|
for (k = this.spectralStart; k <= this.spectralEnd; k++) |
||||
|
{ |
||||
|
ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k]); |
||||
|
if (p != 0) |
||||
|
{ |
||||
|
if (this.GetBit() != 0) |
||||
|
{ |
||||
|
if ((p & bit) == 0) |
||||
|
{ |
||||
|
if (p > 0) |
||||
|
{ |
||||
|
p += bit; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
p -= bit; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
k = this.spectralStart; |
||||
|
do |
||||
|
{ |
||||
|
int rs = this.DecodeHuffman(ref acTable); |
||||
|
if (rs < 0) |
||||
|
{ |
||||
|
JpegThrowHelper.ThrowBadHuffmanCode(); |
||||
|
} |
||||
|
|
||||
|
int s = rs & 15; |
||||
|
int r = rs >> 4; |
||||
|
|
||||
|
if (s == 0) |
||||
|
{ |
||||
|
// r=15 s=0 should write 16 0s, so we just do
|
||||
|
// a run of 15 0s and then write s (which is 0),
|
||||
|
// so we don't have to do anything special here
|
||||
|
if (r < 15) |
||||
|
{ |
||||
|
this.eobrun = (1 << r) - 1; |
||||
|
|
||||
|
if (r != 0) |
||||
|
{ |
||||
|
this.eobrun += this.GetBits(r); |
||||
|
} |
||||
|
|
||||
|
r = 64; // Force end of block
|
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (s != 1) |
||||
|
{ |
||||
|
JpegThrowHelper.ThrowBadHuffmanCode(); |
||||
|
} |
||||
|
|
||||
|
// Sign bit
|
||||
|
if (this.GetBit() != 0) |
||||
|
{ |
||||
|
s = bit; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
s = -bit; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Advance by r
|
||||
|
while (k <= this.spectralEnd) |
||||
|
{ |
||||
|
ref short p = ref Unsafe.Add(ref blockDataRef, this.dctZigZag[k++]); |
||||
|
if (p != 0) |
||||
|
{ |
||||
|
if (this.GetBit() != 0) |
||||
|
{ |
||||
|
if ((p & bit) == 0) |
||||
|
{ |
||||
|
if (p > 0) |
||||
|
{ |
||||
|
p += bit; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
p -= bit; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (r == 0) |
||||
|
{ |
||||
|
p = (short)s; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
r--; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
while (k <= this.spectralEnd); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private int GetBits(int n) |
||||
|
{ |
||||
|
if (this.codeBits < n) |
||||
|
{ |
||||
|
this.FillBuffer(); |
||||
|
} |
||||
|
|
||||
|
uint k = LRot(this.codeBuffer, n); |
||||
|
this.codeBuffer = k & ~Bmask[n]; |
||||
|
k &= Bmask[n]; |
||||
|
this.codeBits -= n; |
||||
|
return (int)k; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private int GetBit() |
||||
|
{ |
||||
|
if (this.codeBits < 1) |
||||
|
{ |
||||
|
this.FillBuffer(); |
||||
|
} |
||||
|
|
||||
|
uint k = this.codeBuffer; |
||||
|
this.codeBuffer <<= 1; |
||||
|
this.codeBits--; |
||||
|
|
||||
|
return (int)(k & 0x80000000); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ColdPath)] |
||||
|
private void FillBuffer() |
||||
|
{ |
||||
|
// Attempt to load at least the minimum nbumber of required bits into the buffer.
|
||||
|
// We fail to do so only if we hit a marker or reach the end of the input stream.
|
||||
|
do |
||||
|
{ |
||||
|
int b = this.nomore ? 0 : this.stream.ReadByte(); |
||||
|
|
||||
|
if (b == -1) |
||||
|
{ |
||||
|
// We've encountered the end of the file stream which means there's no EOI marker in the image
|
||||
|
// or the SOS marker has the wrong dimensions set.
|
||||
|
this.eof = true; |
||||
|
b = 0; |
||||
|
} |
||||
|
|
||||
|
// Found a marker.
|
||||
|
if (b == JpegConstants.Markers.XFF) |
||||
|
{ |
||||
|
this.markerPosition = this.stream.Position - 1; |
||||
|
int c = this.stream.ReadByte(); |
||||
|
while (c == JpegConstants.Markers.XFF) |
||||
|
{ |
||||
|
c = this.stream.ReadByte(); |
||||
|
|
||||
|
if (c == -1) |
||||
|
{ |
||||
|
this.eof = true; |
||||
|
c = 0; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (c != 0) |
||||
|
{ |
||||
|
this.marker = (byte)c; |
||||
|
this.nomore = true; |
||||
|
if (!this.HasRestart()) |
||||
|
{ |
||||
|
this.badMarker = true; |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.codeBuffer |= (uint)b << (24 - this.codeBits); |
||||
|
this.codeBits += 8; |
||||
|
} |
||||
|
while (this.codeBits <= 24); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private int DecodeHuffman(ref PdfJsHuffmanTable table) |
||||
|
{ |
||||
|
this.CheckBits(); |
||||
|
|
||||
|
// Look at the top FastBits and determine what symbol ID it is,
|
||||
|
// if the code is <= FastBits.
|
||||
|
int c = this.PeekBits(); |
||||
|
int k = table.Lookahead[c]; |
||||
|
if (k < 0xFF) |
||||
|
{ |
||||
|
int s = table.Sizes[k]; |
||||
|
if (s > this.codeBits) |
||||
|
{ |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
this.codeBuffer <<= s; |
||||
|
this.codeBits -= s; |
||||
|
return table.Values[k]; |
||||
|
} |
||||
|
|
||||
|
return this.DecodeHuffmanSlow(ref table); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ColdPath)] |
||||
|
private int DecodeHuffmanSlow(ref PdfJsHuffmanTable table) |
||||
|
{ |
||||
|
// Naive test is to shift the code_buffer down so k bits are
|
||||
|
// valid, then test against MaxCode. To speed this up, we've
|
||||
|
// preshifted maxcode left so that it has (16-k) 0s at the
|
||||
|
// end; in other words, regardless of the number of bits, it
|
||||
|
// wants to be compared against something shifted to have 16;
|
||||
|
// that way we don't need to shift inside the loop.
|
||||
|
uint temp = this.codeBuffer >> 16; |
||||
|
int k; |
||||
|
for (k = FastBits + 1; ; k++) |
||||
|
{ |
||||
|
if (temp < table.MaxCode[k]) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (k == 17) |
||||
|
{ |
||||
|
// Error! code not found
|
||||
|
this.codeBits -= 16; |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
if (k > this.codeBits) |
||||
|
{ |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
// Convert the huffman code to the symbol id
|
||||
|
int c = (int)(((this.codeBuffer >> (32 - k)) & Bmask[k]) + table.ValOffset[k]); |
||||
|
|
||||
|
// Convert the id to a symbol
|
||||
|
this.codeBits -= k; |
||||
|
this.codeBuffer <<= k; |
||||
|
return table.Values[c]; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private int ExtendReceive(int n) |
||||
|
{ |
||||
|
if (this.codeBits < n) |
||||
|
{ |
||||
|
this.FillBuffer(); |
||||
|
} |
||||
|
|
||||
|
int sgn = (int)this.codeBuffer >> 31; |
||||
|
uint k = LRot(this.codeBuffer, n); |
||||
|
this.codeBuffer = k & ~Bmask[n]; |
||||
|
k &= Bmask[n]; |
||||
|
this.codeBits -= n; |
||||
|
return (int)(k + (Bias[n] & ~sgn)); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private void CheckBits() |
||||
|
{ |
||||
|
if (this.codeBits < 16) |
||||
|
{ |
||||
|
this.FillBuffer(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private int PeekBits() => (int)((this.codeBuffer >> (32 - FastBits)) & ((1 << FastBits) - 1)); |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private bool ContinueOnMcuComplete() |
||||
|
{ |
||||
|
if (--this.todo > 0) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (this.codeBits < 24) |
||||
|
{ |
||||
|
this.FillBuffer(); |
||||
|
} |
||||
|
|
||||
|
// If it's NOT a restart, then just bail, so we get corrupt data rather than no data.
|
||||
|
// Reset the stream to before any bad markers to ensure we can read sucessive segments.
|
||||
|
if (this.badMarker) |
||||
|
{ |
||||
|
this.stream.Position = this.markerPosition; |
||||
|
} |
||||
|
|
||||
|
if (!this.HasRestart()) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
this.Reset(); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(InliningOptions.ShortMethod)] |
||||
|
private bool HasRestart() |
||||
|
{ |
||||
|
byte m = this.marker; |
||||
|
return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7; |
||||
|
} |
||||
|
|
||||
|
private void Reset() |
||||
|
{ |
||||
|
this.codeBits = 0; |
||||
|
this.codeBuffer = 0; |
||||
|
|
||||
|
for (int i = 0; i < this.components.Length; i++) |
||||
|
{ |
||||
|
PdfJsFrameComponent c = this.components[i]; |
||||
|
c.DcPredictor = 0; |
||||
|
} |
||||
|
|
||||
|
this.nomore = false; |
||||
|
this.marker = JpegConstants.Markers.XFF; |
||||
|
this.markerPosition = 0; |
||||
|
this.badMarker = false; |
||||
|
this.eobrun = 0; |
||||
|
|
||||
|
// No more than 1<<31 MCUs if no restartInterval? that's plenty safe since we don't even allow 1<<30 pixels
|
||||
|
this.todo = this.restartInterval > 0 ? this.restartInterval : int.MaxValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Transforms |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Enumerated anchor positions to apply to resized images.
|
/// Enumerated anchor positions to apply to resized images.
|
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Filters |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Enumerates the various types of defined color blindness filters.
|
/// Enumerates the various types of defined color blindness filters.
|
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Convolution |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Enumerates the various types of defined edge detection filters.
|
/// Enumerates the various types of defined edge detection filters.
|
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Transforms |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Provides enumeration over how a image should be flipped.
|
/// Provides enumeration over how a image should be flipped.
|
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Filters |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Enumerates the various types of defined grayscale filters.
|
/// Enumerates the various types of defined grayscale filters.
|
||||
@ -1,9 +1,9 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.Processing.Dithering.ErrorDiffusion; |
using SixLabors.ImageSharp.Processing.Processors.Dithering; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Dithering |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Contains reusable static instances of known error diffusion algorithms
|
/// Contains reusable static instances of known error diffusion algorithms
|
||||
@ -1,14 +1,14 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.Processing.Dithering.Ordered; |
using SixLabors.ImageSharp.Processing.Processors.Dithering; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Dithering |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Contains reusable static instances of known ordered dither matrices
|
/// Contains reusable static instances of known ordered dither matrices
|
||||
/// </summary>
|
/// </summary>
|
||||
public class KnownDitherers |
public static class KnownDitherers |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the order ditherer using the 2x2 Bayer dithering matrix
|
/// Gets the order ditherer using the 2x2 Bayer dithering matrix
|
||||
@ -1,7 +1,9 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Quantization |
using SixLabors.ImageSharp.Processing.Processors.Quantization; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Contains reusable static instances of known quantizing algorithms
|
/// Contains reusable static instances of known quantizing algorithms
|
||||
@ -1,9 +1,9 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.Processing.Transforms.Resamplers; |
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Transforms |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Contains reusable static instances of known resampling algorithms
|
/// Contains reusable static instances of known resampling algorithms
|
||||
@ -1,7 +1,7 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Transforms |
namespace SixLabors.ImageSharp.Processing |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Enumerates the available orientation values supplied by EXIF metadata.
|
/// Enumerates the available orientation values supplied by EXIF metadata.
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue