mirror of https://github.com/SixLabors/ImageSharp
123 changed files with 1184 additions and 4364 deletions
@ -1,24 +1,23 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components |
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a 2 pairs of huffman tables
|
|||
/// Defines a 2 pairs of huffman tables.
|
|||
/// </summary>
|
|||
internal sealed class PdfJsHuffmanTables |
|||
internal sealed class HuffmanTables |
|||
{ |
|||
private readonly PdfJsHuffmanTable[] tables = new PdfJsHuffmanTable[4]; |
|||
private readonly HuffmanTable[] tables = new HuffmanTable[4]; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the table at the given index.
|
|||
/// </summary>
|
|||
/// <param name="index">The index</param>
|
|||
/// <returns>The <see cref="PdfJsHuffmanTable"/></returns>
|
|||
public ref PdfJsHuffmanTable this[int index] |
|||
/// <returns>The <see cref="HuffmanTable"/></returns>
|
|||
public ref HuffmanTable this[int index] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => ref this.tables[index]; |
|||
@ -1,155 +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.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Holds the unprocessed bits that have been taken from the byte-stream.
|
|||
/// The n least significant bits of a form the unread bits, to be read in MSB to
|
|||
/// LSB order.
|
|||
/// </summary>
|
|||
internal struct Bits |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the accumulator.
|
|||
/// </summary>
|
|||
public int Accumulator; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the mask.
|
|||
/// <![CDATA[mask==1<<(unreadbits-1) when unreadbits>0, with mask==0 when unreadbits==0.]]>
|
|||
/// </summary>
|
|||
public int Mask; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of unread bits in the accumulator.
|
|||
/// </summary>
|
|||
public int UnreadBits; |
|||
|
|||
/// <summary>
|
|||
/// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
|
|||
/// least n. For best performance (avoiding function calls inside hot loops),
|
|||
/// the caller is the one responsible for first checking that bits.UnreadBits < n.
|
|||
/// </summary>
|
|||
/// <param name="n">The number of bits to ensure.</param>
|
|||
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void EnsureNBits(int n, ref InputProcessor inputProcessor) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor); |
|||
errorCode.EnsureNoError(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
|
|||
/// least n. For best performance (avoiding function calls inside hot loops),
|
|||
/// the caller is the one responsible for first checking that bits.UnreadBits < n.
|
|||
/// This method does not throw. Returns <see cref="GolangDecoderErrorCode"/> instead.
|
|||
/// </summary>
|
|||
/// <param name="n">The number of bits to ensure.</param>
|
|||
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
|
|||
/// <returns>Error code</returns>
|
|||
public GolangDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor) |
|||
{ |
|||
while (true) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor); |
|||
if (errorCode != GolangDecoderErrorCode.NoError || this.UnreadBits >= n) |
|||
{ |
|||
return errorCode; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==8
|
|||
/// </summary>
|
|||
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
|
|||
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor) |
|||
{ |
|||
return this.EnsureBitsStepImpl(ref inputProcessor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==1
|
|||
/// </summary>
|
|||
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
|
|||
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor) |
|||
{ |
|||
return this.EnsureBitsStepImpl(ref inputProcessor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Receive extend
|
|||
/// </summary>
|
|||
/// <param name="t">Byte</param>
|
|||
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
|
|||
/// <returns>Read bits value</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public int ReceiveExtend(int t, ref InputProcessor inputProcessor) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out int x); |
|||
errorCode.EnsureNoError(); |
|||
return x; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Receive extend
|
|||
/// </summary>
|
|||
/// <param name="t">Byte</param>
|
|||
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
|
|||
/// <param name="x">Read bits value</param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x) |
|||
{ |
|||
if (this.UnreadBits < t) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor); |
|||
if (errorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
x = int.MaxValue; |
|||
return errorCode; |
|||
} |
|||
} |
|||
|
|||
this.UnreadBits -= t; |
|||
this.Mask >>= t; |
|||
int s = 1 << t; |
|||
x = (this.Accumulator >> this.UnreadBits) & (s - 1); |
|||
|
|||
if (x < (s >> 1)) |
|||
{ |
|||
x += ((-1) << t) + 1; |
|||
} |
|||
|
|||
return GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
private GolangDecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out int c); |
|||
|
|||
if (errorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
return errorCode; |
|||
} |
|||
|
|||
this.Accumulator = (this.Accumulator << 8) | c; |
|||
this.UnreadBits += 8; |
|||
if (this.Mask == 0) |
|||
{ |
|||
this.Mask = 1 << 7; |
|||
} |
|||
else |
|||
{ |
|||
this.Mask <<= 8; |
|||
} |
|||
|
|||
return errorCode; |
|||
} |
|||
} |
|||
} |
|||
@ -1,255 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Bytes is a byte buffer, similar to a stream, except that it
|
|||
/// has to be able to unread more than 1 byte, due to byte stuffing.
|
|||
/// Byte stuffing is specified in section F.1.2.3.
|
|||
/// TODO: Optimize buffer management inside this class!
|
|||
/// </summary>
|
|||
internal struct Bytes : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Specifies the buffer size for <see cref="Buffer"/> and <see cref="BufferAsInt"/>
|
|||
/// </summary>
|
|||
public const int BufferSize = 4096; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the buffer.
|
|||
/// buffer[i:j] are the buffered bytes read from the underlying
|
|||
/// stream that haven't yet been passed further on.
|
|||
/// </summary>
|
|||
public byte[] Buffer; |
|||
|
|||
/// <summary>
|
|||
/// Values of <see cref="Buffer"/> converted to <see cref="int"/>-s
|
|||
/// </summary>
|
|||
public int[] BufferAsInt; |
|||
|
|||
/// <summary>
|
|||
/// Start of bytes read
|
|||
/// </summary>
|
|||
public int I; |
|||
|
|||
/// <summary>
|
|||
/// End of bytes read
|
|||
/// </summary>
|
|||
public int J; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the unreadable bytes. The number of bytes to back up i after
|
|||
/// overshooting. It can be 0, 1 or 2.
|
|||
/// </summary>
|
|||
public int UnreadableBytes; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="Bytes"/>, and initializes it's buffer.
|
|||
/// </summary>
|
|||
/// <returns>The bytes created</returns>
|
|||
public static Bytes Create() |
|||
{ |
|||
// DO NOT bother with buffers and array pooling here!
|
|||
// It only makes things worse!
|
|||
return new Bytes |
|||
{ |
|||
Buffer = new byte[BufferSize], |
|||
BufferAsInt = new int[BufferSize] |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes of the underlying buffer
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.Buffer = null; |
|||
this.BufferAsInt = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
|
|||
/// </summary>
|
|||
/// <param name="inputStream">Input stream</param>
|
|||
/// <param name="x">The result byte as <see cref="int"/></param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x) |
|||
{ |
|||
// Take the fast path if bytes.buf contains at least two bytes.
|
|||
if (this.I + 2 <= this.J) |
|||
{ |
|||
x = this.BufferAsInt[this.I]; |
|||
this.I++; |
|||
this.UnreadableBytes = 1; |
|||
if (x != JpegConstants.Markers.XFFInt) |
|||
{ |
|||
return GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
if (this.BufferAsInt[this.I] != 0x00) |
|||
{ |
|||
return GolangDecoderErrorCode.MissingFF00; |
|||
} |
|||
|
|||
this.I++; |
|||
this.UnreadableBytes = 2; |
|||
x = JpegConstants.Markers.XFF; |
|||
return GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
this.UnreadableBytes = 0; |
|||
|
|||
GolangDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x); |
|||
this.UnreadableBytes = 1; |
|||
if (errorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
return errorCode; |
|||
} |
|||
|
|||
if (x != JpegConstants.Markers.XFF) |
|||
{ |
|||
return GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
errorCode = this.ReadByteAsIntUnsafe(inputStream, out x); |
|||
this.UnreadableBytes = 2; |
|||
if (errorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
return errorCode; |
|||
} |
|||
|
|||
if (x != 0x00) |
|||
{ |
|||
return GolangDecoderErrorCode.MissingFF00; |
|||
} |
|||
|
|||
x = JpegConstants.Markers.XFF; |
|||
return GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
|
|||
/// </summary>
|
|||
/// <param name="inputStream">Input stream</param>
|
|||
/// <returns>The <see cref="byte"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public byte ReadByte(Stream inputStream) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out byte result); |
|||
errorCode.EnsureNoError(); |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing.
|
|||
/// This method does not throw on format error, it returns a <see cref="GolangDecoderErrorCode"/> instead.
|
|||
/// </summary>
|
|||
/// <param name="inputStream">Input stream</param>
|
|||
/// <param name="result">The result <see cref="byte"/> as out parameter</param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = GolangDecoderErrorCode.NoError; |
|||
while (this.I == this.J) |
|||
{ |
|||
errorCode = this.FillUnsafe(inputStream); |
|||
if (errorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
result = 0; |
|||
return errorCode; |
|||
} |
|||
} |
|||
|
|||
result = this.Buffer[this.I]; |
|||
this.I++; |
|||
this.UnreadableBytes = 0; |
|||
return errorCode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Same as <see cref="ReadByteUnsafe"/> but the result is an <see cref="int"/>
|
|||
/// </summary>
|
|||
/// <param name="inputStream">The input stream</param>
|
|||
/// <param name="result">The result <see cref="int"/></param>
|
|||
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public GolangDecoderErrorCode ReadByteAsIntUnsafe(Stream inputStream, out int result) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = GolangDecoderErrorCode.NoError; |
|||
while (this.I == this.J) |
|||
{ |
|||
errorCode = this.FillUnsafe(inputStream); |
|||
if (errorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
result = 0; |
|||
return errorCode; |
|||
} |
|||
} |
|||
|
|||
result = this.BufferAsInt[this.I]; |
|||
this.I++; |
|||
this.UnreadableBytes = 0; |
|||
return errorCode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills up the bytes buffer from the underlying stream.
|
|||
/// It should only be called when there are no unread bytes in bytes.
|
|||
/// </summary>
|
|||
/// <exception cref="EOFException">Thrown when reached end of stream unexpectedly.</exception>
|
|||
/// <param name="inputStream">Input stream</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Fill(Stream inputStream) |
|||
{ |
|||
GolangDecoderErrorCode errorCode = this.FillUnsafe(inputStream); |
|||
errorCode.EnsureNoError(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills up the bytes buffer from the underlying stream.
|
|||
/// It should only be called when there are no unread bytes in bytes.
|
|||
/// This method does not throw <see cref="EOFException"/>, returns a <see cref="GolangDecoderErrorCode"/> instead!
|
|||
/// </summary>
|
|||
/// <param name="inputStream">Input stream</param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode FillUnsafe(Stream inputStream) |
|||
{ |
|||
if (this.I != this.J) |
|||
{ |
|||
// Unrecoverable error in the input, throwing!
|
|||
DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist(); |
|||
} |
|||
|
|||
// Move the last 2 bytes to the start of the buffer, in case we need
|
|||
// to call UnreadByteStuffedByte.
|
|||
if (this.J > 2) |
|||
{ |
|||
this.Buffer[0] = this.Buffer[this.J - 2]; |
|||
this.Buffer[1] = this.Buffer[this.J - 1]; |
|||
this.I = 2; |
|||
this.J = 2; |
|||
} |
|||
|
|||
// Fill in the rest of the buffer.
|
|||
int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J); |
|||
if (n == 0) |
|||
{ |
|||
return GolangDecoderErrorCode.UnexpectedEndOfStream; |
|||
} |
|||
|
|||
this.J += n; |
|||
|
|||
for (int i = 0; i < this.Buffer.Length; i++) |
|||
{ |
|||
this.BufferAsInt[i] = this.Buffer[i]; |
|||
} |
|||
|
|||
return GolangDecoderErrorCode.NoError; |
|||
} |
|||
} |
|||
} |
|||
@ -1,96 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates exception thrower methods for the Jpeg Encoder
|
|||
/// </summary>
|
|||
internal static class DecoderThrowHelper |
|||
{ |
|||
/// <summary>
|
|||
/// Throws an exception that belongs to the given <see cref="GolangDecoderErrorCode"/>
|
|||
/// </summary>
|
|||
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void ThrowExceptionForErrorCode(this GolangDecoderErrorCode errorCode) |
|||
{ |
|||
// REMARK: If this method throws for an image that is expected to be decodable,
|
|||
// consider using the ***Unsafe variant of the parsing method that asks for ThrowExceptionForErrorCode()
|
|||
// then verify the error code + implement fallback logic manually!
|
|||
switch (errorCode) |
|||
{ |
|||
case GolangDecoderErrorCode.NoError: |
|||
throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode)); |
|||
case GolangDecoderErrorCode.MissingFF00: |
|||
throw new MissingFF00Exception(); |
|||
case GolangDecoderErrorCode.UnexpectedEndOfStream: |
|||
throw new EOFException(); |
|||
default: |
|||
throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Throws an exception if the given <see cref="GolangDecoderErrorCode"/> defines an error.
|
|||
/// </summary>
|
|||
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void EnsureNoError(this GolangDecoderErrorCode errorCode) |
|||
{ |
|||
if (errorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
ThrowExceptionForErrorCode(errorCode); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Throws an exception if the given <see cref="GolangDecoderErrorCode"/> is <see cref="GolangDecoderErrorCode.UnexpectedEndOfStream"/>.
|
|||
/// </summary>
|
|||
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void EnsureNoEOF(this GolangDecoderErrorCode errorCode) |
|||
{ |
|||
if (errorCode == GolangDecoderErrorCode.UnexpectedEndOfStream) |
|||
{ |
|||
errorCode.ThrowExceptionForErrorCode(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encapsulates methods throwing different flavours of <see cref="ImageFormatException"/>-s.
|
|||
/// </summary>
|
|||
public static class ThrowImageFormatException |
|||
{ |
|||
/// <summary>
|
|||
/// Throws "Fill called when unread bytes exist".
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void FillCalledWhenUnreadBytesExist() |
|||
{ |
|||
throw new ImageFormatException("Fill called when unread bytes exist!"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Throws "Bad Huffman code".
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void BadHuffmanCode() |
|||
{ |
|||
throw new ImageFormatException("Bad Huffman code!"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Throws "Uninitialized Huffman table".
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void UninitializedHuffmanTable() |
|||
{ |
|||
throw new ImageFormatException("Uninitialized Huffman table"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// The EOF (End of File exception).
|
|||
/// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker
|
|||
/// TODO: Rename to UnexpectedEndOfStreamException
|
|||
/// </summary>
|
|||
internal class EOFException : Exception |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EOFException"/> class.
|
|||
/// </summary>
|
|||
public EOFException() |
|||
: base("Reached end of stream before proceeding EOI marker!") |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,253 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <inheritdoc cref="IJpegComponent" />
|
|||
/// <summary>
|
|||
/// Represents a single color component
|
|||
/// </summary>
|
|||
internal class GolangComponent : IDisposable, IJpegComponent |
|||
{ |
|||
public GolangComponent(byte identifier, int index) |
|||
{ |
|||
this.Identifier = identifier; |
|||
this.Index = index; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the identifier
|
|||
/// </summary>
|
|||
public byte Identifier { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public int Index { get; } |
|||
|
|||
public Size SizeInBlocks { get; private set; } |
|||
|
|||
public Size SamplingFactors { get; private set; } |
|||
|
|||
public Size SubSamplingDivisors { get; private set; } |
|||
|
|||
public int HorizontalSamplingFactor => this.SamplingFactors.Width; |
|||
|
|||
public int VerticalSamplingFactor => this.SamplingFactors.Height; |
|||
|
|||
/// <inheritdoc />
|
|||
public int QuantizationTableIndex { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
/// <summary>
|
|||
/// Gets the <see cref="T:SixLabors.ImageSharp.Memory.Buffer`1" /> storing the "raw" frequency-domain decoded blocks.
|
|||
/// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks.
|
|||
/// This is done by <see cref="M:SixLabors.ImageSharp.Formats.Jpeg.GolangPort.OrigJpegDecoderCore.ProcessBlocksIntoJpegImageChannels" />.
|
|||
/// When <see cref="P:SixLabors.ImageSharp.Formats.Jpeg.GolangPort.OrigJpegDecoderCore.IsProgressive" /> us true, we are touching these blocks multiple times - each time we process a Scan.
|
|||
/// </summary>
|
|||
public Buffer2D<Block8x8> SpectralBlocks { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Initializes <see cref="SpectralBlocks"/>
|
|||
/// </summary>
|
|||
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
|
|||
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
|
|||
public void InitializeDerivedData(MemoryAllocator memoryAllocator, GolangJpegDecoderCore decoder) |
|||
{ |
|||
// For 4-component images (either CMYK or YCbCrK), we only support two
|
|||
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
|
|||
// Theoretically, 4-component JPEG images could mix and match hv values
|
|||
// but in practice, those two combinations are the only ones in use,
|
|||
// and it simplifies the applyBlack code below if we can assume that:
|
|||
// - for CMYK, the C and K channels have full samples, and if the M
|
|||
// and Y channels subsample, they subsample both horizontally and
|
|||
// vertically.
|
|||
// - for YCbCrK, the Y and K channels have full samples.
|
|||
this.SizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(this.SamplingFactors); |
|||
|
|||
if (this.Index == 0 || this.Index == 3) |
|||
{ |
|||
this.SubSamplingDivisors = new Size(1, 1); |
|||
} |
|||
else |
|||
{ |
|||
GolangComponent c0 = decoder.Components[0]; |
|||
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); |
|||
} |
|||
|
|||
this.SpectralBlocks = memoryAllocator.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, AllocationOptions.Clean); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes all component data except <see cref="SpectralBlocks"/>.
|
|||
/// </summary>
|
|||
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
|
|||
public void InitializeCoreData(GolangJpegDecoderCore decoder) |
|||
{ |
|||
// Section B.2.2 states that "the value of C_i shall be different from
|
|||
// the values of C_1 through C_(i-1)".
|
|||
int i = this.Index; |
|||
|
|||
for (int j = 0; j < this.Index; j++) |
|||
{ |
|||
if (this.Identifier == decoder.Components[j].Identifier) |
|||
{ |
|||
throw new ImageFormatException("Repeated component identifier"); |
|||
} |
|||
} |
|||
|
|||
this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)]; |
|||
if (this.QuantizationTableIndex > GolangJpegDecoderCore.MaxTq) |
|||
{ |
|||
throw new ImageFormatException("Bad Tq value"); |
|||
} |
|||
|
|||
byte hv = decoder.Temp[7 + (3 * i)]; |
|||
int h = hv >> 4; |
|||
int v = hv & 0x0f; |
|||
if (h < 1 || h > 4 || v < 1 || v > 4) |
|||
{ |
|||
throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio"); |
|||
} |
|||
|
|||
if (h == 3 || v == 3) |
|||
{ |
|||
throw new ImageFormatException("Lnsupported subsampling ratio"); |
|||
} |
|||
|
|||
switch (decoder.ComponentCount) |
|||
{ |
|||
case 1: |
|||
|
|||
// If a JPEG image has only one component, section A.2 says "this data
|
|||
// is non-interleaved by definition" and section A.2.2 says "[in this
|
|||
// case...] the order of data units within a scan shall be left-to-right
|
|||
// and top-to-bottom... regardless of the values of H_1 and V_1". Section
|
|||
// 4.8.2 also says "[for non-interleaved data], the MCU is defined to be
|
|||
// one data unit". Similarly, section A.1.1 explains that it is the ratio
|
|||
// of H_i to max_j(H_j) that matters, and similarly for V. For grayscale
|
|||
// images, H_1 is the maximum H_j for all components j, so that ratio is
|
|||
// always 1. The component's (h, v) is effectively always (1, 1): even if
|
|||
// the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
|
|||
// MCUs, not two 16x8 MCUs.
|
|||
h = 1; |
|||
v = 1; |
|||
break; |
|||
|
|||
case 3: |
|||
|
|||
// For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
|
|||
// 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the
|
|||
// (h, v) values for the Y component are either (1, 1), (1, 2),
|
|||
// (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values
|
|||
// must be a multiple of the Cb and Cr component's values. We also
|
|||
// assume that the two chroma components have the same subsampling
|
|||
// ratio.
|
|||
switch (i) |
|||
{ |
|||
case 0: |
|||
{ |
|||
// Y.
|
|||
// We have already verified, above, that h and v are both
|
|||
// either 1, 2 or 4, so invalid (h, v) combinations are those
|
|||
// with v == 4.
|
|||
if (v == 4) |
|||
{ |
|||
throw new ImageFormatException("Unsupported subsampling ratio"); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case 1: |
|||
{ |
|||
// Cb.
|
|||
Size s0 = decoder.Components[0].SamplingFactors; |
|||
|
|||
if (s0.Width % h != 0 || s0.Height % v != 0) |
|||
{ |
|||
throw new ImageFormatException("Unsupported subsampling ratio"); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case 2: |
|||
{ |
|||
// Cr.
|
|||
Size s1 = decoder.Components[1].SamplingFactors; |
|||
|
|||
if (s1.Width != h || s1.Height != v) |
|||
{ |
|||
throw new ImageFormatException("Unsupported subsampling ratio"); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 4: |
|||
|
|||
// For 4-component images (either CMYK or YCbCrK), we only support two
|
|||
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
|
|||
// Theoretically, 4-component JPEG images could mix and match hv values
|
|||
// but in practice, those two combinations are the only ones in use,
|
|||
// and it simplifies the applyBlack code below if we can assume that:
|
|||
// - for CMYK, the C and K channels have full samples, and if the M
|
|||
// and Y channels subsample, they subsample both horizontally and
|
|||
// vertically.
|
|||
// - for YCbCrK, the Y and K channels have full samples.
|
|||
switch (i) |
|||
{ |
|||
case 0: |
|||
if (hv != 0x11 && hv != 0x22) |
|||
{ |
|||
throw new ImageFormatException("Unsupported subsampling ratio"); |
|||
} |
|||
|
|||
break; |
|||
case 1: |
|||
case 2: |
|||
if (hv != 0x11) |
|||
{ |
|||
throw new ImageFormatException("Unsupported subsampling ratio"); |
|||
} |
|||
|
|||
break; |
|||
case 3: |
|||
Size s0 = decoder.Components[0].SamplingFactors; |
|||
|
|||
if (s0.Width != h || s0.Height != v) |
|||
{ |
|||
throw new ImageFormatException("Unsupported subsampling ratio"); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
this.SamplingFactors = new Size(h, v); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public ref Block8x8 GetBlockReference(int column, int row) |
|||
{ |
|||
return ref this.SpectralBlocks[column, row]; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.SpectralBlocks?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a component scan
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct GolangComponentScan |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the component index.
|
|||
/// </summary>
|
|||
public byte ComponentIndex; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the DC table selector
|
|||
/// </summary>
|
|||
public byte DcTableSelector; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the AC table selector
|
|||
/// </summary>
|
|||
public byte AcTableSelector; |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Represents "recoverable" decoder errors.
|
|||
/// </summary>
|
|||
internal enum GolangDecoderErrorCode |
|||
{ |
|||
/// <summary>
|
|||
/// NoError
|
|||
/// </summary>
|
|||
NoError, |
|||
|
|||
/// <summary>
|
|||
/// MissingFF00
|
|||
/// </summary>
|
|||
// ReSharper disable once InconsistentNaming
|
|||
MissingFF00, |
|||
|
|||
/// <summary>
|
|||
/// End of stream reached unexpectedly
|
|||
/// </summary>
|
|||
UnexpectedEndOfStream |
|||
} |
|||
} |
|||
@ -1,260 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a Huffman tree
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal unsafe struct GolangHuffmanTree |
|||
{ |
|||
/// <summary>
|
|||
/// The index of the AC table row
|
|||
/// </summary>
|
|||
public const int AcTableIndex = 1; |
|||
|
|||
/// <summary>
|
|||
/// The index of the DC table row
|
|||
/// </summary>
|
|||
public const int DcTableIndex = 0; |
|||
|
|||
/// <summary>
|
|||
/// The maximum (inclusive) number of codes in a Huffman tree.
|
|||
/// </summary>
|
|||
public const int MaxNCodes = 256; |
|||
|
|||
/// <summary>
|
|||
/// The maximum (inclusive) number of bits in a Huffman code.
|
|||
/// </summary>
|
|||
public const int MaxCodeLength = 16; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of Huffman table classes
|
|||
/// </summary>
|
|||
public const int MaxTc = 1; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of Huffman table identifiers
|
|||
/// </summary>
|
|||
public const int MaxTh = 3; |
|||
|
|||
/// <summary>
|
|||
/// Row size of the Huffman table
|
|||
/// </summary>
|
|||
public const int ThRowSize = MaxTh + 1; |
|||
|
|||
/// <summary>
|
|||
/// Number of Hufman Trees in the Huffman table
|
|||
/// </summary>
|
|||
public const int NumberOfTrees = (MaxTc + 1) * (MaxTh + 1); |
|||
|
|||
/// <summary>
|
|||
/// The log-2 size of the Huffman decoder's look-up table.
|
|||
/// </summary>
|
|||
public const int LutSizeLog2 = 8; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of codes in the tree.
|
|||
/// </summary>
|
|||
public int Length; |
|||
|
|||
/// <summary>
|
|||
/// Gets the look-up table for the next LutSize bits in the bit-stream.
|
|||
/// The high 8 bits of the uint16 are the encoded value. The low 8 bits
|
|||
/// are 1 plus the code length, or 0 if the value is too large to fit in
|
|||
/// lutSize bits.
|
|||
/// </summary>
|
|||
public FixedInt32Buffer256 Lut; |
|||
|
|||
/// <summary>
|
|||
/// Gets the the decoded values, sorted by their encoding.
|
|||
/// </summary>
|
|||
public FixedInt32Buffer256 Values; |
|||
|
|||
/// <summary>
|
|||
/// Gets the array of minimum codes.
|
|||
/// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
|
|||
/// </summary>
|
|||
public FixedInt32Buffer16 MinCodes; |
|||
|
|||
/// <summary>
|
|||
/// Gets the array of maximum codes.
|
|||
/// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
|
|||
/// </summary>
|
|||
public FixedInt32Buffer16 MaxCodes; |
|||
|
|||
/// <summary>
|
|||
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
|
|||
/// </summary>
|
|||
public FixedInt32Buffer16 Indices; |
|||
|
|||
/// <summary>
|
|||
/// Creates and initializes an array of <see cref="GolangHuffmanTree" /> instances of size <see cref="NumberOfTrees" />
|
|||
/// </summary>
|
|||
/// <returns>An array of <see cref="GolangHuffmanTree" /> instances representing the Huffman tables</returns>
|
|||
public static GolangHuffmanTree[] CreateHuffmanTrees() |
|||
{ |
|||
return new GolangHuffmanTree[NumberOfTrees]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Internal part of the DHT processor, whatever does it mean
|
|||
/// </summary>
|
|||
/// <param name="inputProcessor">The decoder instance</param>
|
|||
/// <param name="defineHuffmanTablesData">The temporary buffer that holds the data that has been read from the Jpeg stream</param>
|
|||
/// <param name="remaining">Remaining bits</param>
|
|||
public void ProcessDefineHuffmanTablesMarkerLoop( |
|||
ref InputProcessor inputProcessor, |
|||
byte[] defineHuffmanTablesData, |
|||
ref int remaining) |
|||
{ |
|||
// Read nCodes and huffman.Valuess (and derive h.Length).
|
|||
// nCodes[i] is the number of codes with code length i.
|
|||
// h.Length is the total number of codes.
|
|||
this.Length = 0; |
|||
|
|||
int[] ncodes = new int[MaxCodeLength]; |
|||
for (int i = 0; i < ncodes.Length; i++) |
|||
{ |
|||
ncodes[i] = defineHuffmanTablesData[i + 1]; |
|||
this.Length += ncodes[i]; |
|||
} |
|||
|
|||
if (this.Length == 0) |
|||
{ |
|||
throw new ImageFormatException("Huffman table has zero length"); |
|||
} |
|||
|
|||
if (this.Length > MaxNCodes) |
|||
{ |
|||
throw new ImageFormatException("Huffman table has excessive length"); |
|||
} |
|||
|
|||
remaining -= this.Length + 17; |
|||
if (remaining < 0) |
|||
{ |
|||
throw new ImageFormatException("DHT has wrong length"); |
|||
} |
|||
|
|||
byte[] values = new byte[MaxNCodes]; |
|||
inputProcessor.ReadFull(values, 0, this.Length); |
|||
|
|||
fixed (int* valuesPtr = this.Values.Data) |
|||
fixed (int* lutPtr = this.Lut.Data) |
|||
{ |
|||
for (int i = 0; i < values.Length; i++) |
|||
{ |
|||
valuesPtr[i] = values[i]; |
|||
} |
|||
|
|||
// Derive the look-up table.
|
|||
for (int i = 0; i < MaxNCodes; i++) |
|||
{ |
|||
lutPtr[i] = 0; |
|||
} |
|||
|
|||
int x = 0, code = 0; |
|||
|
|||
for (int i = 0; i < LutSizeLog2; i++) |
|||
{ |
|||
code <<= 1; |
|||
|
|||
for (int j = 0; j < ncodes[i]; j++) |
|||
{ |
|||
// The codeLength is 1+i, so shift code by 8-(1+i) to
|
|||
// calculate the high bits for every 8-bit sequence
|
|||
// whose codeLength's high bits matches code.
|
|||
// The high 8 bits of lutValue are the encoded value.
|
|||
// The low 8 bits are 1 plus the codeLength.
|
|||
int base2 = code << (7 - i); |
|||
int lutValue = (valuesPtr[x] << 8) | (2 + i); |
|||
|
|||
for (int k = 0; k < 1 << (7 - i); k++) |
|||
{ |
|||
lutPtr[base2 | k] = lutValue; |
|||
} |
|||
|
|||
code++; |
|||
x++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
fixed (int* minCodesPtr = this.MinCodes.Data) |
|||
fixed (int* maxCodesPtr = this.MaxCodes.Data) |
|||
fixed (int* indicesPtr = this.Indices.Data) |
|||
{ |
|||
// Derive minCodes, maxCodes, and indices.
|
|||
int c = 0, index = 0; |
|||
for (int i = 0; i < ncodes.Length; i++) |
|||
{ |
|||
int nc = ncodes[i]; |
|||
if (nc == 0) |
|||
{ |
|||
minCodesPtr[i] = -1; |
|||
maxCodesPtr[i] = -1; |
|||
indicesPtr[i] = -1; |
|||
} |
|||
else |
|||
{ |
|||
minCodesPtr[i] = c; |
|||
maxCodesPtr[i] = c + nc - 1; |
|||
indicesPtr[i] = index; |
|||
c += nc; |
|||
index += nc; |
|||
} |
|||
|
|||
c <<= 1; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value for the given code and index.
|
|||
/// </summary>
|
|||
/// <param name="code">The code</param>
|
|||
/// <param name="codeLength">The code length</param>
|
|||
/// <returns>The value</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public int GetValue(int code, int codeLength) |
|||
{ |
|||
return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]]; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct FixedInt32Buffer256 |
|||
{ |
|||
public fixed int Data[256]; |
|||
|
|||
public int this[int idx] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
ref int self = ref Unsafe.As<FixedInt32Buffer256, int>(ref this); |
|||
return Unsafe.Add(ref self, idx); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct FixedInt32Buffer16 |
|||
{ |
|||
public fixed int Data[16]; |
|||
|
|||
public int this[int idx] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
ref int self = ref Unsafe.As<FixedInt32Buffer16, int>(ref this); |
|||
return Unsafe.Add(ref self, idx); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <content>
|
|||
/// Conains the definition of <see cref="ComputationData"/>
|
|||
/// </content>
|
|||
internal unsafe partial struct GolangJpegScanDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Holds the "large" data blocks needed for computations.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct ComputationData |
|||
{ |
|||
/// <summary>
|
|||
/// The main input/working block
|
|||
/// </summary>
|
|||
public Block8x8 Block; |
|||
|
|||
/// <summary>
|
|||
/// The jpeg unzig data
|
|||
/// </summary>
|
|||
public ZigZag Unzig; |
|||
|
|||
/// <summary>
|
|||
/// The buffer storing the <see cref="GolangComponentScan"/>-s for each component
|
|||
/// </summary>
|
|||
public fixed byte ScanData[3 * GolangJpegDecoderCore.MaxComponents]; |
|||
|
|||
/// <summary>
|
|||
/// The DC values for each component
|
|||
/// </summary>
|
|||
public fixed int Dc[GolangJpegDecoderCore.MaxComponents]; |
|||
|
|||
/// <summary>
|
|||
/// Creates and initializes a new <see cref="ComputationData"/> instance
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="ComputationData"/></returns>
|
|||
public static ComputationData Create() |
|||
{ |
|||
ComputationData data = default; |
|||
data.Unzig = ZigZag.CreateUnzigTable(); |
|||
return data; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <content>
|
|||
/// Conains the definition of <see cref="DataPointers"/>
|
|||
/// </content>
|
|||
internal unsafe partial struct GolangJpegScanDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Contains pointers to the memory regions of <see cref="ComputationData"/> so they can be easily passed around to pointer based utility methods of <see cref="Block8x8F"/>
|
|||
/// </summary>
|
|||
public struct DataPointers |
|||
{ |
|||
/// <summary>
|
|||
/// Pointer to <see cref="ComputationData.Block"/>
|
|||
/// </summary>
|
|||
public Block8x8* Block; |
|||
|
|||
/// <summary>
|
|||
/// Pointer to <see cref="ComputationData.Unzig"/> as byte*
|
|||
/// </summary>
|
|||
public byte* Unzig; |
|||
|
|||
/// <summary>
|
|||
/// Pointer to <see cref="ComputationData.ScanData"/> as Scan*
|
|||
/// </summary>
|
|||
public GolangComponentScan* ComponentScan; |
|||
|
|||
/// <summary>
|
|||
/// Pointer to <see cref="ComputationData.Dc"/>
|
|||
/// </summary>
|
|||
public int* Dc; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DataPointers" /> struct.
|
|||
/// </summary>
|
|||
/// <param name="basePtr">The pointer pointing to <see cref="ComputationData"/></param>
|
|||
public DataPointers(ComputationData* basePtr) |
|||
{ |
|||
this.Block = &basePtr->Block; |
|||
this.Unzig = basePtr->Unzig.Data; |
|||
this.ComponentScan = (GolangComponentScan*)basePtr->ScanData; |
|||
this.Dc = basePtr->Dc; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,705 +0,0 @@ |
|||
// 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; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates the impementation of Jpeg SOS Huffman decoding. See JpegScanDecoder.md!
|
|||
///
|
|||
/// <see cref="zigStart"/> and <see cref="zigEnd"/> are the spectral selection bounds.
|
|||
/// <see cref="ah"/> and <see cref="al"/> are the successive approximation high and low values.
|
|||
/// The spec calls these values Ss, Se, Ah and Al.
|
|||
/// For progressive JPEGs, these are the two more-or-less independent
|
|||
/// aspects of progression. Spectral selection progression is when not
|
|||
/// all of a block's 64 DCT coefficients are transmitted in one pass.
|
|||
/// For example, three passes could transmit coefficient 0 (the DC
|
|||
/// component), coefficients 1-5, and coefficients 6-63, in zig-zag
|
|||
/// order. Successive approximation is when not all of the bits of a
|
|||
/// band of coefficients are transmitted in one pass. For example,
|
|||
/// three passes could transmit the 6 most significant bits, followed
|
|||
/// by the second-least significant bit, followed by the least
|
|||
/// significant bit.
|
|||
/// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal unsafe partial struct GolangJpegScanDecoder |
|||
{ |
|||
// The JpegScanDecoder members should be ordered in a way that results in optimal memory layout.
|
|||
#pragma warning disable SA1202 // ElementsMustBeOrderedByAccess
|
|||
|
|||
/// <summary>
|
|||
/// The <see cref="ComputationData"/> buffer
|
|||
/// </summary>
|
|||
private ComputationData data; |
|||
|
|||
/// <summary>
|
|||
/// Pointers to elements of <see cref="data"/>
|
|||
/// </summary>
|
|||
private DataPointers pointers; |
|||
|
|||
/// <summary>
|
|||
/// The current component index
|
|||
/// </summary>
|
|||
public int ComponentIndex; |
|||
|
|||
/// <summary>
|
|||
/// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
|
|||
/// </summary>
|
|||
private int bx; |
|||
|
|||
/// <summary>
|
|||
/// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
|
|||
/// </summary>
|
|||
private int by; |
|||
|
|||
/// <summary>
|
|||
/// Start index of the zig-zag selection bound
|
|||
/// </summary>
|
|||
private int zigStart; |
|||
|
|||
/// <summary>
|
|||
/// End index of the zig-zag selection bound
|
|||
/// </summary>
|
|||
private int zigEnd; |
|||
|
|||
/// <summary>
|
|||
/// Successive approximation high value
|
|||
/// </summary>
|
|||
private int ah; |
|||
|
|||
/// <summary>
|
|||
/// Successive approximation low value
|
|||
/// </summary>
|
|||
private int al; |
|||
|
|||
/// <summary>
|
|||
/// The number of component scans
|
|||
/// </summary>
|
|||
private int componentScanCount; |
|||
|
|||
/// <summary>
|
|||
/// Horizontal sampling factor at the current component index
|
|||
/// </summary>
|
|||
private int hi; |
|||
|
|||
/// <summary>
|
|||
/// End-of-Band run, specified in section G.1.2.2.
|
|||
/// </summary>
|
|||
private int eobRun; |
|||
|
|||
/// <summary>
|
|||
/// The block counter
|
|||
/// </summary>
|
|||
private int blockCounter; |
|||
|
|||
/// <summary>
|
|||
/// The MCU counter
|
|||
/// </summary>
|
|||
private int mcuCounter; |
|||
|
|||
/// <summary>
|
|||
/// The expected RST marker value
|
|||
/// </summary>
|
|||
private byte expectedRst; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a default-constructed <see cref="GolangJpegScanDecoder"/> instance for reading data from <see cref="GolangJpegDecoderCore"/>-s stream.
|
|||
/// </summary>
|
|||
/// <param name="p">Pointer to <see cref="GolangJpegScanDecoder"/> on the stack</param>
|
|||
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
public static void InitStreamReading(GolangJpegScanDecoder* p, GolangJpegDecoderCore decoder, int remaining) |
|||
{ |
|||
p->data = ComputationData.Create(); |
|||
p->pointers = new DataPointers(&p->data); |
|||
p->InitStreamReadingImpl(decoder, remaining); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Read Huffman data from Jpeg scans in <see cref="GolangJpegDecoderCore.InputStream"/>,
|
|||
/// and decode it as <see cref="Block8x8"/> into <see cref="GolangComponent.SpectralBlocks"/>.
|
|||
///
|
|||
/// The blocks are traversed one MCU at a time. For 4:2:0 chroma
|
|||
/// subsampling, there are four Y 8x8 blocks in every 16x16 MCU.
|
|||
/// For a baseline 32x16 pixel image, the Y blocks visiting order is:
|
|||
/// 0 1 4 5
|
|||
/// 2 3 6 7
|
|||
/// For progressive images, the interleaved scans (those with component count > 1)
|
|||
/// are traversed as above, but non-interleaved scans are traversed left
|
|||
/// to right, top to bottom:
|
|||
/// 0 1 2 3
|
|||
/// 4 5 6 7
|
|||
/// Only DC scans (zigStart == 0) can be interleave AC scans must have
|
|||
/// only one component.
|
|||
/// To further complicate matters, for non-interleaved scans, there is no
|
|||
/// data for any blocks that are inside the image at the MCU level but
|
|||
/// outside the image at the pixel level. For example, a 24x16 pixel 4:2:0
|
|||
/// progressive image consists of two 16x16 MCUs. The interleaved scans
|
|||
/// will process 8 Y blocks:
|
|||
/// 0 1 4 5
|
|||
/// 2 3 6 7
|
|||
/// The non-interleaved scans will process only 6 Y blocks:
|
|||
/// 0 1 2
|
|||
/// 3 4 5
|
|||
/// </summary>
|
|||
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
|
|||
public void DecodeBlocks(GolangJpegDecoderCore decoder) |
|||
{ |
|||
decoder.InputProcessor.ResetErrorState(); |
|||
|
|||
this.blockCounter = 0; |
|||
this.mcuCounter = 0; |
|||
this.expectedRst = JpegConstants.Markers.RST0; |
|||
|
|||
for (int my = 0; my < decoder.MCUCountY; my++) |
|||
{ |
|||
for (int mx = 0; mx < decoder.MCUCountX; mx++) |
|||
{ |
|||
this.DecodeBlocksAtMcuIndex(decoder, mx, my); |
|||
|
|||
this.mcuCounter++; |
|||
|
|||
// Handling restart intervals
|
|||
// Useful info: https://stackoverflow.com/a/8751802
|
|||
if (decoder.IsAtRestartInterval(this.mcuCounter)) |
|||
{ |
|||
this.ProcessRSTMarker(decoder); |
|||
this.Reset(decoder); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void DecodeBlocksAtMcuIndex(GolangJpegDecoderCore decoder, int mx, int my) |
|||
{ |
|||
for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) |
|||
{ |
|||
this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; |
|||
GolangComponent component = decoder.Components[this.ComponentIndex]; |
|||
|
|||
this.hi = component.HorizontalSamplingFactor; |
|||
int vi = component.VerticalSamplingFactor; |
|||
|
|||
for (int j = 0; j < this.hi * vi; j++) |
|||
{ |
|||
if (this.componentScanCount != 1) |
|||
{ |
|||
this.bx = (this.hi * mx) + (j % this.hi); |
|||
this.by = (vi * my) + (j / this.hi); |
|||
} |
|||
else |
|||
{ |
|||
int q = decoder.MCUCountX * this.hi; |
|||
this.bx = this.blockCounter % q; |
|||
this.by = this.blockCounter / q; |
|||
this.blockCounter++; |
|||
if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight) |
|||
{ |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
// Find the block at (bx,by) in the component's buffer:
|
|||
ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by); |
|||
|
|||
// Copy block to stack
|
|||
this.data.Block = blockRefOnHeap; |
|||
|
|||
if (!decoder.InputProcessor.ReachedEOF) |
|||
{ |
|||
this.DecodeBlock(decoder, scanIndex); |
|||
} |
|||
|
|||
// Store the result block:
|
|||
blockRefOnHeap = this.data.Block; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ProcessRSTMarker(GolangJpegDecoderCore decoder) |
|||
{ |
|||
// Attempt to look for RST[0-7] markers to resynchronize from corrupt input.
|
|||
if (!decoder.InputProcessor.ReachedEOF) |
|||
{ |
|||
decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); |
|||
if (decoder.InputProcessor.CheckEOFEnsureNoError()) |
|||
{ |
|||
if (decoder.Temp[0] != 0xFF || decoder.Temp[1] != this.expectedRst) |
|||
{ |
|||
bool invalidRst = true; |
|||
|
|||
// Most jpeg's containing well-formed input will have a RST[0-7] marker following immediately
|
|||
// but some, see Issue #481, contain padding bytes "0xFF" before the RST[0-7] marker.
|
|||
// If we identify that case we attempt to read until we have bypassed the padded bytes.
|
|||
// We then check again for our RST marker and throw if invalid.
|
|||
// No other methods are attempted to resynchronize from corrupt input.
|
|||
if (decoder.Temp[0] == 0xFF && decoder.Temp[1] == 0xFF) |
|||
{ |
|||
while (decoder.Temp[0] == 0xFF && decoder.InputProcessor.CheckEOFEnsureNoError()) |
|||
{ |
|||
decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 1); |
|||
if (!decoder.InputProcessor.CheckEOFEnsureNoError()) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Have we found a valid restart marker?
|
|||
invalidRst = decoder.Temp[0] != this.expectedRst; |
|||
} |
|||
|
|||
if (invalidRst) |
|||
{ |
|||
throw new ImageFormatException("Bad RST marker"); |
|||
} |
|||
} |
|||
|
|||
this.expectedRst++; |
|||
if (this.expectedRst == JpegConstants.Markers.RST7 + 1) |
|||
{ |
|||
this.expectedRst = JpegConstants.Markers.RST0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void Reset(GolangJpegDecoderCore decoder) |
|||
{ |
|||
decoder.InputProcessor.ResetHuffmanDecoder(); |
|||
|
|||
this.ResetDcValues(); |
|||
|
|||
// Reset the progressive decoder state, as per section G.1.2.2.
|
|||
this.eobRun = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset the DC components, as per section F.2.1.3.1.
|
|||
/// </summary>
|
|||
private void ResetDcValues() |
|||
{ |
|||
Unsafe.InitBlock(this.pointers.Dc, default, sizeof(int) * GolangJpegDecoderCore.MaxComponents); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The implementation part of <see cref="InitStreamReading"/> as an instance method.
|
|||
/// </summary>
|
|||
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/></param>
|
|||
/// <param name="remaining">The remaining bytes</param>
|
|||
private void InitStreamReadingImpl(GolangJpegDecoderCore decoder, int remaining) |
|||
{ |
|||
if (decoder.ComponentCount == 0) |
|||
{ |
|||
throw new ImageFormatException("Missing SOF marker"); |
|||
} |
|||
|
|||
if (remaining < 6 || 4 + (2 * decoder.ComponentCount) < remaining || remaining % 2 != 0) |
|||
{ |
|||
throw new ImageFormatException("SOS has wrong length"); |
|||
} |
|||
|
|||
decoder.InputProcessor.ReadFull(decoder.Temp, 0, remaining); |
|||
this.componentScanCount = decoder.Temp[0]; |
|||
|
|||
int scanComponentCountX2 = 2 * this.componentScanCount; |
|||
if (remaining != 4 + scanComponentCountX2) |
|||
{ |
|||
throw new ImageFormatException("SOS length inconsistent with number of components"); |
|||
} |
|||
|
|||
int totalHv = 0; |
|||
|
|||
for (int i = 0; i < this.componentScanCount; i++) |
|||
{ |
|||
this.InitComponentScan(decoder, i, ref this.pointers.ComponentScan[i], ref totalHv); |
|||
} |
|||
|
|||
// Section B.2.3 states that if there is more than one component then the
|
|||
// total H*V values in a scan must be <= 10.
|
|||
if (decoder.ComponentCount > 1 && totalHv > 10) |
|||
{ |
|||
throw new ImageFormatException("Total sampling factors too large."); |
|||
} |
|||
|
|||
this.zigEnd = Block8x8F.Size - 1; |
|||
|
|||
if (decoder.IsProgressive) |
|||
{ |
|||
this.zigStart = decoder.Temp[1 + scanComponentCountX2]; |
|||
this.zigEnd = decoder.Temp[2 + scanComponentCountX2]; |
|||
this.ah = decoder.Temp[3 + scanComponentCountX2] >> 4; |
|||
this.al = decoder.Temp[3 + scanComponentCountX2] & 0x0f; |
|||
|
|||
if ((this.zigStart == 0 && this.zigEnd != 0) || this.zigStart > this.zigEnd |
|||
|| this.zigEnd >= Block8x8F.Size) |
|||
{ |
|||
throw new ImageFormatException("Bad spectral selection bounds"); |
|||
} |
|||
|
|||
if (this.zigStart != 0 && this.componentScanCount != 1) |
|||
{ |
|||
throw new ImageFormatException("Progressive AC coefficients for more than one component"); |
|||
} |
|||
|
|||
if (this.ah != 0 && this.ah != this.al + 1) |
|||
{ |
|||
throw new ImageFormatException("Bad successive approximation values"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Read the current the current block at (<see cref="bx"/>, <see cref="by"/>) from the decoders stream
|
|||
/// </summary>
|
|||
/// <param name="decoder">The decoder</param>
|
|||
/// <param name="scanIndex">The index of the scan</param>
|
|||
private void DecodeBlock(GolangJpegDecoderCore decoder, int scanIndex) |
|||
{ |
|||
Block8x8* b = this.pointers.Block; |
|||
int huffmannIdx = (GolangHuffmanTree.AcTableIndex * GolangHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; |
|||
if (this.ah != 0) |
|||
{ |
|||
this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al); |
|||
} |
|||
else |
|||
{ |
|||
int zig = this.zigStart; |
|||
|
|||
if (zig == 0) |
|||
{ |
|||
zig++; |
|||
|
|||
// Decode the DC coefficient, as specified in section F.2.2.1.
|
|||
int huffmanIndex = (GolangHuffmanTree.DcTableIndex * GolangHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector; |
|||
decoder.InputProcessor.DecodeHuffmanUnsafe( |
|||
ref decoder.HuffmanTrees[huffmanIndex], |
|||
out int value); |
|||
if (!decoder.InputProcessor.CheckEOF()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (value > 16) |
|||
{ |
|||
throw new ImageFormatException("Excessive DC component"); |
|||
} |
|||
|
|||
decoder.InputProcessor.ReceiveExtendUnsafe(value, out int deltaDC); |
|||
if (!decoder.InputProcessor.CheckEOFEnsureNoError()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.pointers.Dc[this.ComponentIndex] += deltaDC; |
|||
|
|||
// b[0] = dc[compIndex] << al;
|
|||
value = this.pointers.Dc[this.ComponentIndex] << this.al; |
|||
Block8x8.SetScalarAt(b, 0, (short)value); |
|||
} |
|||
|
|||
if (zig <= this.zigEnd && this.eobRun > 0) |
|||
{ |
|||
this.eobRun--; |
|||
} |
|||
else |
|||
{ |
|||
// Decode the AC coefficients, as specified in section F.2.2.2.
|
|||
for (; zig <= this.zigEnd; zig++) |
|||
{ |
|||
decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out int value); |
|||
if (decoder.InputProcessor.HasError) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int val0 = value >> 4; |
|||
int val1 = value & 0x0f; |
|||
if (val1 != 0) |
|||
{ |
|||
zig += val0; |
|||
if (zig > this.zigEnd) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
decoder.InputProcessor.ReceiveExtendUnsafe(val1, out int ac); |
|||
if (decoder.InputProcessor.HasError) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// b[Unzig[zig]] = ac << al;
|
|||
value = ac << this.al; |
|||
Block8x8.SetScalarAt(b, this.pointers.Unzig[zig], (short)value); |
|||
} |
|||
else |
|||
{ |
|||
if (val0 != 0x0f) |
|||
{ |
|||
this.eobRun = (ushort)(1 << val0); |
|||
if (val0 != 0) |
|||
{ |
|||
this.DecodeEobRun(val0, ref decoder.InputProcessor); |
|||
if (!decoder.InputProcessor.CheckEOFEnsureNoError()) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
this.eobRun--; |
|||
break; |
|||
} |
|||
|
|||
zig += 0x0f; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void DecodeEobRun(int count, ref InputProcessor processor) |
|||
{ |
|||
processor.DecodeBitsUnsafe(count, out int bitsResult); |
|||
if (processor.LastErrorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.eobRun |= bitsResult; |
|||
} |
|||
|
|||
private void InitComponentScan(GolangJpegDecoderCore decoder, int i, ref GolangComponentScan currentComponentScan, ref int totalHv) |
|||
{ |
|||
// Component selector.
|
|||
int cs = decoder.Temp[1 + (2 * i)]; |
|||
int compIndex = -1; |
|||
for (int j = 0; j < decoder.ComponentCount; j++) |
|||
{ |
|||
// Component compv = ;
|
|||
if (cs == decoder.Components[j].Identifier) |
|||
{ |
|||
compIndex = j; |
|||
} |
|||
} |
|||
|
|||
if (compIndex < 0) |
|||
{ |
|||
throw new ImageFormatException("Unknown component selector"); |
|||
} |
|||
|
|||
currentComponentScan.ComponentIndex = (byte)compIndex; |
|||
|
|||
this.ProcessComponentImpl(decoder, i, ref currentComponentScan, ref totalHv, decoder.Components[compIndex]); |
|||
} |
|||
|
|||
private void ProcessComponentImpl( |
|||
GolangJpegDecoderCore decoder, |
|||
int i, |
|||
ref GolangComponentScan currentComponentScan, |
|||
ref int totalHv, |
|||
GolangComponent currentComponent) |
|||
{ |
|||
// Section B.2.3 states that "the value of Cs_j shall be different from
|
|||
// the values of Cs_1 through Cs_(j-1)". Since we have previously
|
|||
// verified that a frame's component identifiers (C_i values in section
|
|||
// B.2.2) are unique, it suffices to check that the implicit indexes
|
|||
// into comp are unique.
|
|||
for (int j = 0; j < i; j++) |
|||
{ |
|||
if (currentComponentScan.ComponentIndex == this.pointers.ComponentScan[j].ComponentIndex) |
|||
{ |
|||
throw new ImageFormatException("Repeated component selector"); |
|||
} |
|||
} |
|||
|
|||
totalHv += currentComponent.HorizontalSamplingFactor * currentComponent.VerticalSamplingFactor; |
|||
|
|||
currentComponentScan.DcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] >> 4); |
|||
if (currentComponentScan.DcTableSelector > GolangHuffmanTree.MaxTh) |
|||
{ |
|||
throw new ImageFormatException("Bad DC table selector value"); |
|||
} |
|||
|
|||
currentComponentScan.AcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] & 0x0f); |
|||
if (currentComponentScan.AcTableSelector > GolangHuffmanTree.MaxTh) |
|||
{ |
|||
throw new ImageFormatException("Bad AC table selector value"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
|
|||
/// </summary>
|
|||
/// <param name="bp">The <see cref="InputProcessor"/> instance</param>
|
|||
/// <param name="h">The Huffman tree</param>
|
|||
/// <param name="delta">The low transform offset</param>
|
|||
private void Refine(ref InputProcessor bp, ref GolangHuffmanTree h, int delta) |
|||
{ |
|||
Block8x8* b = this.pointers.Block; |
|||
|
|||
// Refining a DC component is trivial.
|
|||
if (this.zigStart == 0) |
|||
{ |
|||
if (this.zigEnd != 0) |
|||
{ |
|||
throw new ImageFormatException("Invalid state for zig DC component"); |
|||
} |
|||
|
|||
bp.DecodeBitUnsafe(out bool bit); |
|||
if (!bp.CheckEOFEnsureNoError()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (bit) |
|||
{ |
|||
int stuff = Block8x8.GetScalarAt(b, 0); |
|||
|
|||
// int stuff = (int)b[0];
|
|||
stuff |= delta; |
|||
|
|||
// b[0] = stuff;
|
|||
Block8x8.SetScalarAt(b, 0, (short)stuff); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
// Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3.
|
|||
int zig = this.zigStart; |
|||
if (this.eobRun == 0) |
|||
{ |
|||
for (; zig <= this.zigEnd; zig++) |
|||
{ |
|||
bool done = false; |
|||
int z = 0; |
|||
|
|||
bp.DecodeHuffmanUnsafe(ref h, out int val); |
|||
if (!bp.CheckEOF()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int val0 = val >> 4; |
|||
int val1 = val & 0x0f; |
|||
|
|||
switch (val1) |
|||
{ |
|||
case 0: |
|||
if (val0 != 0x0f) |
|||
{ |
|||
this.eobRun = 1 << val0; |
|||
if (val0 != 0) |
|||
{ |
|||
this.DecodeEobRun(val0, ref bp); |
|||
if (!bp.CheckEOFEnsureNoError()) |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
done = true; |
|||
} |
|||
|
|||
break; |
|||
case 1: |
|||
z = delta; |
|||
|
|||
bp.DecodeBitUnsafe(out bool bit); |
|||
if (!bp.CheckEOFEnsureNoError()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!bit) |
|||
{ |
|||
z = -z; |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
throw new ImageFormatException("Unexpected Huffman code"); |
|||
} |
|||
|
|||
if (done) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
zig = this.RefineNonZeroes(ref bp, zig, val0, delta); |
|||
|
|||
if (bp.ReachedEOF || bp.HasError) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (z != 0 && zig <= this.zigEnd) |
|||
{ |
|||
// b[Unzig[zig]] = z;
|
|||
Block8x8.SetScalarAt(b, this.pointers.Unzig[zig], (short)z); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (this.eobRun > 0) |
|||
{ |
|||
this.eobRun--; |
|||
this.RefineNonZeroes(ref bp, zig, -1, delta); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Refines non-zero entries of b in zig-zag order.
|
|||
/// If <paramref name="nz" /> >= 0, the first <paramref name="nz" /> zero entries are skipped over.
|
|||
/// </summary>
|
|||
/// <param name="bp">The <see cref="InputProcessor"/></param>
|
|||
/// <param name="zig">The zig-zag start index</param>
|
|||
/// <param name="nz">The non-zero entry</param>
|
|||
/// <param name="delta">The low transform offset</param>
|
|||
/// <returns>The <see cref="int" /></returns>
|
|||
private int RefineNonZeroes(ref InputProcessor bp, int zig, int nz, int delta) |
|||
{ |
|||
Block8x8* b = this.pointers.Block; |
|||
for (; zig <= this.zigEnd; zig++) |
|||
{ |
|||
int u = this.pointers.Unzig[zig]; |
|||
int bu = Block8x8.GetScalarAt(b, u); |
|||
|
|||
// TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary?
|
|||
if (bu == 0) |
|||
{ |
|||
if (nz == 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
nz--; |
|||
continue; |
|||
} |
|||
|
|||
bp.DecodeBitUnsafe(out bool bit); |
|||
if (bp.HasError) |
|||
{ |
|||
return int.MinValue; |
|||
} |
|||
|
|||
if (!bit) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int val = bu >= 0 ? bu + delta : bu - delta; |
|||
|
|||
Block8x8.SetScalarAt(b, u, (short)val); |
|||
} |
|||
|
|||
return zig; |
|||
} |
|||
} |
|||
} |
|||
@ -1,392 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// Encapsulates stream reading and processing data and operations for <see cref="GolangJpegDecoderCore"/>.
|
|||
/// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
|
|||
/// </summary>
|
|||
internal struct InputProcessor : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Holds the unprocessed bits that have been taken from the byte-stream.
|
|||
/// </summary>
|
|||
public Bits Bits; |
|||
|
|||
/// <summary>
|
|||
/// The byte buffer
|
|||
/// </summary>
|
|||
public Bytes Bytes; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="InputProcessor"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="inputStream">The input <see cref="Stream"/></param>
|
|||
/// <param name="temp">Temporal buffer, same as <see cref="GolangJpegDecoderCore.Temp"/></param>
|
|||
public InputProcessor(Stream inputStream, byte[] temp) |
|||
{ |
|||
this.Bits = default; |
|||
this.Bytes = Bytes.Create(); |
|||
this.InputStream = inputStream; |
|||
this.Temp = temp; |
|||
this.LastErrorCode = GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the input stream
|
|||
/// </summary>
|
|||
public Stream InputStream { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the temporary buffer, same instance as <see cref="GolangJpegDecoderCore.Temp"/>
|
|||
/// </summary>
|
|||
public byte[] Temp { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether an unexpected EOF reached in <see cref="InputStream"/>.
|
|||
/// </summary>
|
|||
public bool ReachedEOF => this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream; |
|||
|
|||
public bool HasError => this.LastErrorCode != GolangDecoderErrorCode.NoError; |
|||
|
|||
public GolangDecoderErrorCode LastErrorCode { get; private set; } |
|||
|
|||
public void ResetErrorState() => this.LastErrorCode = GolangDecoderErrorCode.NoError; |
|||
|
|||
/// <summary>
|
|||
/// If errorCode indicates unexpected EOF, sets <see cref="ReachedEOF"/> to true and returns false.
|
|||
/// Calls <see cref="DecoderThrowHelper.EnsureNoError"/> and returns true otherwise.
|
|||
/// </summary>
|
|||
/// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
|
|||
public bool CheckEOFEnsureNoError() |
|||
{ |
|||
if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.LastErrorCode.EnsureNoError(); |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// If errorCode indicates unexpected EOF, sets <see cref="ReachedEOF"/> to true and returns false.
|
|||
/// Returns true otherwise.
|
|||
/// </summary>
|
|||
/// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
|
|||
public bool CheckEOF() |
|||
{ |
|||
if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
this.Bytes.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="byte" /></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public byte ReadByte() |
|||
{ |
|||
return this.Bytes.ReadByte(this.InputStream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public GolangDecoderErrorCode ReadByteUnsafe(out byte result) |
|||
{ |
|||
this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result); |
|||
return this.LastErrorCode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a single bit
|
|||
/// TODO: This method (and also the usages) could be optimized by batching!
|
|||
/// </summary>
|
|||
/// <param name="result">The decoded bit as a <see cref="bool"/></param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode" /></returns>
|
|||
public GolangDecoderErrorCode DecodeBitUnsafe(out bool result) |
|||
{ |
|||
if (this.Bits.UnreadBits == 0) |
|||
{ |
|||
this.LastErrorCode = this.Bits.Ensure1BitUnsafe(ref this); |
|||
if (this.LastErrorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
result = false; |
|||
return this.LastErrorCode; |
|||
} |
|||
} |
|||
|
|||
result = (this.Bits.Accumulator & this.Bits.Mask) != 0; |
|||
this.Bits.UnreadBits--; |
|||
this.Bits.Mask >>= 1; |
|||
return this.LastErrorCode = GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads exactly length bytes into data. It does not care about byte stuffing.
|
|||
/// Does not throw on errors, returns <see cref="GolangJpegDecoderCore"/> instead!
|
|||
/// </summary>
|
|||
/// <param name="data">The data to write to.</param>
|
|||
/// <param name="offset">The offset in the source buffer</param>
|
|||
/// <param name="length">The number of bytes to read</param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length) |
|||
{ |
|||
// Unread the overshot bytes, if any.
|
|||
if (this.Bytes.UnreadableBytes != 0) |
|||
{ |
|||
if (this.Bits.UnreadBits >= 8) |
|||
{ |
|||
this.UnreadByteStuffedByte(); |
|||
} |
|||
|
|||
this.Bytes.UnreadableBytes = 0; |
|||
} |
|||
|
|||
this.LastErrorCode = GolangDecoderErrorCode.NoError; |
|||
while (length > 0 && this.LastErrorCode == GolangDecoderErrorCode.NoError) |
|||
{ |
|||
if (this.Bytes.J - this.Bytes.I >= length) |
|||
{ |
|||
Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, length); |
|||
this.Bytes.I += length; |
|||
length -= length; |
|||
} |
|||
else |
|||
{ |
|||
Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, this.Bytes.J - this.Bytes.I); |
|||
offset += this.Bytes.J - this.Bytes.I; |
|||
length -= this.Bytes.J - this.Bytes.I; |
|||
this.Bytes.I += this.Bytes.J - this.Bytes.I; |
|||
|
|||
this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream); |
|||
} |
|||
} |
|||
|
|||
return this.LastErrorCode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the given number of bits
|
|||
/// </summary>
|
|||
/// <param name="count">The number of bits to decode.</param>
|
|||
/// <param name="result">The <see cref="uint" /> result</param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode DecodeBitsUnsafe(int count, out int result) |
|||
{ |
|||
if (this.Bits.UnreadBits < count) |
|||
{ |
|||
this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(count, ref this); |
|||
if (this.LastErrorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
result = 0; |
|||
return this.LastErrorCode; |
|||
} |
|||
} |
|||
|
|||
result = this.Bits.Accumulator >> (this.Bits.UnreadBits - count); |
|||
result = result & ((1 << count) - 1); |
|||
this.Bits.UnreadBits -= count; |
|||
this.Bits.Mask >>= count; |
|||
return this.LastErrorCode = GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Extracts the next Huffman-coded value from the bit-stream into result, decoded according to the given value.
|
|||
/// </summary>
|
|||
/// <param name="huffmanTree">The huffman value</param>
|
|||
/// <param name="result">The decoded <see cref="byte" /></param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode DecodeHuffmanUnsafe(ref GolangHuffmanTree huffmanTree, out int result) |
|||
{ |
|||
result = 0; |
|||
|
|||
if (huffmanTree.Length == 0) |
|||
{ |
|||
DecoderThrowHelper.ThrowImageFormatException.UninitializedHuffmanTable(); |
|||
} |
|||
|
|||
if (this.Bits.UnreadBits < 8) |
|||
{ |
|||
this.LastErrorCode = this.Bits.Ensure8BitsUnsafe(ref this); |
|||
|
|||
if (this.LastErrorCode == GolangDecoderErrorCode.NoError) |
|||
{ |
|||
int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - GolangHuffmanTree.LutSizeLog2)) & 0xFF; |
|||
int v = huffmanTree.Lut[lutIndex]; |
|||
|
|||
if (v != 0) |
|||
{ |
|||
int n = (v & 0xFF) - 1; |
|||
this.Bits.UnreadBits -= n; |
|||
this.Bits.Mask >>= n; |
|||
result = v >> 8; |
|||
return this.LastErrorCode; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.UnreadByteStuffedByte(); |
|||
return this.LastErrorCode; |
|||
} |
|||
} |
|||
|
|||
int code = 0; |
|||
for (int i = 0; i < GolangHuffmanTree.MaxCodeLength; i++) |
|||
{ |
|||
if (this.Bits.UnreadBits == 0) |
|||
{ |
|||
this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(1, ref this); |
|||
|
|||
if (this.HasError) |
|||
{ |
|||
return this.LastErrorCode; |
|||
} |
|||
} |
|||
|
|||
if ((this.Bits.Accumulator & this.Bits.Mask) != 0) |
|||
{ |
|||
code |= 1; |
|||
} |
|||
|
|||
this.Bits.UnreadBits--; |
|||
this.Bits.Mask >>= 1; |
|||
|
|||
if (code <= huffmanTree.MaxCodes[i]) |
|||
{ |
|||
result = huffmanTree.GetValue(code, i); |
|||
return this.LastErrorCode = GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
code <<= 1; |
|||
} |
|||
|
|||
// Unrecoverable error, throwing:
|
|||
DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode(); |
|||
|
|||
// DUMMY RETURN! C# doesn't know we have thrown an exception!
|
|||
return GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skips the next n bytes.
|
|||
/// </summary>
|
|||
/// <param name="count">The number of bytes to ignore.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Skip(int count) |
|||
{ |
|||
this.LastErrorCode = this.SkipUnsafe(count); |
|||
this.LastErrorCode.EnsureNoError(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skips the next n bytes.
|
|||
/// Does not throw, returns <see cref="GolangDecoderErrorCode"/> instead!
|
|||
/// </summary>
|
|||
/// <param name="count">The number of bytes to ignore.</param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode SkipUnsafe(int count) |
|||
{ |
|||
// Unread the overshot bytes, if any.
|
|||
if (this.Bytes.UnreadableBytes != 0) |
|||
{ |
|||
if (this.Bits.UnreadBits >= 8) |
|||
{ |
|||
this.UnreadByteStuffedByte(); |
|||
} |
|||
|
|||
this.Bytes.UnreadableBytes = 0; |
|||
} |
|||
|
|||
while (true) |
|||
{ |
|||
int m = this.Bytes.J - this.Bytes.I; |
|||
if (m > count) |
|||
{ |
|||
m = count; |
|||
} |
|||
|
|||
this.Bytes.I += m; |
|||
count -= m; |
|||
if (count == 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream); |
|||
if (this.LastErrorCode != GolangDecoderErrorCode.NoError) |
|||
{ |
|||
return this.LastErrorCode; |
|||
} |
|||
} |
|||
|
|||
return this.LastErrorCode = GolangDecoderErrorCode.NoError; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads exactly length bytes into data. It does not care about byte stuffing.
|
|||
/// </summary>
|
|||
/// <param name="data">The data to write to.</param>
|
|||
/// <param name="offset">The offset in the source buffer</param>
|
|||
/// <param name="length">The number of bytes to read</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void ReadFull(byte[] data, int offset, int length) |
|||
{ |
|||
this.LastErrorCode = this.ReadFullUnsafe(data, offset, length); |
|||
this.LastErrorCode.EnsureNoError(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Undoes the most recent ReadByteStuffedByte call,
|
|||
/// giving a byte of data back from bits to bytes. The Huffman look-up table
|
|||
/// requires at least 8 bits for look-up, which means that Huffman decoding can
|
|||
/// sometimes overshoot and read one or two too many bytes. Two-byte overshoot
|
|||
/// can happen when expecting to read a 0xff 0x00 byte-stuffed byte.
|
|||
/// </summary>
|
|||
public void UnreadByteStuffedByte() |
|||
{ |
|||
this.Bytes.I -= this.Bytes.UnreadableBytes; |
|||
this.Bytes.UnreadableBytes = 0; |
|||
if (this.Bits.UnreadBits >= 8) |
|||
{ |
|||
this.Bits.Accumulator >>= 8; |
|||
this.Bits.UnreadBits -= 8; |
|||
this.Bits.Mask >>= 8; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Receive extend
|
|||
/// </summary>
|
|||
/// <param name="t">Byte</param>
|
|||
/// <param name="x">Read bits value</param>
|
|||
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
|
|||
public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, out int x) |
|||
{ |
|||
this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x); |
|||
return this.LastErrorCode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset the Huffman decoder.
|
|||
/// </summary>
|
|||
public void ResetHuffmanDecoder() |
|||
{ |
|||
this.Bits = default; |
|||
} |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
## JpegScanDecoder |
|||
Encapsulates the impementation of the Jpeg top-to bottom scan decoder triggered by the `SOS` marker. |
|||
The implementation is optimized to hold most of the necessary data in a single value type, which is intended to be used as an on-stack object. |
|||
|
|||
#### Benefits: |
|||
- Maximized locality of reference by keeping most of the operation data on the stack |
|||
- Achieving this without long parameter lists, most of the values describing the state of the decoder algorithm |
|||
are members of the `JpegScanDecoder` struct |
|||
- Most of the logic related to Scan decoding is refactored & simplified now to live in the methods of `JpegScanDecoder` |
|||
- The first step is done towards separating the stream reading from block processing. They can be refactored later to be executed in two disctinct loops. |
|||
- The input processing loop can be `async` |
|||
- The block processing loop can be parallelized |
|||
|
|||
#### Data layout |
|||
|
|||
|JpegScanDecoder | |
|||
|-------------------| |
|||
|Variables | |
|||
|DataPointers | |
|||
|ComputationData | |
|||
|
|||
- **ComputationData** holds the "large" data blocks needed for computations (Mostly `Block8x8F`-s) |
|||
- **DataPointers** contains pointers to the memory regions of `ComponentData` so they can be easily passed around to pointer based utility methods of `Block8x8F` |
|||
|
|||
|
|||
@ -1,15 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder |
|||
{ |
|||
/// <summary>
|
|||
/// The missing ff00 exception.
|
|||
/// </summary>
|
|||
// ReSharper disable once InconsistentNaming
|
|||
internal class MissingFF00Exception : Exception |
|||
{ |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a jpg stream.
|
|||
/// </summary>
|
|||
internal sealed class GolangJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
public bool IgnoreMetadata { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var decoder = new GolangJpegDecoderCore(configuration, this)) |
|||
{ |
|||
return decoder.Decode<TPixel>(stream); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(Configuration configuration, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var decoder = new GolangJpegDecoderCore(configuration, this)) |
|||
{ |
|||
return decoder.Identify(stream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,863 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; |
|||
using SixLabors.ImageSharp.MetaData; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Exif; |
|||
using SixLabors.ImageSharp.MetaData.Profiles.Icc; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort |
|||
{ |
|||
/// <inheritdoc />
|
|||
/// <summary>
|
|||
/// Performs the jpeg decoding operation.
|
|||
/// </summary>
|
|||
internal sealed unsafe class GolangJpegDecoderCore : IRawJpegData |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum number of color components
|
|||
/// </summary>
|
|||
public const int MaxComponents = 4; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of quantization tables
|
|||
/// </summary>
|
|||
public const int MaxTq = 3; |
|||
|
|||
/// <summary>
|
|||
/// The only supported precision
|
|||
/// </summary>
|
|||
public const int SupportedPrecision = 8; |
|||
|
|||
// Complex value type field + mutable + available to other classes = the field MUST NOT be private :P
|
|||
#pragma warning disable SA1401 // FieldsMustBePrivate
|
|||
|
|||
/// <summary>
|
|||
/// Encapsulates stream reading and processing data and operations for <see cref="GolangJpegDecoderCore"/>.
|
|||
/// It's a value type for improved data locality, and reduced number of CALLVIRT-s
|
|||
/// </summary>
|
|||
public InputProcessor InputProcessor; |
|||
#pragma warning restore SA401
|
|||
|
|||
/// <summary>
|
|||
/// The global configuration
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// Whether the image has a JFIF header
|
|||
/// It's faster to check this than to use the equality operator on the struct
|
|||
/// </summary>
|
|||
private bool isJFif; |
|||
|
|||
/// <summary>
|
|||
/// Contains information about the JFIF marker
|
|||
/// </summary>
|
|||
private JFifMarker jFif; |
|||
|
|||
/// <summary>
|
|||
/// Whether the image has a EXIF header
|
|||
/// </summary>
|
|||
private bool isExif; |
|||
|
|||
/// <summary>
|
|||
/// Contains exif data
|
|||
/// </summary>
|
|||
private byte[] exifData; |
|||
|
|||
/// <summary>
|
|||
/// Whether the image has an Adobe marker.
|
|||
/// It's faster to check this than to use the equality operator on the struct
|
|||
/// </summary>
|
|||
private bool isAdobe; |
|||
|
|||
/// <summary>
|
|||
/// Contains information about the Adobe marker
|
|||
/// </summary>
|
|||
private AdobeMarker adobe; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GolangJpegDecoderCore" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
public GolangJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) |
|||
{ |
|||
this.IgnoreMetadata = options.IgnoreMetadata; |
|||
this.configuration = configuration ?? Configuration.Default; |
|||
this.Temp = new byte[2 * Block8x8F.Size]; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public JpegColorSpace ColorSpace { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the component array
|
|||
/// </summary>
|
|||
public GolangComponent[] Components { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the huffman trees
|
|||
/// </summary>
|
|||
public GolangHuffmanTree[] HuffmanTrees { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public Block8x8F[] QuantizationTables { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the temporary buffer used to store bytes read from the stream.
|
|||
/// TODO: Should be stack allocated, fixed sized buffer!
|
|||
/// </summary>
|
|||
public byte[] Temp { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public Size ImageSizeInPixels { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
|
|||
/// </summary>
|
|||
public Size ImageSizeInMCU { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public int ComponentCount { get; private set; } |
|||
|
|||
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components; |
|||
|
|||
/// <summary>
|
|||
/// Gets the color depth, in number of bits per pixel.
|
|||
/// </summary>
|
|||
public int BitsPerPixel => this.ComponentCount * SupportedPrecision; |
|||
|
|||
/// <summary>
|
|||
/// Gets the image height
|
|||
/// </summary>
|
|||
public int ImageHeight => this.ImageSizeInPixels.Height; |
|||
|
|||
/// <summary>
|
|||
/// Gets the image width
|
|||
/// </summary>
|
|||
public int ImageWidth => this.ImageSizeInPixels.Width; |
|||
|
|||
/// <summary>
|
|||
/// Gets the input stream.
|
|||
/// </summary>
|
|||
public Stream InputStream { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the image is interlaced (progressive)
|
|||
/// </summary>
|
|||
public bool IsProgressive { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the restart interval
|
|||
/// </summary>
|
|||
public int RestartInterval { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the X axis
|
|||
/// </summary>
|
|||
public int MCUCountX => this.ImageSizeInMCU.Width; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the Y axis
|
|||
/// </summary>
|
|||
public int MCUCountY => this.ImageSizeInMCU.Height; |
|||
|
|||
/// <summary>
|
|||
/// Gets the total number of MCU-s (Minimum Coded Units) in the image.
|
|||
/// </summary>
|
|||
public int TotalMCUCount => this.MCUCountX * this.MCUCountY; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
public bool IgnoreMetadata { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="ImageMetaData"/> decoded by this decoder instance.
|
|||
/// </summary>
|
|||
public ImageMetaData MetaData { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Decodes the image from the specified <see cref="Stream"/> and sets
|
|||
/// the data to image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The stream, where the image should be.</param>
|
|||
/// <returns>The decoded image.</returns>
|
|||
public Image<TPixel> Decode<TPixel>(Stream stream) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
this.ParseStream(stream); |
|||
return this.PostProcessIntoImage<TPixel>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the raw image information from the specified stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|||
public IImageInfo Identify(Stream stream) |
|||
{ |
|||
this.ParseStream(stream, true); |
|||
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
if (this.Components != null) |
|||
{ |
|||
foreach (GolangComponent component in this.Components) |
|||
{ |
|||
component?.Dispose(); |
|||
} |
|||
} |
|||
|
|||
this.InputProcessor.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Read metadata from stream and read the blocks in the scans into <see cref="GolangComponent.SpectralBlocks"/>.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream</param>
|
|||
/// <param name="metadataOnly">Whether to decode metadata only.</param>
|
|||
public void ParseStream(Stream stream, bool metadataOnly = false) |
|||
{ |
|||
this.MetaData = new ImageMetaData(); |
|||
this.InputStream = stream; |
|||
this.InputProcessor = new InputProcessor(stream, this.Temp); |
|||
|
|||
if (!metadataOnly) |
|||
{ |
|||
this.HuffmanTrees = GolangHuffmanTree.CreateHuffmanTrees(); |
|||
this.QuantizationTables = new Block8x8F[MaxTq + 1]; |
|||
} |
|||
|
|||
// Check for the Start Of Image marker.
|
|||
this.InputProcessor.ReadFull(this.Temp, 0, 2); |
|||
|
|||
if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI) |
|||
{ |
|||
throw new ImageFormatException("Missing SOI marker."); |
|||
} |
|||
|
|||
// Process the remaining segments until the End Of Image marker.
|
|||
bool processBytes = true; |
|||
|
|||
// we can't currently short circute progressive images so don't try.
|
|||
while (processBytes) |
|||
{ |
|||
this.InputProcessor.ReadFull(this.Temp, 0, 2); |
|||
|
|||
if (this.InputProcessor.ReachedEOF) |
|||
{ |
|||
// We've reached the end of the stream.
|
|||
processBytes = false; |
|||
} |
|||
|
|||
while (this.Temp[0] != 0xff) |
|||
{ |
|||
// Strictly speaking, this is a format error. However, libjpeg is
|
|||
// liberal in what it accepts. As of version 9, next_marker in
|
|||
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
|
|||
// continues to decode the stream. Even before next_marker sees
|
|||
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
|
|||
// bytes as it can, possibly past the end of a scan's data. It
|
|||
// effectively puts back any markers that it overscanned (e.g. an
|
|||
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
|
|||
// and thus it can silently ignore a small number of extraneous
|
|||
// non-marker bytes before next_marker has a chance to see them (and
|
|||
// print a warning).
|
|||
// We are therefore also liberal in what we accept. Extraneous data
|
|||
// is silently ignore
|
|||
// This is similar to, but not exactly the same as, the restart
|
|||
// mechanism within a scan (the RST[0-7] markers).
|
|||
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
|
|||
// "\xff\x00", and so are detected a little further down below.
|
|||
this.Temp[0] = this.Temp[1]; |
|||
this.Temp[1] = this.InputProcessor.ReadByte(); |
|||
} |
|||
|
|||
byte marker = this.Temp[1]; |
|||
if (marker == 0) |
|||
{ |
|||
// Treat "\xff\x00" as extraneous data.
|
|||
continue; |
|||
} |
|||
|
|||
while (marker == 0xff) |
|||
{ |
|||
// Section B.1.1.2 says, "Any marker may optionally be preceded by any
|
|||
// number of fill bytes, which are bytes assigned code X'FF'".
|
|||
this.InputProcessor.ReadByteUnsafe(out marker); |
|||
|
|||
if (this.InputProcessor.ReachedEOF) |
|||
{ |
|||
// We've reached the end of the stream.
|
|||
processBytes = false; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// End Of Image.
|
|||
if (marker == JpegConstants.Markers.EOI) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) |
|||
{ |
|||
// Figures B.2 and B.16 of the specification suggest that restart markers should
|
|||
// only occur between Entropy Coded Segments and not after the final ECS.
|
|||
// However, some encoders may generate incorrect JPEGs with a final restart
|
|||
// marker. That restart marker will be seen here instead of inside the ProcessSOS
|
|||
// method, and is ignored as a harmless error. Restart markers have no extra data,
|
|||
// so we check for this before we read the 16-bit length of the segment.
|
|||
continue; |
|||
} |
|||
|
|||
// Read the 16-bit length of the segment. The value includes the 2 bytes for the
|
|||
// length itself, so we subtract 2 to get the number of remaining bytes.
|
|||
this.InputProcessor.ReadFullUnsafe(this.Temp, 0, 2); |
|||
int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; |
|||
if (remaining < 0) |
|||
{ |
|||
throw new ImageFormatException("Short segment length."); |
|||
} |
|||
|
|||
switch (marker) |
|||
{ |
|||
case JpegConstants.Markers.SOF0: |
|||
case JpegConstants.Markers.SOF1: |
|||
case JpegConstants.Markers.SOF2: |
|||
this.IsProgressive = marker == JpegConstants.Markers.SOF2; |
|||
this.ProcessStartOfFrameMarker(remaining, metadataOnly); |
|||
|
|||
break; |
|||
case JpegConstants.Markers.DHT: |
|||
|
|||
if (metadataOnly) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
} |
|||
else |
|||
{ |
|||
this.ProcessDefineHuffmanTablesMarker(remaining); |
|||
} |
|||
|
|||
break; |
|||
case JpegConstants.Markers.DQT: |
|||
if (metadataOnly) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
} |
|||
else |
|||
{ |
|||
this.ProcessDefineQuantizationTablesMarker(remaining); |
|||
} |
|||
|
|||
break; |
|||
case JpegConstants.Markers.SOS: |
|||
if (!metadataOnly) |
|||
{ |
|||
this.ProcessStartOfScanMarker(remaining); |
|||
if (this.InputProcessor.ReachedEOF) |
|||
{ |
|||
// If unexpected EOF reached. We can stop processing bytes as we now have the image data.
|
|||
processBytes = false; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// It's highly unlikely that APPn related data will be found after the SOS marker
|
|||
// We should have gathered everything we need by now.
|
|||
processBytes = false; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case JpegConstants.Markers.DRI: |
|||
if (metadataOnly) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
} |
|||
else |
|||
{ |
|||
this.ProcessDefineRestartIntervalMarker(remaining); |
|||
} |
|||
|
|||
break; |
|||
case JpegConstants.Markers.APP0: |
|||
this.ProcessApplicationHeaderMarker(remaining); |
|||
break; |
|||
case JpegConstants.Markers.APP1: |
|||
this.ProcessApp1Marker(remaining); |
|||
break; |
|||
case JpegConstants.Markers.APP2: |
|||
this.ProcessApp2Marker(remaining); |
|||
break; |
|||
case JpegConstants.Markers.APP14: |
|||
this.ProcessApp14Marker(remaining); |
|||
break; |
|||
default: |
|||
if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15) |
|||
|| marker == JpegConstants.Markers.COM) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
this.InitExifProfile(); |
|||
this.InitDerivedMetaDataProperties(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns true if 'mcuCounter' is at restart interval
|
|||
/// </summary>
|
|||
public bool IsAtRestartInterval(int mcuCounter) |
|||
{ |
|||
return this.RestartInterval > 0 && mcuCounter % this.RestartInterval == 0 |
|||
&& mcuCounter < this.TotalMCUCount; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes the exif profile.
|
|||
/// </summary>
|
|||
private void InitExifProfile() |
|||
{ |
|||
if (this.isExif) |
|||
{ |
|||
this.MetaData.ExifProfile = new ExifProfile(this.exifData); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header.
|
|||
/// </summary>
|
|||
private void InitDerivedMetaDataProperties() |
|||
{ |
|||
if (this.isJFif) |
|||
{ |
|||
this.MetaData.HorizontalResolution = this.jFif.XDensity; |
|||
this.MetaData.VerticalResolution = this.jFif.YDensity; |
|||
} |
|||
else if (this.isExif) |
|||
{ |
|||
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizonalTag) |
|||
? ((Rational)horizonalTag.Value).ToDouble() |
|||
: 0; |
|||
|
|||
double verticalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.YResolution, out ExifValue verticalTag) |
|||
? ((Rational)verticalTag.Value).ToDouble() |
|||
: 0; |
|||
|
|||
if (horizontalValue > 0 && verticalValue > 0) |
|||
{ |
|||
this.MetaData.HorizontalResolution = horizontalValue; |
|||
this.MetaData.VerticalResolution = verticalValue; |
|||
} |
|||
} |
|||
|
|||
if (this.MetaData.IccProfile?.CheckIsValid() == false) |
|||
{ |
|||
this.MetaData.IccProfile = null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the application header containing the JFIF identifier plus extra data.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessApplicationHeaderMarker(int remaining) |
|||
{ |
|||
if (remaining < 5) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
const int MarkerLength = JFifMarker.Length; |
|||
this.InputProcessor.ReadFull(this.Temp, 0, MarkerLength); |
|||
remaining -= MarkerLength; |
|||
|
|||
this.isJFif = JFifMarker.TryParse(this.Temp, out this.jFif); |
|||
|
|||
if (remaining > 0) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the App1 marker retrieving any stored metadata
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessApp1Marker(int remaining) |
|||
{ |
|||
if (remaining < 6 || this.IgnoreMetadata) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
byte[] profile = new byte[remaining]; |
|||
this.InputProcessor.ReadFull(profile, 0, remaining); |
|||
|
|||
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) |
|||
{ |
|||
this.isExif = true; |
|||
if (this.exifData == null) |
|||
{ |
|||
// the first 6 bytes (Exif00) will be skipped, because this is Jpeg specific
|
|||
this.exifData = profile.Skip(6).ToArray(); |
|||
} |
|||
else |
|||
{ |
|||
// if the exif information exceeds 64K, it will be split over multiple APP1 markers
|
|||
this.ExtendExif(profile.Skip(6).ToArray()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Extends the exif profile with additional data.
|
|||
/// </summary>
|
|||
/// <param name="bytes">The array containing addition profile data.</param>
|
|||
private void ExtendExif(byte[] bytes) |
|||
{ |
|||
int currentLength = this.exifData.Length; |
|||
|
|||
Array.Resize(ref this.exifData, currentLength + bytes.Length); |
|||
Buffer.BlockCopy(bytes, 0, this.exifData, currentLength, bytes.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the App2 marker retrieving any stored ICC profile information
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessApp2Marker(int remaining) |
|||
{ |
|||
// Length is 14 though we only need to check 12.
|
|||
const int Icclength = 14; |
|||
if (remaining < Icclength || this.IgnoreMetadata) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
byte[] identifier = new byte[Icclength]; |
|||
this.InputProcessor.ReadFull(identifier, 0, Icclength); |
|||
remaining -= Icclength; // We have read it by this point
|
|||
|
|||
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) |
|||
{ |
|||
byte[] profile = new byte[remaining]; |
|||
this.InputProcessor.ReadFull(profile, 0, remaining); |
|||
|
|||
if (this.MetaData.IccProfile == null) |
|||
{ |
|||
this.MetaData.IccProfile = new IccProfile(profile); |
|||
} |
|||
else |
|||
{ |
|||
this.MetaData.IccProfile.Extend(profile); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this.
|
|||
this.InputProcessor.Skip(remaining); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the application header containing the Adobe identifier
|
|||
/// which stores image encoding information for DCT filters.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessApp14Marker(int remaining) |
|||
{ |
|||
const int MarkerLength = AdobeMarker.Length; |
|||
if (remaining < MarkerLength) |
|||
{ |
|||
// Skip the application header length
|
|||
this.InputProcessor.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
this.InputProcessor.ReadFull(this.Temp, 0, MarkerLength); |
|||
remaining -= MarkerLength; |
|||
|
|||
this.isAdobe = AdobeMarker.TryParse(this.Temp, out this.adobe); |
|||
|
|||
if (remaining > 0) |
|||
{ |
|||
this.InputProcessor.Skip(remaining); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
/// <exception cref="ImageFormatException">
|
|||
/// Thrown if the tables do not match the header
|
|||
/// </exception>
|
|||
private void ProcessDefineQuantizationTablesMarker(int remaining) |
|||
{ |
|||
while (remaining > 0) |
|||
{ |
|||
bool done = false; |
|||
|
|||
remaining--; |
|||
byte x = this.InputProcessor.ReadByte(); |
|||
int tq = x & 0x0F; |
|||
if (tq > MaxTq) |
|||
{ |
|||
throw new ImageFormatException("Bad Tq value"); |
|||
} |
|||
|
|||
switch (x >> 4) |
|||
{ |
|||
case 0: |
|||
if (remaining < Block8x8F.Size) |
|||
{ |
|||
done = true; |
|||
break; |
|||
} |
|||
|
|||
remaining -= Block8x8F.Size; |
|||
this.InputProcessor.ReadFull(this.Temp, 0, Block8x8F.Size); |
|||
|
|||
for (int i = 0; i < Block8x8F.Size; i++) |
|||
{ |
|||
this.QuantizationTables[tq][i] = this.Temp[i]; |
|||
} |
|||
|
|||
break; |
|||
case 1: |
|||
if (remaining < 2 * Block8x8F.Size) |
|||
{ |
|||
done = true; |
|||
break; |
|||
} |
|||
|
|||
remaining -= 2 * Block8x8F.Size; |
|||
this.InputProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.Size); |
|||
|
|||
for (int i = 0; i < Block8x8F.Size; i++) |
|||
{ |
|||
this.QuantizationTables[tq][i] = (this.Temp[2 * i] << 8) | this.Temp[(2 * i) + 1]; |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
throw new ImageFormatException("Bad Pq value"); |
|||
} |
|||
|
|||
if (done) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (remaining != 0) |
|||
{ |
|||
throw new ImageFormatException("DQT has wrong length"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the Start of Frame marker. Specified in section B.2.2.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
/// <param name="metadataOnly">Whether to decode metadata only.</param>
|
|||
private void ProcessStartOfFrameMarker(int remaining, bool metadataOnly) |
|||
{ |
|||
if (this.ComponentCount != 0) |
|||
{ |
|||
throw new ImageFormatException("Multiple SOF markers"); |
|||
} |
|||
|
|||
switch (remaining) |
|||
{ |
|||
case 6 + (3 * 1): // grayscale image.
|
|||
this.ComponentCount = 1; |
|||
break; |
|||
case 6 + (3 * 3): // YCbCr or RGB image.
|
|||
this.ComponentCount = 3; |
|||
break; |
|||
case 6 + (3 * 4): // YCbCrK or CMYK image.
|
|||
this.ComponentCount = 4; |
|||
break; |
|||
default: |
|||
throw new ImageFormatException("Incorrect number of components"); |
|||
} |
|||
|
|||
this.InputProcessor.ReadFull(this.Temp, 0, remaining); |
|||
|
|||
// We only support 8-bit precision.
|
|||
if (this.Temp[0] != SupportedPrecision) |
|||
{ |
|||
throw new ImageFormatException("Only 8-Bit precision supported."); |
|||
} |
|||
|
|||
int height = (this.Temp[1] << 8) + this.Temp[2]; |
|||
int width = (this.Temp[3] << 8) + this.Temp[4]; |
|||
|
|||
this.ImageSizeInPixels = new Size(width, height); |
|||
|
|||
if (this.Temp[5] != this.ComponentCount) |
|||
{ |
|||
throw new ImageFormatException("SOF has wrong length"); |
|||
} |
|||
|
|||
if (!metadataOnly) |
|||
{ |
|||
this.Components = new GolangComponent[this.ComponentCount]; |
|||
|
|||
for (int i = 0; i < this.ComponentCount; i++) |
|||
{ |
|||
byte componentIdentifier = this.Temp[6 + (3 * i)]; |
|||
var component = new GolangComponent(componentIdentifier, i); |
|||
component.InitializeCoreData(this); |
|||
this.Components[i] = component; |
|||
} |
|||
|
|||
int h0 = this.Components[0].HorizontalSamplingFactor; |
|||
int v0 = this.Components[0].VerticalSamplingFactor; |
|||
|
|||
this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0); |
|||
|
|||
this.ColorSpace = this.DeduceJpegColorSpace(); |
|||
|
|||
foreach (GolangComponent component in this.Components) |
|||
{ |
|||
component.InitializeDerivedData(this.configuration.MemoryAllocator, this); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes a Define Huffman Table marker, and initializes a huffman
|
|||
/// struct from its contents. Specified in section B.2.4.2.
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessDefineHuffmanTablesMarker(int remaining) |
|||
{ |
|||
while (remaining > 0) |
|||
{ |
|||
if (remaining < 17) |
|||
{ |
|||
throw new ImageFormatException($"DHT has wrong length. {remaining}"); |
|||
} |
|||
|
|||
this.InputProcessor.ReadFull(this.Temp, 0, 17); |
|||
|
|||
int tc = this.Temp[0] >> 4; |
|||
if (tc > GolangHuffmanTree.MaxTc) |
|||
{ |
|||
throw new ImageFormatException("Bad Tc value"); |
|||
} |
|||
|
|||
int th = this.Temp[0] & 0x0f; |
|||
if (th > GolangHuffmanTree.MaxTh) |
|||
{ |
|||
throw new ImageFormatException("Bad Th value"); |
|||
} |
|||
|
|||
int huffTreeIndex = (tc * GolangHuffmanTree.ThRowSize) + th; |
|||
this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop( |
|||
ref this.InputProcessor, |
|||
this.Temp, |
|||
ref remaining); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
|
|||
/// macroblocks
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
private void ProcessDefineRestartIntervalMarker(int remaining) |
|||
{ |
|||
if (remaining != 2) |
|||
{ |
|||
throw new ImageFormatException("DRI has wrong length"); |
|||
} |
|||
|
|||
this.InputProcessor.ReadFull(this.Temp, 0, remaining); |
|||
this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the SOS (Start of scan marker).
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
/// <exception cref="ImageFormatException">
|
|||
/// Missing SOF Marker
|
|||
/// SOS has wrong length
|
|||
/// </exception>
|
|||
private void ProcessStartOfScanMarker(int remaining) |
|||
{ |
|||
GolangJpegScanDecoder scan = default; |
|||
GolangJpegScanDecoder.InitStreamReading(&scan, this, remaining); |
|||
this.InputProcessor.Bits = default; |
|||
scan.DecodeBlocks(this); |
|||
} |
|||
|
|||
private JpegColorSpace DeduceJpegColorSpace() |
|||
{ |
|||
switch (this.ComponentCount) |
|||
{ |
|||
case 1: |
|||
return JpegColorSpace.Grayscale; |
|||
case 3: |
|||
if (!this.isAdobe || this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr) |
|||
{ |
|||
return JpegColorSpace.YCbCr; |
|||
} |
|||
|
|||
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) |
|||
{ |
|||
return JpegColorSpace.RGB; |
|||
} |
|||
|
|||
break; |
|||
case 4: |
|||
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck) |
|||
{ |
|||
return JpegColorSpace.Ycck; |
|||
} |
|||
|
|||
return JpegColorSpace.Cmyk; |
|||
} |
|||
|
|||
throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}." |
|||
+ "JpegDecoder only supports YCbCr, RGB, YccK, CMYK and grayscale color spaces."); |
|||
} |
|||
|
|||
private Image<TPixel> PostProcessIntoImage<TPixel>() |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryAllocator, this)) |
|||
{ |
|||
var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); |
|||
postProcessor.PostProcess(image.Frames.RootFrame); |
|||
return image; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a jpg stream.
|
|||
/// </summary>
|
|||
internal sealed class PdfJsJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
public bool IgnoreMetadata { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var decoder = new PdfJsJpegDecoderCore(configuration, this)) |
|||
{ |
|||
return decoder.Decode<TPixel>(stream); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(Configuration configuration, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
using (var decoder = new PdfJsJpegDecoderCore(configuration, this)) |
|||
{ |
|||
return decoder.Identify(stream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +1,8 @@ |
|||
Encoder/Decoder adapted and extended from: |
|||
Encoder adapted and extended from: |
|||
https://golang.org/src/image/jpeg/ |
|||
|
|||
https://golang.org/src/image/jpeg/ |
|||
Decoder orchestration code is based on: |
|||
https://github.com/mozilla/pdf.js |
|||
|
|||
Huffmann decoder is based on: |
|||
https://github.com/rds1983/StbSharp |
|||
@ -1,34 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// A buffer implementation that consumes an existing <see cref="Memory{T}"/> instance.
|
|||
/// The ownership of the memory remains external.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type</typeparam>
|
|||
internal sealed class ConsumedBuffer<T> : IBuffer<T> |
|||
where T : struct |
|||
{ |
|||
public ConsumedBuffer(Memory<T> memory) |
|||
{ |
|||
this.Memory = memory; |
|||
} |
|||
|
|||
public Memory<T> Memory { get; } |
|||
|
|||
public bool IsMemoryOwner => false; |
|||
|
|||
public Span<T> GetSpan() |
|||
{ |
|||
return this.Memory.Span; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a contigous memory buffer of value-type items.
|
|||
/// Depending on it's implementation, an <see cref="IBuffer{T}"/> can (1) OWN or (2) CONSUME the <see cref="Memory{T}"/> instance it wraps.
|
|||
/// For a deeper understanding of the owner/consumer model, read the following docs: <br/>
|
|||
/// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6
|
|||
/// TODO: We need more SOC here! For owned buffers we should use <see cref="IMemoryOwner{T}"/>.
|
|||
/// For the consumption case we should not use buffers at all. We need to refactor Buffer2D{T} for this.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type</typeparam>
|
|||
internal interface IBuffer<T> : IDisposable |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the <see cref="Memory{T}"/> ownerd/consumed by this buffer.
|
|||
/// </summary>
|
|||
Memory<T> Memory { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this instance is owning the <see cref="Memory"/>.
|
|||
/// </summary>
|
|||
bool IsMemoryOwner { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the span to the memory "promised" by this buffer when it's OWNED (1).
|
|||
/// Gets `this.Memory.Span` when the buffer CONSUMED (2).
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Span{T}"/></returns>
|
|||
Span<T> GetSpan(); |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.Memory |
|||
{ |
|||
/// <summary>
|
|||
/// Holds a <see cref="System.Memory{T}"/> that is either OWNED or CONSUMED.
|
|||
/// When the memory is being owned, the <see cref="IMemoryOwner{T}"/> instance is also known.
|
|||
/// Implements content transfer logic in <see cref="SwapOrCopyContent"/> that depends on the ownership status.
|
|||
/// This is needed to transfer the contents of a temporary <see cref="Buffer2D{T}"/>
|
|||
/// to a persistent <see cref="SixLabors.ImageSharp.ImageFrame{T}.PixelBuffer"/> without copying the buffer.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// For a deeper understanding of the owner/consumer model, check out the following docs: <br/>
|
|||
/// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6
|
|||
/// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T
|
|||
/// </remarks>
|
|||
internal struct MemorySource<T> : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="MemorySource{T}"/> struct
|
|||
/// by wrapping an existing <see cref="IMemoryOwner{T}"/>.
|
|||
/// </summary>
|
|||
/// <param name="memoryOwner">The <see cref="IMemoryOwner{T}"/> to wrap</param>
|
|||
/// <param name="isInternalMemorySource">
|
|||
/// A value indicating whether <paramref name="memoryOwner"/> is an internal memory source managed by ImageSharp.
|
|||
/// Eg. allocated by a <see cref="MemoryAllocator"/>.
|
|||
/// </param>
|
|||
public MemorySource(IMemoryOwner<T> memoryOwner, bool isInternalMemorySource) |
|||
{ |
|||
this.MemoryOwner = memoryOwner; |
|||
this.Memory = memoryOwner.Memory; |
|||
this.HasSwappableContents = isInternalMemorySource; |
|||
} |
|||
|
|||
public MemorySource(Memory<T> memory) |
|||
{ |
|||
this.Memory = memory; |
|||
this.MemoryOwner = null; |
|||
this.HasSwappableContents = false; |
|||
} |
|||
|
|||
public IMemoryOwner<T> MemoryOwner { get; private set; } |
|||
|
|||
public Memory<T> Memory { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether we are allowed to swap the contents of this buffer
|
|||
/// with an other <see cref="MemorySource{T}"/> instance.
|
|||
/// The value is true only and only if <see cref="MemoryOwner"/> is present,
|
|||
/// and it's coming from an internal source managed by ImageSharp (<see cref="MemoryAllocator"/>).
|
|||
/// </summary>
|
|||
public bool HasSwappableContents { get; } |
|||
|
|||
public Span<T> GetSpan() => this.Memory.Span; |
|||
|
|||
public void Clear() => this.Memory.Span.Clear(); |
|||
|
|||
/// <summary>
|
|||
/// Swaps the contents of 'destination' with 'source' if the buffers are owned (1),
|
|||
/// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2!
|
|||
/// </summary>
|
|||
public static void SwapOrCopyContent(ref MemorySource<T> destination, ref MemorySource<T> source) |
|||
{ |
|||
if (source.HasSwappableContents && destination.HasSwappableContents) |
|||
{ |
|||
SwapContents(ref destination, ref source); |
|||
} |
|||
else |
|||
{ |
|||
if (destination.Memory.Length != source.Memory.Length) |
|||
{ |
|||
throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!"); |
|||
} |
|||
|
|||
source.Memory.CopyTo(destination.Memory); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
this.MemoryOwner?.Dispose(); |
|||
} |
|||
|
|||
private static void SwapContents(ref MemorySource<T> a, ref MemorySource<T> b) |
|||
{ |
|||
IMemoryOwner<T> tempOwner = a.MemoryOwner; |
|||
Memory<T> tempMemory = a.Memory; |
|||
|
|||
a.MemoryOwner = b.MemoryOwner; |
|||
a.Memory = b.Memory; |
|||
|
|||
b.MemoryOwner = tempOwner; |
|||
b.Memory = tempMemory; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue