mirror of https://github.com/SixLabors/ImageSharp
16 changed files with 3832 additions and 0 deletions
@ -0,0 +1,74 @@ |
|||
// <copyright file="Adobe.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Provides information about the Adobe marker segment
|
|||
/// </summary>
|
|||
internal struct Adobe : IEquatable<Adobe> |
|||
{ |
|||
/// <summary>
|
|||
/// The DCT Encode Version
|
|||
/// </summary>
|
|||
public short DCTEncodeVersion; |
|||
|
|||
/// <summary>
|
|||
/// 0x0 : (none)
|
|||
/// Bit 15 : Encoded with Blend=1 downsampling
|
|||
/// </summary>
|
|||
public short APP14Flags0; |
|||
|
|||
/// <summary>
|
|||
/// 0x0 : (none)
|
|||
/// </summary>
|
|||
public short APP14Flags1; |
|||
|
|||
/// <summary>
|
|||
/// Determines the colorspace transform
|
|||
/// 00 : Unknown (RGB or CMYK)
|
|||
/// 01 : YCbCr
|
|||
/// 02 : YCCK
|
|||
/// </summary>
|
|||
public byte ColorTransform; |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(Adobe other) |
|||
{ |
|||
return this.DCTEncodeVersion == other.DCTEncodeVersion |
|||
&& this.APP14Flags0 == other.APP14Flags0 |
|||
&& this.APP14Flags1 == other.APP14Flags1 |
|||
&& this.ColorTransform == other.ColorTransform; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return obj is Adobe && this.Equals((Adobe)obj); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
// TODO: Merge and use HashCodeHelpers
|
|||
int hashCode = this.DCTEncodeVersion.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.APP14Flags0.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.APP14Flags1.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.ColorTransform.GetHashCode(); |
|||
return hashCode; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// <copyright file="Component.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
using System.Numerics; |
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Represents a component block
|
|||
/// </summary>
|
|||
internal struct Component : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the output
|
|||
/// </summary>
|
|||
public Buffer<short> Output; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the scaling factors
|
|||
/// </summary>
|
|||
public Vector2 Scale; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of blocks per line
|
|||
/// </summary>
|
|||
public int BlocksPerLine; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of blocks per column
|
|||
/// </summary>
|
|||
public int BlocksPerColumn; |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.Output?.Dispose(); |
|||
this.Output = null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// <copyright file="Components.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Contains all the decoded component blocks
|
|||
/// </summary>
|
|||
internal sealed class ComponentBlocks : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the component blocks
|
|||
/// </summary>
|
|||
public Component[] Components { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
if (this.Components != null) |
|||
{ |
|||
for (int i = 0; i < this.Components.Length; i++) |
|||
{ |
|||
this.Components[i].Dispose(); |
|||
} |
|||
|
|||
this.Components = null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// <copyright file="HuffmanTables.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a jpeg file marker
|
|||
/// </summary>
|
|||
internal struct FileMarker |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="FileMarker"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="marker">The marker</param>
|
|||
/// <param name="position">The position within the stream</param>
|
|||
public FileMarker(ushort marker, long position) |
|||
{ |
|||
this.Marker = marker; |
|||
this.Position = position; |
|||
this.Invalid = false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="FileMarker"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="marker">The marker</param>
|
|||
/// <param name="position">The position within the stream</param>
|
|||
/// <param name="invalid">Whether the current marker is invalid</param>
|
|||
public FileMarker(ushort marker, long position, bool invalid) |
|||
{ |
|||
this.Marker = marker; |
|||
this.Position = position; |
|||
this.Invalid = invalid; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the current marker is invalid
|
|||
/// </summary>
|
|||
public bool Invalid { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the position of the marker within a stream
|
|||
/// </summary>
|
|||
public ushort Marker { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the position of the marker within a stream
|
|||
/// </summary>
|
|||
public long Position { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
return this.Marker.ToString("X"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
// <copyright file="QuantizationTables.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Represent a single jpeg frame
|
|||
/// </summary>
|
|||
internal sealed class Frame : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the frame uses the extended specification
|
|||
/// </summary>
|
|||
public bool Extended { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the frame uses the progressive specification
|
|||
/// </summary>
|
|||
public bool Progressive { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the precision
|
|||
/// </summary>
|
|||
public byte Precision { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of scanlines within the frame
|
|||
/// </summary>
|
|||
public short Scanlines { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of samples per scanline
|
|||
/// </summary>
|
|||
public short SamplesPerLine { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4
|
|||
/// </summary>
|
|||
public byte ComponentCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the component id collection
|
|||
/// </summary>
|
|||
public byte[] ComponentIds { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the frame component collection
|
|||
/// </summary>
|
|||
public FrameComponent[] Components { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum horizontal sampling factor
|
|||
/// </summary>
|
|||
public int MaxHorizontalFactor { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum vertical sampling factor
|
|||
/// </summary>
|
|||
public int MaxVerticalFactor { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of MCU's per line
|
|||
/// </summary>
|
|||
public int McusPerLine { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of MCU's per column
|
|||
/// </summary>
|
|||
public int McusPerColumn { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
if (this.Components != null) |
|||
{ |
|||
for (int i = 0; i < this.Components.Length; i++) |
|||
{ |
|||
this.Components[i].Dispose(); |
|||
} |
|||
|
|||
this.Components = null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
// <copyright file="FrameComponent.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
|
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Represents a single frame component
|
|||
/// </summary>
|
|||
internal struct FrameComponent : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the component Id
|
|||
/// </summary>
|
|||
public byte Id; |
|||
|
|||
/// <summary>
|
|||
/// TODO: What does pred stand for?
|
|||
/// </summary>
|
|||
public int Pred; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the horizontal sampling factor.
|
|||
/// </summary>
|
|||
public int HorizontalFactor; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the vertical sampling factor.
|
|||
/// </summary>
|
|||
public int VerticalFactor; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the identifier
|
|||
/// </summary>
|
|||
public byte QuantizationIdentifier; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the block data
|
|||
/// </summary>
|
|||
public Buffer<short> BlockData; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of blocks per line
|
|||
/// </summary>
|
|||
public int BlocksPerLine; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of blocks per column
|
|||
/// </summary>
|
|||
public int BlocksPerColumn; |
|||
|
|||
/// <summary>
|
|||
/// Gets the index for the DC Huffman table
|
|||
/// </summary>
|
|||
public int DCHuffmanTableId; |
|||
|
|||
/// <summary>
|
|||
/// Gets the index for the AC Huffman table
|
|||
/// </summary>
|
|||
public int ACHuffmanTableId; |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.BlockData?.Dispose(); |
|||
this.BlockData = null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,212 @@ |
|||
// <copyright file="HuffmanTable.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Represents a Huffman Table
|
|||
/// </summary>
|
|||
internal struct HuffmanTable : IDisposable |
|||
{ |
|||
private Buffer<short> lookahead; |
|||
private Buffer<short> valOffset; |
|||
private Buffer<long> maxcode; |
|||
private Buffer<byte> huffval; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="lengths">The code lengths</param>
|
|||
/// <param name="values">The huffman values</param>
|
|||
public HuffmanTable(byte[] lengths, byte[] values) |
|||
{ |
|||
this.lookahead = Buffer<short>.CreateClean(256); |
|||
this.valOffset = Buffer<short>.CreateClean(18); |
|||
this.maxcode = Buffer<long>.CreateClean(18); |
|||
|
|||
using (var huffsize = Buffer<short>.CreateClean(257)) |
|||
using (var huffcode = Buffer<short>.CreateClean(257)) |
|||
{ |
|||
GenerateSizeTable(lengths, huffsize); |
|||
GenerateCodeTable(huffsize, huffcode); |
|||
GenerateDecoderTables(lengths, huffcode, this.valOffset, this.maxcode); |
|||
GenerateLookaheadTables(lengths, values, this.lookahead); |
|||
} |
|||
|
|||
this.huffval = Buffer<byte>.CreateClean(values.Length); |
|||
Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length); |
|||
|
|||
this.MaxCode = this.maxcode.Array; |
|||
this.ValOffset = this.valOffset.Array; |
|||
this.HuffVal = this.huffval.Array; |
|||
this.Lookahead = this.lookahead.Array; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the max code array
|
|||
/// </summary>
|
|||
public long[] MaxCode |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value offset array
|
|||
/// </summary>
|
|||
public short[] ValOffset |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the huffman value array
|
|||
/// </summary>
|
|||
public byte[] HuffVal |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lookahead array
|
|||
/// </summary>
|
|||
public short[] Lookahead |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.lookahead?.Dispose(); |
|||
this.valOffset?.Dispose(); |
|||
this.maxcode?.Dispose(); |
|||
this.huffval?.Dispose(); |
|||
|
|||
this.lookahead = null; |
|||
this.valOffset = null; |
|||
this.maxcode = null; |
|||
this.huffval = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Figure C.1: make table of Huffman code length for each symbol
|
|||
/// </summary>
|
|||
/// <param name="lengths">The code lengths</param>
|
|||
/// <param name="huffsize">The huffman size span</param>
|
|||
private static void GenerateSizeTable(byte[] lengths, Span<short> huffsize) |
|||
{ |
|||
short index = 0; |
|||
for (short l = 1; l <= 16; l++) |
|||
{ |
|||
byte i = lengths[l]; |
|||
for (short j = 0; j < i; j++) |
|||
{ |
|||
huffsize[index] = l; |
|||
index++; |
|||
} |
|||
} |
|||
|
|||
huffsize[index] = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Figure C.2: generate the codes themselves
|
|||
/// </summary>
|
|||
/// <param name="huffsize">The huffman size span</param>
|
|||
/// <param name="huffcode">The huffman code span</param>
|
|||
private static void GenerateCodeTable(Span<short> huffsize, Span<short> huffcode) |
|||
{ |
|||
short k = 0; |
|||
short si = huffsize[0]; |
|||
short code = 0; |
|||
for (short i = 0; i < huffsize.Length; i++) |
|||
{ |
|||
while (huffsize[k] == si) |
|||
{ |
|||
huffcode[k] = code; |
|||
code++; |
|||
k++; |
|||
} |
|||
|
|||
code <<= 1; |
|||
si++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Figure F.15: generate decoding tables for bit-sequential decoding
|
|||
/// </summary>
|
|||
/// <param name="lengths">The code lengths</param>
|
|||
/// <param name="huffcode">The huffman code span</param>
|
|||
/// <param name="valOffset">The value offset span</param>
|
|||
/// <param name="maxcode">The max code span</param>
|
|||
private static void GenerateDecoderTables(byte[] lengths, Span<short> huffcode, Span<short> valOffset, Span<long> maxcode) |
|||
{ |
|||
short bitcount = 0; |
|||
for (int i = 1; i <= 16; i++) |
|||
{ |
|||
if (lengths[i] != 0) |
|||
{ |
|||
// valoffset[l] = huffval[] index of 1st symbol of code length i,
|
|||
// minus the minimum code of length i
|
|||
valOffset[i] = (short)(bitcount - huffcode[bitcount]); |
|||
bitcount += lengths[i]; |
|||
maxcode[i] = huffcode[bitcount - 1]; // maximum code of length i
|
|||
} |
|||
else |
|||
{ |
|||
maxcode[i] = -1; // -1 if no codes of this length
|
|||
} |
|||
} |
|||
|
|||
valOffset[17] = 0; |
|||
maxcode[17] = 0xFFFFFL; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Generates lookup tables to speed up decoding
|
|||
/// </summary>
|
|||
/// <param name="lengths">The code lengths</param>
|
|||
/// <param name="huffval">The huffman value array</param>
|
|||
/// <param name="lookahead">The lookahead span</param>
|
|||
private static void GenerateLookaheadTables(byte[] lengths, byte[] huffval, Span<short> lookahead) |
|||
{ |
|||
int x = 0, code = 0; |
|||
|
|||
for (int i = 0; i < 8; i++) |
|||
{ |
|||
code <<= 1; |
|||
|
|||
for (int j = 0; j < lengths[i + 1]; 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.
|
|||
byte base2 = (byte)(code << (7 - i)); |
|||
short lutValue = (short)((short)(huffval[x] << 8) | (short)(2 + i)); |
|||
|
|||
for (int k = 0; k < 1 << (7 - i); k++) |
|||
{ |
|||
lookahead[base2 | k] = lutValue; |
|||
} |
|||
|
|||
code++; |
|||
x++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// <copyright file="HuffmanTables.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <summary>
|
|||
/// Defines a pair of huffman tables
|
|||
/// </summary>
|
|||
internal sealed class HuffmanTables : IDisposable |
|||
{ |
|||
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="List{HuffmanBranch}"/></returns>
|
|||
public ref HuffmanTable this[int index] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
return ref this.tables[index]; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
for (int i = 0; i < this.tables.Length; i++) |
|||
{ |
|||
this.tables[i].Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,511 @@ |
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Performs the inverse Descrete Cosine Transform on each frame component.
|
|||
/// </summary>
|
|||
internal static class IDCT |
|||
{ |
|||
/// <summary>
|
|||
/// Precomputed values scaled up by 14 bits
|
|||
/// </summary>
|
|||
public static readonly short[] Aanscales = |
|||
{ |
|||
16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 22725, 31521, 29692, 26722, 22725, 17855, |
|||
12299, 6270, 21407, 29692, 27969, 25172, 21407, 16819, 11585, |
|||
5906, 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, |
|||
16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, 12873, |
|||
17855, 16819, 15137, 12873, 10114, 6967, 3552, 8867, 12299, |
|||
11585, 10426, 8867, 6967, 4799, 2446, 4520, 6270, 5906, 5315, |
|||
4520, 3552, 2446, 1247 |
|||
}; |
|||
|
|||
private const int DctCos1 = 4017; // cos(pi/16)
|
|||
private const int DctSin1 = 799; // sin(pi/16)
|
|||
private const int DctCos3 = 3406; // cos(3*pi/16)
|
|||
private const int DctSin3 = 2276; // sin(3*pi/16)
|
|||
private const int DctCos6 = 1567; // cos(6*pi/16)
|
|||
private const int DctSin6 = 3784; // sin(6*pi/16)
|
|||
private const int DctSqrt2 = 5793; // sqrt(2)
|
|||
private const int DctSqrt1D2 = 2896; // sqrt(2) / 2
|
|||
|
|||
#pragma warning disable SA1310 // Field names must not contain underscore
|
|||
private const int FIX_1_082392200 = 277; // FIX(1.082392200)
|
|||
private const int FIX_1_414213562 = 362; // FIX(1.414213562)
|
|||
private const int FIX_1_847759065 = 473; // FIX(1.847759065)
|
|||
private const int FIX_2_613125930 = 669; // FIX(2.613125930)
|
|||
#pragma warning restore SA1310 // Field names must not contain underscore
|
|||
|
|||
private const int ConstBits = 8; |
|||
private const int Pass1Bits = 2; // Factional bits in scale factors
|
|||
private const int MaxJSample = 255; |
|||
private const int CenterJSample = 128; |
|||
private const int RangeCenter = (MaxJSample * 2) + 2; |
|||
|
|||
// First segment of range limit table: limit[x] = 0 for x < 0
|
|||
// allow negative subscripts of simple table
|
|||
private const int TableOffset = 2 * (MaxJSample + 1); |
|||
private const int LimitOffset = TableOffset - (RangeCenter - CenterJSample); |
|||
|
|||
// Each IDCT routine is responsible for range-limiting its results and
|
|||
// converting them to unsigned form (0..MaxJSample). The raw outputs could
|
|||
// be quite far out of range if the input data is corrupt, so a bulletproof
|
|||
// range-limiting step is required. We use a mask-and-table-lookup method
|
|||
// to do the combined operations quickly, assuming that MaxJSample+1
|
|||
// is a power of 2.
|
|||
private const int RangeMask = (MaxJSample * 4) + 3; // 2 bits wider than legal samples
|
|||
|
|||
private static readonly byte[] Limit = new byte[5 * (MaxJSample + 1)]; |
|||
|
|||
static IDCT() |
|||
{ |
|||
// Main part of range limit table: limit[x] = x
|
|||
int i; |
|||
for (i = 0; i <= MaxJSample; i++) |
|||
{ |
|||
Limit[TableOffset + i] = (byte)i; |
|||
} |
|||
|
|||
// End of range limit table: Limit[x] = MaxJSample for x > MaxJSample
|
|||
for (; i < 3 * (MaxJSample + 1); i++) |
|||
{ |
|||
Limit[TableOffset + i] = MaxJSample; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A port of Poppler's IDCT method which in turn is taken from:
|
|||
/// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
|
|||
/// 'Practical Fast 1-D DCT Algorithms with 11 Multiplications',
|
|||
/// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, 988-991.
|
|||
/// </summary>
|
|||
/// <param name="component">The fram component</param>
|
|||
/// <param name="blockBufferOffset">The block buffer offset</param>
|
|||
/// <param name="computationBuffer">The computational buffer for holding temp values</param>
|
|||
/// <param name="quantizationTable">The quantization table</param>
|
|||
public static void QuantizeAndInverse(ref FrameComponent component, int blockBufferOffset, ref Span<short> computationBuffer, ref Span<short> quantizationTable) |
|||
{ |
|||
Span<short> blockData = component.BlockData.Slice(blockBufferOffset); |
|||
int v0, v1, v2, v3, v4, v5, v6, v7; |
|||
int p0, p1, p2, p3, p4, p5, p6, p7; |
|||
int t; |
|||
|
|||
// inverse DCT on rows
|
|||
for (int row = 0; row < 64; row += 8) |
|||
{ |
|||
// gather block data
|
|||
p0 = blockData[row]; |
|||
p1 = blockData[row + 1]; |
|||
p2 = blockData[row + 2]; |
|||
p3 = blockData[row + 3]; |
|||
p4 = blockData[row + 4]; |
|||
p5 = blockData[row + 5]; |
|||
p6 = blockData[row + 6]; |
|||
p7 = blockData[row + 7]; |
|||
|
|||
// dequant p0
|
|||
p0 *= quantizationTable[row]; |
|||
|
|||
// check for all-zero AC coefficients
|
|||
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) |
|||
{ |
|||
t = ((DctSqrt2 * p0) + 512) >> 10; |
|||
short st = (short)t; |
|||
computationBuffer[row] = st; |
|||
computationBuffer[row + 1] = st; |
|||
computationBuffer[row + 2] = st; |
|||
computationBuffer[row + 3] = st; |
|||
computationBuffer[row + 4] = st; |
|||
computationBuffer[row + 5] = st; |
|||
computationBuffer[row + 6] = st; |
|||
computationBuffer[row + 7] = st; |
|||
continue; |
|||
} |
|||
|
|||
// dequant p1 ... p7
|
|||
p1 *= quantizationTable[row + 1]; |
|||
p2 *= quantizationTable[row + 2]; |
|||
p3 *= quantizationTable[row + 3]; |
|||
p4 *= quantizationTable[row + 4]; |
|||
p5 *= quantizationTable[row + 5]; |
|||
p6 *= quantizationTable[row + 6]; |
|||
p7 *= quantizationTable[row + 7]; |
|||
|
|||
// stage 4
|
|||
v0 = ((DctSqrt2 * p0) + 128) >> 8; |
|||
v1 = ((DctSqrt2 * p4) + 128) >> 8; |
|||
v2 = p2; |
|||
v3 = p6; |
|||
v4 = ((DctSqrt1D2 * (p1 - p7)) + 128) >> 8; |
|||
v7 = ((DctSqrt1D2 * (p1 + p7)) + 128) >> 8; |
|||
v5 = p3 << 4; |
|||
v6 = p5 << 4; |
|||
|
|||
// stage 3
|
|||
v0 = (v0 + v1 + 1) >> 1; |
|||
v1 = v0 - v1; |
|||
t = ((v2 * DctSin6) + (v3 * DctCos6) + 128) >> 8; |
|||
v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 128) >> 8; |
|||
v3 = t; |
|||
v4 = (v4 + v6 + 1) >> 1; |
|||
v6 = v4 - v6; |
|||
v7 = (v7 + v5 + 1) >> 1; |
|||
v5 = v7 - v5; |
|||
|
|||
// stage 2
|
|||
v0 = (v0 + v3 + 1) >> 1; |
|||
v3 = v0 - v3; |
|||
v1 = (v1 + v2 + 1) >> 1; |
|||
v2 = v1 - v2; |
|||
t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12; |
|||
v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12; |
|||
v7 = t; |
|||
t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12; |
|||
v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12; |
|||
v6 = t; |
|||
|
|||
// stage 1
|
|||
computationBuffer[row] = (short)(v0 + v7); |
|||
computationBuffer[row + 7] = (short)(v0 - v7); |
|||
computationBuffer[row + 1] = (short)(v1 + v6); |
|||
computationBuffer[row + 6] = (short)(v1 - v6); |
|||
computationBuffer[row + 2] = (short)(v2 + v5); |
|||
computationBuffer[row + 5] = (short)(v2 - v5); |
|||
computationBuffer[row + 3] = (short)(v3 + v4); |
|||
computationBuffer[row + 4] = (short)(v3 - v4); |
|||
} |
|||
|
|||
// inverse DCT on columns
|
|||
for (int col = 0; col < 8; ++col) |
|||
{ |
|||
p0 = computationBuffer[col]; |
|||
p1 = computationBuffer[col + 8]; |
|||
p2 = computationBuffer[col + 16]; |
|||
p3 = computationBuffer[col + 24]; |
|||
p4 = computationBuffer[col + 32]; |
|||
p5 = computationBuffer[col + 40]; |
|||
p6 = computationBuffer[col + 48]; |
|||
p7 = computationBuffer[col + 56]; |
|||
|
|||
// check for all-zero AC coefficients
|
|||
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) |
|||
{ |
|||
t = ((DctSqrt2 * p0) + 8192) >> 14; |
|||
|
|||
// convert to 8 bit
|
|||
t = (t < -2040) ? 0 : (t >= 2024) ? MaxJSample : (t + 2056) >> 4; |
|||
short st = (short)t; |
|||
|
|||
blockData[col] = st; |
|||
blockData[col + 8] = st; |
|||
blockData[col + 16] = st; |
|||
blockData[col + 24] = st; |
|||
blockData[col + 32] = st; |
|||
blockData[col + 40] = st; |
|||
blockData[col + 48] = st; |
|||
blockData[col + 56] = st; |
|||
continue; |
|||
} |
|||
|
|||
// stage 4
|
|||
v0 = ((DctSqrt2 * p0) + 2048) >> 12; |
|||
v1 = ((DctSqrt2 * p4) + 2048) >> 12; |
|||
v2 = p2; |
|||
v3 = p6; |
|||
v4 = ((DctSqrt1D2 * (p1 - p7)) + 2048) >> 12; |
|||
v7 = ((DctSqrt1D2 * (p1 + p7)) + 2048) >> 12; |
|||
v5 = p3; |
|||
v6 = p5; |
|||
|
|||
// stage 3
|
|||
// Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when
|
|||
// converting to UInt8 range later.
|
|||
v0 = ((v0 + v1 + 1) >> 1) + 4112; |
|||
v1 = v0 - v1; |
|||
t = ((v2 * DctSin6) + (v3 * DctCos6) + 2048) >> 12; |
|||
v2 = ((v2 * DctCos6) - (v3 * DctSin6) + 2048) >> 12; |
|||
v3 = t; |
|||
v4 = (v4 + v6 + 1) >> 1; |
|||
v6 = v4 - v6; |
|||
v7 = (v7 + v5 + 1) >> 1; |
|||
v5 = v7 - v5; |
|||
|
|||
// stage 2
|
|||
v0 = (v0 + v3 + 1) >> 1; |
|||
v3 = v0 - v3; |
|||
v1 = (v1 + v2 + 1) >> 1; |
|||
v2 = v1 - v2; |
|||
t = ((v4 * DctSin3) + (v7 * DctCos3) + 2048) >> 12; |
|||
v4 = ((v4 * DctCos3) - (v7 * DctSin3) + 2048) >> 12; |
|||
v7 = t; |
|||
t = ((v5 * DctSin1) + (v6 * DctCos1) + 2048) >> 12; |
|||
v5 = ((v5 * DctCos1) - (v6 * DctSin1) + 2048) >> 12; |
|||
v6 = t; |
|||
|
|||
// stage 1
|
|||
p0 = v0 + v7; |
|||
p7 = v0 - v7; |
|||
p1 = v1 + v6; |
|||
p6 = v1 - v6; |
|||
p2 = v2 + v5; |
|||
p5 = v2 - v5; |
|||
p3 = v3 + v4; |
|||
p4 = v3 - v4; |
|||
|
|||
// convert to 8-bit integers
|
|||
p0 = (p0 < 16) ? 0 : (p0 >= 4080) ? MaxJSample : p0 >> 4; |
|||
p1 = (p1 < 16) ? 0 : (p1 >= 4080) ? MaxJSample : p1 >> 4; |
|||
p2 = (p2 < 16) ? 0 : (p2 >= 4080) ? MaxJSample : p2 >> 4; |
|||
p3 = (p3 < 16) ? 0 : (p3 >= 4080) ? MaxJSample : p3 >> 4; |
|||
p4 = (p4 < 16) ? 0 : (p4 >= 4080) ? MaxJSample : p4 >> 4; |
|||
p5 = (p5 < 16) ? 0 : (p5 >= 4080) ? MaxJSample : p5 >> 4; |
|||
p6 = (p6 < 16) ? 0 : (p6 >= 4080) ? MaxJSample : p6 >> 4; |
|||
p7 = (p7 < 16) ? 0 : (p7 >= 4080) ? MaxJSample : p7 >> 4; |
|||
|
|||
// store block data
|
|||
blockData[col] = (short)p0; |
|||
blockData[col + 8] = (short)p1; |
|||
blockData[col + 16] = (short)p2; |
|||
blockData[col + 24] = (short)p3; |
|||
blockData[col + 32] = (short)p4; |
|||
blockData[col + 40] = (short)p5; |
|||
blockData[col + 48] = (short)p6; |
|||
blockData[col + 56] = (short)p7; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A port of <see href="https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/jidctfst.c#L171"/>
|
|||
/// A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT
|
|||
/// on each row(or vice versa, but it's more convenient to emit a row at
|
|||
/// a time). Direct algorithms are also available, but they are much more
|
|||
/// complex and seem not to be any faster when reduced to code.
|
|||
///
|
|||
/// This implementation is based on Arai, Agui, and Nakajima's algorithm for
|
|||
/// scaled DCT.Their original paper (Trans.IEICE E-71(11):1095) is in
|
|||
/// Japanese, but the algorithm is described in the Pennebaker & Mitchell
|
|||
/// JPEG textbook(see REFERENCES section in file README.ijg). The following
|
|||
/// code is based directly on figure 4-8 in P&M.
|
|||
/// While an 8-point DCT cannot be done in less than 11 multiplies, it is
|
|||
/// possible to arrange the computation so that many of the multiplies are
|
|||
/// simple scalings of the final outputs.These multiplies can then be
|
|||
/// folded into the multiplications or divisions by the JPEG quantization
|
|||
/// table entries. The AA&N method leaves only 5 multiplies and 29 adds
|
|||
/// to be done in the DCT itself.
|
|||
/// The primary disadvantage of this method is that with fixed-point math,
|
|||
/// accuracy is lost due to imprecise representation of the scaled
|
|||
/// quantization values.The smaller the quantization table entry, the less
|
|||
/// precise the scaled value, so this implementation does worse with high -
|
|||
/// quality - setting files than with low - quality ones.
|
|||
/// </summary>
|
|||
/// <param name="component">The frame component</param>
|
|||
/// <param name="blockBufferOffset">The block buffer offset</param>
|
|||
/// <param name="computationBuffer">The computational buffer for holding temp values</param>
|
|||
/// <param name="multiplierTable">The multiplier table</param>
|
|||
public static void QuantizeAndInverseFast(ref FrameComponent component, int blockBufferOffset, ref Span<short> computationBuffer, ref Span<short> multiplierTable) |
|||
{ |
|||
Span<short> blockData = component.BlockData.Slice(blockBufferOffset); |
|||
int p0, p1, p2, p3, p4, p5, p6, p7; |
|||
|
|||
for (int col = 0; col < 8; col++) |
|||
{ |
|||
// Gather block data
|
|||
p0 = blockData[col]; |
|||
p1 = blockData[col + 8]; |
|||
p2 = blockData[col + 16]; |
|||
p3 = blockData[col + 24]; |
|||
p4 = blockData[col + 32]; |
|||
p5 = blockData[col + 40]; |
|||
p6 = blockData[col + 48]; |
|||
p7 = blockData[col + 56]; |
|||
|
|||
int tmp0 = p0 * multiplierTable[col]; |
|||
|
|||
// Due to quantization, we will usually find that many of the input
|
|||
// coefficients are zero, especially the AC terms. We can exploit this
|
|||
// by short-circuiting the IDCT calculation for any column in which all
|
|||
// the AC terms are zero. In that case each output is equal to the
|
|||
// DC coefficient (with scale factor as needed).
|
|||
// With typical images and quantization tables, half or more of the
|
|||
// column DCT calculations can be simplified this way.
|
|||
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) |
|||
{ |
|||
short dcval = (short)tmp0; |
|||
|
|||
computationBuffer[col] = dcval; |
|||
computationBuffer[col + 8] = dcval; |
|||
computationBuffer[col + 16] = dcval; |
|||
computationBuffer[col + 24] = dcval; |
|||
computationBuffer[col + 32] = dcval; |
|||
computationBuffer[col + 40] = dcval; |
|||
computationBuffer[col + 48] = dcval; |
|||
computationBuffer[col + 56] = dcval; |
|||
|
|||
continue; |
|||
} |
|||
|
|||
// Even part
|
|||
int tmp1 = p2 * multiplierTable[col + 16]; |
|||
int tmp2 = p4 * multiplierTable[col + 32]; |
|||
int tmp3 = p6 * multiplierTable[col + 48]; |
|||
|
|||
int tmp10 = tmp0 + tmp2; // Phase 3
|
|||
int tmp11 = tmp0 - tmp2; |
|||
|
|||
int tmp13 = tmp1 + tmp3; // Phases 5-3
|
|||
int tmp12 = Multiply(tmp1 - tmp3, FIX_1_414213562) - tmp13; // 2*c4
|
|||
|
|||
tmp0 = tmp10 + tmp13; // Phase 2
|
|||
tmp3 = tmp10 - tmp13; |
|||
tmp1 = tmp11 + tmp12; |
|||
tmp2 = tmp11 - tmp12; |
|||
|
|||
// Odd Part
|
|||
int tmp4 = p1 * multiplierTable[col + 8]; |
|||
int tmp5 = p3 * multiplierTable[col + 24]; |
|||
int tmp6 = p5 * multiplierTable[col + 40]; |
|||
int tmp7 = p7 * multiplierTable[col + 56]; |
|||
|
|||
int z13 = tmp6 + tmp5; // Phase 6
|
|||
int z10 = tmp6 - tmp5; |
|||
int z11 = tmp4 + tmp7; |
|||
int z12 = tmp4 - tmp7; |
|||
|
|||
tmp7 = z11 + z13; // Phase 5
|
|||
tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4
|
|||
|
|||
int z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2
|
|||
tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6)
|
|||
tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6)
|
|||
|
|||
tmp6 = tmp12 - tmp7; // Phase 2
|
|||
tmp5 = tmp11 - tmp6; |
|||
tmp4 = tmp10 - tmp5; |
|||
|
|||
computationBuffer[col] = (short)(tmp0 + tmp7); |
|||
computationBuffer[col + 56] = (short)(tmp0 - tmp7); |
|||
computationBuffer[col + 8] = (short)(tmp1 + tmp6); |
|||
computationBuffer[col + 48] = (short)(tmp1 - tmp6); |
|||
computationBuffer[col + 16] = (short)(tmp2 + tmp5); |
|||
computationBuffer[col + 40] = (short)(tmp2 - tmp5); |
|||
computationBuffer[col + 24] = (short)(tmp3 + tmp4); |
|||
computationBuffer[col + 32] = (short)(tmp3 - tmp4); |
|||
} |
|||
|
|||
// Pass 2: process rows from work array, store into output array.
|
|||
// Note that we must descale the results by a factor of 8 == 2**3,
|
|||
// and also undo the pass 1 bits scaling.
|
|||
for (int row = 0; row < 64; row += 8) |
|||
{ |
|||
p1 = computationBuffer[row + 1]; |
|||
p2 = computationBuffer[row + 2]; |
|||
p3 = computationBuffer[row + 3]; |
|||
p4 = computationBuffer[row + 4]; |
|||
p5 = computationBuffer[row + 5]; |
|||
p6 = computationBuffer[row + 6]; |
|||
p7 = computationBuffer[row + 7]; |
|||
|
|||
// Add range center and fudge factor for final descale and range-limit.
|
|||
int z5 = computationBuffer[row] + (RangeCenter << (Pass1Bits + 3)) + (1 << (Pass1Bits + 2)); |
|||
|
|||
// Check for all-zero AC coefficients
|
|||
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) == 0) |
|||
{ |
|||
byte dcval = Limit[LimitOffset + (RightShift(z5, Pass1Bits + 3) & RangeMask)]; |
|||
|
|||
blockData[row] = dcval; |
|||
blockData[row + 1] = dcval; |
|||
blockData[row + 2] = dcval; |
|||
blockData[row + 3] = dcval; |
|||
blockData[row + 4] = dcval; |
|||
blockData[row + 5] = dcval; |
|||
blockData[row + 6] = dcval; |
|||
blockData[row + 7] = dcval; |
|||
|
|||
continue; |
|||
} |
|||
|
|||
// Even part
|
|||
int tmp10 = z5 + p4; |
|||
int tmp11 = z5 - p4; |
|||
|
|||
int tmp13 = p2 + p6; |
|||
int tmp12 = Multiply(p2 - p6, FIX_1_414213562) - tmp13; // 2*c4
|
|||
|
|||
int tmp0 = tmp10 + tmp13; |
|||
int tmp3 = tmp10 - tmp13; |
|||
int tmp1 = tmp11 + tmp12; |
|||
int tmp2 = tmp11 - tmp12; |
|||
|
|||
// Odd part
|
|||
int z13 = p5 + p3; |
|||
int z10 = p5 - p3; |
|||
int z11 = p1 + p7; |
|||
int z12 = p1 - p7; |
|||
|
|||
int tmp7 = z11 + z13; // Phase 5
|
|||
tmp11 = Multiply(z11 - z13, FIX_1_414213562); // 2*c4
|
|||
|
|||
z5 = Multiply(z10 + z12, FIX_1_847759065); // 2*c2
|
|||
tmp10 = z5 - Multiply(z12, FIX_1_082392200); // 2*(c2-c6)
|
|||
tmp12 = z5 - Multiply(z10, FIX_2_613125930); // 2*(c2+c6)
|
|||
|
|||
int tmp6 = tmp12 - tmp7; // Phase 2
|
|||
int tmp5 = tmp11 - tmp6; |
|||
int tmp4 = tmp10 - tmp5; |
|||
|
|||
// Final output stage: scale down by a factor of 8, offset, and range-limit
|
|||
blockData[row] = Limit[LimitOffset + (RightShift(tmp0 + tmp7, Pass1Bits + 3) & RangeMask)]; |
|||
blockData[row + 7] = Limit[LimitOffset + (RightShift(tmp0 - tmp7, Pass1Bits + 3) & RangeMask)]; |
|||
blockData[row + 1] = Limit[LimitOffset + (RightShift(tmp1 + tmp6, Pass1Bits + 3) & RangeMask)]; |
|||
blockData[row + 6] = Limit[LimitOffset + (RightShift(tmp1 - tmp6, Pass1Bits + 3) & RangeMask)]; |
|||
blockData[row + 2] = Limit[LimitOffset + (RightShift(tmp2 + tmp5, Pass1Bits + 3) & RangeMask)]; |
|||
blockData[row + 5] = Limit[LimitOffset + (RightShift(tmp2 - tmp5, Pass1Bits + 3) & RangeMask)]; |
|||
blockData[row + 3] = Limit[LimitOffset + (RightShift(tmp3 + tmp4, Pass1Bits + 3) & RangeMask)]; |
|||
blockData[row + 4] = Limit[LimitOffset + (RightShift(tmp3 - tmp4, Pass1Bits + 3) & RangeMask)]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Descale and correctly round an int value that's scaled by <paramref name="n"/> bits.
|
|||
/// We assume <see cref="RightShift"/> rounds towards minus infinity, so adding
|
|||
/// the fudge factor is correct for either sign of <paramref name="value"/>.
|
|||
/// </summary>
|
|||
/// <param name="value">The value</param>
|
|||
/// <param name="n">The number of bits</param>
|
|||
/// <returns>The <see cref="int"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Descale(int value, int n) |
|||
{ |
|||
return RightShift(value + (1 << (n - 1)), n); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Multiply a variable by an int constant, and immediately descale.
|
|||
/// </summary>
|
|||
/// <param name="val">The value</param>
|
|||
/// <param name="c">The multiplier</param>
|
|||
/// <returns>The <see cref="int"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Multiply(int val, int c) |
|||
{ |
|||
return Descale(val * c, ConstBits); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Right-shifts the value by the given amount
|
|||
/// </summary>
|
|||
/// <param name="value">The value</param>
|
|||
/// <param name="shift">The amount to shift by</param>
|
|||
/// <returns>The <see cref="int"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int RightShift(int value, int shift) |
|||
{ |
|||
return value >> shift; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// <copyright file="JFif.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// Provides information about the JFIF marker segment
|
|||
/// TODO: Thumbnail?
|
|||
/// </summary>
|
|||
internal struct JFif : IEquatable<JFif> |
|||
{ |
|||
/// <summary>
|
|||
/// The major version
|
|||
/// </summary>
|
|||
public byte MajorVersion; |
|||
|
|||
/// <summary>
|
|||
/// The minor version
|
|||
/// </summary>
|
|||
public byte MinorVersion; |
|||
|
|||
/// <summary>
|
|||
/// Units for the following pixel density fields
|
|||
/// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity
|
|||
/// 01 : Pixels per inch (2.54 cm)
|
|||
/// 02 : Pixels per centimeter
|
|||
/// </summary>
|
|||
public byte DensityUnits; |
|||
|
|||
/// <summary>
|
|||
/// Horizontal pixel density. Must not be zero.
|
|||
/// </summary>
|
|||
public short XDensity; |
|||
|
|||
/// <summary>
|
|||
/// Vertical pixel density. Must not be zero.
|
|||
/// </summary>
|
|||
public short YDensity; |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(JFif other) |
|||
{ |
|||
return this.MajorVersion == other.MajorVersion |
|||
&& this.MinorVersion == other.MinorVersion |
|||
&& this.DensityUnits == other.DensityUnits |
|||
&& this.XDensity == other.XDensity |
|||
&& this.YDensity == other.YDensity; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return obj is JFif && this.Equals((JFif)obj); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hashCode = this.MajorVersion.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.MinorVersion.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.DensityUnits.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.XDensity.GetHashCode(); |
|||
hashCode = (hashCode * 397) ^ this.YDensity.GetHashCode(); |
|||
return hashCode; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
// <copyright file="JpegPixelArea.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Represents a section of the jpeg component data laid out in pixel order.
|
|||
/// </summary>
|
|||
internal struct JpegPixelArea : IDisposable |
|||
{ |
|||
private readonly int imageWidth; |
|||
|
|||
private readonly int imageHeight; |
|||
|
|||
private Buffer<byte> componentData; |
|||
|
|||
private int rowStride; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegPixelArea"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="imageWidth">The image width</param>
|
|||
/// <param name="imageHeight">The image height</param>
|
|||
/// <param name="numberOfComponents">The number of components</param>
|
|||
public JpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) |
|||
{ |
|||
this.imageWidth = imageWidth; |
|||
this.imageHeight = imageHeight; |
|||
this.Width = 0; |
|||
this.Height = 0; |
|||
this.NumberOfComponents = numberOfComponents; |
|||
this.componentData = null; |
|||
this.rowStride = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of components
|
|||
/// </summary>
|
|||
public int NumberOfComponents { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the width
|
|||
/// </summary>
|
|||
public int Width { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height
|
|||
/// </summary>
|
|||
public int Height { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Organsizes the decoded jpeg components into a linear array ordered by component.
|
|||
/// This must be called before attempting to retrieve the data.
|
|||
/// </summary>
|
|||
/// <param name="components">The jpeg component blocks</param>
|
|||
/// <param name="width">The pixel area width</param>
|
|||
/// <param name="height">The pixel area height</param>
|
|||
public void LinearizeBlockData(ComponentBlocks components, int width, int height) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
int numberOfComponents = this.NumberOfComponents; |
|||
this.rowStride = width * numberOfComponents; |
|||
var scale = new Vector2(this.imageWidth / (float)width, this.imageHeight / (float)height); |
|||
|
|||
this.componentData = new Buffer<byte>(width * height * numberOfComponents); |
|||
Span<byte> componentDataSpan = this.componentData; |
|||
const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs
|
|||
|
|||
using (var xScaleBlockOffset = new Buffer<int>(width)) |
|||
{ |
|||
Span<int> xScaleBlockOffsetSpan = xScaleBlockOffset; |
|||
for (int i = 0; i < numberOfComponents; i++) |
|||
{ |
|||
ref Component component = ref components.Components[i]; |
|||
Vector2 componentScale = component.Scale * scale; |
|||
int offset = i; |
|||
Span<short> output = component.Output; |
|||
int blocksPerScanline = (component.BlocksPerLine + 1) << 3; |
|||
|
|||
// Precalculate the xScaleBlockOffset
|
|||
int j; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
j = (int)(x * componentScale.X); |
|||
xScaleBlockOffsetSpan[x] = (int)((j & Mask3Lsb) << 3) | (j & 7); |
|||
} |
|||
|
|||
// Linearize the blocks of the component
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
j = (int)(y * componentScale.Y); |
|||
int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
componentDataSpan[offset] = (byte)output[index + xScaleBlockOffsetSpan[x]]; |
|||
offset += numberOfComponents; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a <see cref="Span{Byte}"/> representing the row 'y' beginning from the the first byte on that row.
|
|||
/// </summary>
|
|||
/// <param name="y">The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the pixel area.</param>
|
|||
/// <returns>The <see cref="Span{TPixel}"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Span<byte> GetRowSpan(int y) |
|||
{ |
|||
this.CheckCoordinates(y); |
|||
return this.componentData.Slice(y * this.rowStride, this.rowStride); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.componentData?.Dispose(); |
|||
this.componentData = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks the coordinates to ensure they are within bounds.
|
|||
/// </summary>
|
|||
/// <param name="y">The y-coordinate of the row. Must be greater than zero and less than the height of the area.</param>
|
|||
/// <exception cref="ArgumentOutOfRangeException">
|
|||
/// Thrown if the coordinates are not within the bounds of the image.
|
|||
/// </exception>
|
|||
[Conditional("DEBUG")] |
|||
private void CheckCoordinates(int y) |
|||
{ |
|||
if (y < 0 || y >= this.Height) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the area bounds."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
// <copyright file="QuantizationTables.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Contains the quantization tables.
|
|||
/// </summary>
|
|||
internal sealed class QuantizationTables : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the ZigZag scan table
|
|||
/// </summary>
|
|||
public static byte[] DctZigZag |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get; |
|||
} |
|||
|
|||
= |
|||
{ |
|||
0, |
|||
1, 8, |
|||
16, 9, 2, |
|||
3, 10, 17, 24, |
|||
32, 25, 18, 11, 4, |
|||
5, 12, 19, 26, 33, 40, |
|||
48, 41, 34, 27, 20, 13, 6, |
|||
7, 14, 21, 28, 35, 42, 49, 56, |
|||
57, 50, 43, 36, 29, 22, 15, |
|||
23, 30, 37, 44, 51, 58, |
|||
59, 52, 45, 38, 31, |
|||
39, 46, 53, 60, |
|||
61, 54, 47, |
|||
55, 62, |
|||
63 |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the quantization tables.
|
|||
/// </summary>
|
|||
public Buffer2D<short> Tables |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get; set; |
|||
} |
|||
|
|||
= new Buffer2D<short>(64, 4); |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
if (this.Tables != null) |
|||
{ |
|||
this.Tables.Dispose(); |
|||
this.Tables = null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,947 @@ |
|||
// <copyright file="ScanDecoder.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
#if DEBUG
|
|||
using System.Diagnostics; |
|||
#endif
|
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <summary>
|
|||
/// Provides the means to decode a spectral scan
|
|||
/// </summary>
|
|||
internal struct ScanDecoder |
|||
{ |
|||
private byte[] markerBuffer; |
|||
|
|||
private int bitsData; |
|||
|
|||
private int bitsCount; |
|||
|
|||
#pragma warning disable 414
|
|||
private int bitsUnRead; |
|||
|
|||
private int accumulator; |
|||
#pragma warning restore 414
|
|||
|
|||
private int specStart; |
|||
|
|||
private int specEnd; |
|||
|
|||
private int eobrun; |
|||
|
|||
private int compIndex; |
|||
|
|||
private int successiveState; |
|||
|
|||
private int successiveACState; |
|||
|
|||
private int successiveACNextValue; |
|||
|
|||
private bool endOfStreamReached; |
|||
|
|||
private bool unexpectedMarkerReached; |
|||
|
|||
/// <summary>
|
|||
/// Decodes the spectral scan
|
|||
/// </summary>
|
|||
/// <param name="frame">The image frame</param>
|
|||
/// <param name="stream">The input stream</param>
|
|||
/// <param name="dcHuffmanTables">The DC Huffman tables</param>
|
|||
/// <param name="acHuffmanTables">The AC Huffman tables</param>
|
|||
/// <param name="components">The scan components</param>
|
|||
/// <param name="componentIndex">The component index within the array</param>
|
|||
/// <param name="componentsLength">The length of the components. Different to the array length</param>
|
|||
/// <param name="resetInterval">The reset interval</param>
|
|||
/// <param name="spectralStart">The spectral selection start</param>
|
|||
/// <param name="spectralEnd">The spectral selection end</param>
|
|||
/// <param name="successivePrev">The successive approximation bit high end</param>
|
|||
/// <param name="successive">The successive approximation bit low end</param>
|
|||
public void DecodeScan( |
|||
Frame frame, |
|||
Stream stream, |
|||
HuffmanTables dcHuffmanTables, |
|||
HuffmanTables acHuffmanTables, |
|||
FrameComponent[] components, |
|||
int componentIndex, |
|||
int componentsLength, |
|||
ushort resetInterval, |
|||
int spectralStart, |
|||
int spectralEnd, |
|||
int successivePrev, |
|||
int successive) |
|||
{ |
|||
this.markerBuffer = new byte[2]; |
|||
this.compIndex = componentIndex; |
|||
this.specStart = spectralStart; |
|||
this.specEnd = spectralEnd; |
|||
this.successiveState = successive; |
|||
this.endOfStreamReached = false; |
|||
this.unexpectedMarkerReached = false; |
|||
|
|||
bool progressive = frame.Progressive; |
|||
int mcusPerLine = frame.McusPerLine; |
|||
|
|||
int mcu = 0; |
|||
int mcuExpected; |
|||
if (componentsLength == 1) |
|||
{ |
|||
mcuExpected = components[this.compIndex].BlocksPerLine * components[this.compIndex].BlocksPerColumn; |
|||
} |
|||
else |
|||
{ |
|||
mcuExpected = mcusPerLine * frame.McusPerColumn; |
|||
} |
|||
|
|||
FileMarker fileMarker; |
|||
while (mcu < mcuExpected) |
|||
{ |
|||
// Reset interval stuff
|
|||
int mcuToRead = resetInterval != 0 ? Math.Min(mcuExpected - mcu, resetInterval) : mcuExpected; |
|||
for (int i = 0; i < components.Length; i++) |
|||
{ |
|||
ref FrameComponent c = ref components[i]; |
|||
c.Pred = 0; |
|||
} |
|||
|
|||
this.eobrun = 0; |
|||
|
|||
if (!progressive) |
|||
{ |
|||
this.DecodeScanBaseline(dcHuffmanTables, acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); |
|||
} |
|||
else |
|||
{ |
|||
if (this.specStart == 0) |
|||
{ |
|||
if (successivePrev == 0) |
|||
{ |
|||
this.DecodeScanDCFirst(dcHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); |
|||
} |
|||
else |
|||
{ |
|||
this.DecodeScanDCSuccessive(components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (successivePrev == 0) |
|||
{ |
|||
this.DecodeScanACFirst(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); |
|||
} |
|||
else |
|||
{ |
|||
this.DecodeScanACSuccessive(acHuffmanTables, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Find marker
|
|||
this.bitsCount = 0; |
|||
this.accumulator = 0; |
|||
this.bitsUnRead = 0; |
|||
fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); |
|||
|
|||
// Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past
|
|||
// those to attempt to find a valid marker (fixes issue4090.pdf) in original code.
|
|||
if (fileMarker.Invalid) |
|||
{ |
|||
#if DEBUG
|
|||
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); |
|||
#endif
|
|||
} |
|||
|
|||
ushort marker = fileMarker.Marker; |
|||
|
|||
// RSTn - We've alread read the bytes and altered the position so no need to skip
|
|||
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (!fileMarker.Invalid) |
|||
{ |
|||
// We've found a valid marker.
|
|||
// Rewind the stream to the position of the marker and break
|
|||
stream.Position = fileMarker.Position; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
fileMarker = JpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); |
|||
|
|||
// Some images include more Scan blocks than expected, skip past those and
|
|||
// attempt to find the next valid marker (fixes issue8182.pdf) in original code.
|
|||
if (fileMarker.Invalid) |
|||
{ |
|||
#if DEBUG
|
|||
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); |
|||
#endif
|
|||
} |
|||
else |
|||
{ |
|||
// We've found a valid marker.
|
|||
// Rewind the stream to the position of the marker
|
|||
stream.Position = fileMarker.Position; |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int GetBlockBufferOffset(FrameComponent component, int row, int col) |
|||
{ |
|||
return 64 * (((component.BlocksPerLine + 1) * row) + col); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeScanBaseline( |
|||
HuffmanTables dcHuffmanTables, |
|||
HuffmanTables acHuffmanTables, |
|||
FrameComponent[] components, |
|||
int componentsLength, |
|||
int mcusPerLine, |
|||
int mcuToRead, |
|||
ref int mcu, |
|||
Stream stream) |
|||
{ |
|||
if (componentsLength == 1) |
|||
{ |
|||
ref FrameComponent component = ref components[this.compIndex]; |
|||
ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; |
|||
ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|||
|
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeBlockBaseline(ref dcHuffmanTable, ref acHuffmanTable, ref component, mcu, stream); |
|||
mcu++; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
for (int i = 0; i < componentsLength; i++) |
|||
{ |
|||
ref FrameComponent component = ref components[i]; |
|||
ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; |
|||
ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|||
int h = component.HorizontalFactor; |
|||
int v = component.VerticalFactor; |
|||
|
|||
for (int j = 0; j < v; j++) |
|||
{ |
|||
for (int k = 0; k < h; k++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeMcuBaseline(ref dcHuffmanTable, ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); |
|||
} |
|||
} |
|||
} |
|||
|
|||
mcu++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeScanDCFirst( |
|||
HuffmanTables dcHuffmanTables, |
|||
FrameComponent[] components, |
|||
int componentsLength, |
|||
int mcusPerLine, |
|||
int mcuToRead, |
|||
ref int mcu, |
|||
Stream stream) |
|||
{ |
|||
if (componentsLength == 1) |
|||
{ |
|||
ref FrameComponent component = ref components[this.compIndex]; |
|||
ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; |
|||
|
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeBlockDCFirst(ref dcHuffmanTable, ref component, mcu, stream); |
|||
mcu++; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
for (int i = 0; i < componentsLength; i++) |
|||
{ |
|||
ref FrameComponent component = ref components[i]; |
|||
ref HuffmanTable dcHuffmanTable = ref dcHuffmanTables[component.DCHuffmanTableId]; |
|||
int h = component.HorizontalFactor; |
|||
int v = component.VerticalFactor; |
|||
|
|||
for (int j = 0; j < v; j++) |
|||
{ |
|||
for (int k = 0; k < h; k++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeMcuDCFirst(ref dcHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); |
|||
} |
|||
} |
|||
} |
|||
|
|||
mcu++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeScanDCSuccessive( |
|||
FrameComponent[] components, |
|||
int componentsLength, |
|||
int mcusPerLine, |
|||
int mcuToRead, |
|||
ref int mcu, |
|||
Stream stream) |
|||
{ |
|||
if (componentsLength == 1) |
|||
{ |
|||
ref FrameComponent component = ref components[this.compIndex]; |
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeBlockDCSuccessive(ref component, mcu, stream); |
|||
mcu++; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
for (int i = 0; i < componentsLength; i++) |
|||
{ |
|||
ref FrameComponent component = ref components[i]; |
|||
int h = component.HorizontalFactor; |
|||
int v = component.VerticalFactor; |
|||
for (int j = 0; j < v; j++) |
|||
{ |
|||
for (int k = 0; k < h; k++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeMcuDCSuccessive(ref component, mcusPerLine, mcu, j, k, stream); |
|||
} |
|||
} |
|||
} |
|||
|
|||
mcu++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeScanACFirst( |
|||
HuffmanTables acHuffmanTables, |
|||
FrameComponent[] components, |
|||
int componentsLength, |
|||
int mcusPerLine, |
|||
int mcuToRead, |
|||
ref int mcu, |
|||
Stream stream) |
|||
{ |
|||
if (componentsLength == 1) |
|||
{ |
|||
ref FrameComponent component = ref components[this.compIndex]; |
|||
ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|||
|
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeBlockACFirst(ref acHuffmanTable, ref component, mcu, stream); |
|||
mcu++; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
for (int i = 0; i < componentsLength; i++) |
|||
{ |
|||
ref FrameComponent component = ref components[i]; |
|||
ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|||
int h = component.HorizontalFactor; |
|||
int v = component.VerticalFactor; |
|||
|
|||
for (int j = 0; j < v; j++) |
|||
{ |
|||
for (int k = 0; k < h; k++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeMcuACFirst(ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); |
|||
} |
|||
} |
|||
} |
|||
|
|||
mcu++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeScanACSuccessive( |
|||
HuffmanTables acHuffmanTables, |
|||
FrameComponent[] components, |
|||
int componentsLength, |
|||
int mcusPerLine, |
|||
int mcuToRead, |
|||
ref int mcu, |
|||
Stream stream) |
|||
{ |
|||
if (componentsLength == 1) |
|||
{ |
|||
ref FrameComponent component = ref components[this.compIndex]; |
|||
ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|||
|
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeBlockACSuccessive(ref acHuffmanTable, ref component, mcu, stream); |
|||
mcu++; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int n = 0; n < mcuToRead; n++) |
|||
{ |
|||
for (int i = 0; i < componentsLength; i++) |
|||
{ |
|||
ref FrameComponent component = ref components[i]; |
|||
ref HuffmanTable acHuffmanTable = ref acHuffmanTables[component.ACHuffmanTableId]; |
|||
int h = component.HorizontalFactor; |
|||
int v = component.VerticalFactor; |
|||
|
|||
for (int j = 0; j < v; j++) |
|||
{ |
|||
for (int k = 0; k < h; k++) |
|||
{ |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeMcuACSuccessive(ref acHuffmanTable, ref component, mcusPerLine, mcu, j, k, stream); |
|||
} |
|||
} |
|||
} |
|||
|
|||
mcu++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeBlockBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) |
|||
{ |
|||
int blockRow = mcu / component.BlocksPerLine; |
|||
int blockCol = mcu % component.BlocksPerLine; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeBaseline(ref component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeMcuBaseline(ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) |
|||
{ |
|||
int mcuRow = mcu / mcusPerLine; |
|||
int mcuCol = mcu % mcusPerLine; |
|||
int blockRow = (mcuRow * component.VerticalFactor) + row; |
|||
int blockCol = (mcuCol * component.HorizontalFactor) + col; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeBaseline(ref component, offset, ref dcHuffmanTable, ref acHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeBlockDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcu, Stream stream) |
|||
{ |
|||
int blockRow = mcu / component.BlocksPerLine; |
|||
int blockCol = mcu % component.BlocksPerLine; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeDCFirst(ref component, offset, ref dcHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeMcuDCFirst(ref HuffmanTable dcHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) |
|||
{ |
|||
int mcuRow = mcu / mcusPerLine; |
|||
int mcuCol = mcu % mcusPerLine; |
|||
int blockRow = (mcuRow * component.VerticalFactor) + row; |
|||
int blockCol = (mcuCol * component.HorizontalFactor) + col; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeDCFirst(ref component, offset, ref dcHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeBlockDCSuccessive(ref FrameComponent component, int mcu, Stream stream) |
|||
{ |
|||
int blockRow = mcu / component.BlocksPerLine; |
|||
int blockCol = mcu % component.BlocksPerLine; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeDCSuccessive(ref component, offset, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeMcuDCSuccessive(ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) |
|||
{ |
|||
int mcuRow = mcu / mcusPerLine; |
|||
int mcuCol = mcu % mcusPerLine; |
|||
int blockRow = (mcuRow * component.VerticalFactor) + row; |
|||
int blockCol = (mcuCol * component.HorizontalFactor) + col; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeDCSuccessive(ref component, offset, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeBlockACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) |
|||
{ |
|||
int blockRow = mcu / component.BlocksPerLine; |
|||
int blockCol = mcu % component.BlocksPerLine; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeACFirst(ref component, offset, ref acHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeMcuACFirst(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) |
|||
{ |
|||
int mcuRow = mcu / mcusPerLine; |
|||
int mcuCol = mcu % mcusPerLine; |
|||
int blockRow = (mcuRow * component.VerticalFactor) + row; |
|||
int blockCol = (mcuCol * component.HorizontalFactor) + col; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeACFirst(ref component, offset, ref acHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeBlockACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcu, Stream stream) |
|||
{ |
|||
int blockRow = mcu / component.BlocksPerLine; |
|||
int blockCol = mcu % component.BlocksPerLine; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeACSuccessive(ref component, offset, ref acHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeMcuACSuccessive(ref HuffmanTable acHuffmanTable, ref FrameComponent component, int mcusPerLine, int mcu, int row, int col, Stream stream) |
|||
{ |
|||
int mcuRow = mcu / mcusPerLine; |
|||
int mcuCol = mcu % mcusPerLine; |
|||
int blockRow = (mcuRow * component.VerticalFactor) + row; |
|||
int blockCol = (mcuCol * component.HorizontalFactor) + col; |
|||
int offset = GetBlockBufferOffset(component, blockRow, blockCol); |
|||
this.DecodeACSuccessive(ref component, offset, ref acHuffmanTable, stream); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private int ReadBit(Stream stream) |
|||
{ |
|||
// TODO: I wonder if we can do this two bytes at a time; libjpeg turbo seems to do that?
|
|||
if (this.bitsCount > 0) |
|||
{ |
|||
this.bitsCount--; |
|||
return (this.bitsData >> this.bitsCount) & 1; |
|||
} |
|||
|
|||
this.bitsData = stream.ReadByte(); |
|||
|
|||
if (this.bitsData == -0x1) |
|||
{ |
|||
// We've encountered the end of the file stream which means there's no EOI marker in the image
|
|||
this.endOfStreamReached = true; |
|||
} |
|||
|
|||
if (this.bitsData == JpegConstants.Markers.Prefix) |
|||
{ |
|||
int nextByte = stream.ReadByte(); |
|||
if (nextByte != 0) |
|||
{ |
|||
#if DEBUG
|
|||
Debug.WriteLine($"DecodeScan - Unexpected marker {(this.bitsData << 8) | nextByte:X} at {stream.Position}"); |
|||
#endif
|
|||
|
|||
// We've encountered an unexpected marker. Reverse the stream and exit.
|
|||
this.unexpectedMarkerReached = true; |
|||
stream.Position -= 2; |
|||
} |
|||
|
|||
// Unstuff 0
|
|||
} |
|||
|
|||
this.bitsCount = 7; |
|||
|
|||
return this.bitsData >> 7; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private short DecodeHuffman(ref HuffmanTable tree, Stream stream) |
|||
{ |
|||
short code = -1; |
|||
|
|||
// TODO: Adding this code introduces error into the decoder.
|
|||
// NOTES # During investigation of the libjpeg implementation it appears that they pull 32bits at a time and operate on those bits
|
|||
// using 3 methods: FillBits, PeekBits, and ReadBits. We should attempt to do the same.
|
|||
// It doesn't appear to speed anything up either.
|
|||
// if (this.bitsUnRead < 8)
|
|||
// {
|
|||
// if (this.bitsCount <= 0)
|
|||
// {
|
|||
// code = (short)this.ReadBit(stream);
|
|||
// if (this.endOfStreamReached || this.unexpectedMarkerReached)
|
|||
// {
|
|||
// return -1;
|
|||
// }
|
|||
//
|
|||
// this.bitsUnRead += 8;
|
|||
// }
|
|||
//
|
|||
// this.accumulator = (this.accumulator << 8) | this.bitsData;
|
|||
// int lutIndex = (this.accumulator >> (8 - this.bitsUnRead)) & 0xFF;
|
|||
// int v = tree.Lookahead[lutIndex];
|
|||
// if (v != 0)
|
|||
// {
|
|||
// int nb = (v & 0xFF) - 1;
|
|||
// this.bitsCount -= nb - 1;
|
|||
// this.bitsUnRead -= nb;
|
|||
// v = v >> 8;
|
|||
// return (short)v;
|
|||
// }
|
|||
// }
|
|||
if (code == -1) |
|||
{ |
|||
code = (short)this.ReadBit(stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return -1; |
|||
} |
|||
} |
|||
|
|||
// "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
|
|||
int i = 1; |
|||
|
|||
while (code > tree.MaxCode[i]) |
|||
{ |
|||
code <<= 1; |
|||
code |= (short)this.ReadBit(stream); |
|||
|
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
i++; |
|||
} |
|||
|
|||
int j = tree.ValOffset[i]; |
|||
return tree.HuffVal[(j + code) & 0xFF]; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private int Receive(int length, Stream stream) |
|||
{ |
|||
int n = 0; |
|||
while (length > 0) |
|||
{ |
|||
int bit = this.ReadBit(stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
n = (n << 1) | bit; |
|||
length--; |
|||
} |
|||
|
|||
return n; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private int ReceiveAndExtend(int length, Stream stream) |
|||
{ |
|||
if (length == 1) |
|||
{ |
|||
return this.ReadBit(stream) == 1 ? 1 : -1; |
|||
} |
|||
|
|||
int n = this.Receive(length, stream); |
|||
if (n >= 1 << (length - 1)) |
|||
{ |
|||
return n; |
|||
} |
|||
|
|||
return n + (-1 << length) + 1; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeBaseline(ref FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, ref HuffmanTable acHuffmanTable, Stream stream) |
|||
{ |
|||
int t = this.DecodeHuffman(ref dcHuffmanTable, stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); |
|||
component.BlockData[offset] = (short)(component.Pred += diff); |
|||
|
|||
int k = 1; |
|||
while (k < 64) |
|||
{ |
|||
int rs = this.DecodeHuffman(ref acHuffmanTable, stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int s = rs & 15; |
|||
int r = rs >> 4; |
|||
|
|||
if (s == 0) |
|||
{ |
|||
if (r < 15) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
k += 16; |
|||
continue; |
|||
} |
|||
|
|||
k += r; |
|||
|
|||
if (k > 63) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
byte z = QuantizationTables.DctZigZag[k]; |
|||
short re = (short)this.ReceiveAndExtend(s, stream); |
|||
component.BlockData[offset + z] = re; |
|||
k++; |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeDCFirst(ref FrameComponent component, int offset, ref HuffmanTable dcHuffmanTable, Stream stream) |
|||
{ |
|||
int t = this.DecodeHuffman(ref dcHuffmanTable, stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; |
|||
component.BlockData[offset] = (short)(component.Pred += diff); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeDCSuccessive(ref FrameComponent component, int offset, Stream stream) |
|||
{ |
|||
int bit = this.ReadBit(stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
component.BlockData[offset] |= (short)(bit << this.successiveState); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeACFirst(ref FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream) |
|||
{ |
|||
if (this.eobrun > 0) |
|||
{ |
|||
this.eobrun--; |
|||
return; |
|||
} |
|||
|
|||
Span<short> componentBlockDataSpan = component.BlockData.Span; |
|||
int k = this.specStart; |
|||
int e = this.specEnd; |
|||
while (k <= e) |
|||
{ |
|||
short rs = this.DecodeHuffman(ref acHuffmanTable, stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int s = rs & 15; |
|||
int r = rs >> 4; |
|||
|
|||
if (s == 0) |
|||
{ |
|||
if (r < 15) |
|||
{ |
|||
this.eobrun = this.Receive(r, stream) + (1 << r) - 1; |
|||
break; |
|||
} |
|||
|
|||
k += 16; |
|||
continue; |
|||
} |
|||
|
|||
k += r; |
|||
byte z = QuantizationTables.DctZigZag[k]; |
|||
componentBlockDataSpan[offset + z] = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState)); |
|||
k++; |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void DecodeACSuccessive(ref FrameComponent component, int offset, ref HuffmanTable acHuffmanTable, Stream stream) |
|||
{ |
|||
int k = this.specStart; |
|||
int e = this.specEnd; |
|||
int r = 0; |
|||
Span<short> componentBlockDataSpan = component.BlockData.Span; |
|||
while (k <= e) |
|||
{ |
|||
byte z = QuantizationTables.DctZigZag[k]; |
|||
switch (this.successiveACState) |
|||
{ |
|||
case 0: // Initial state
|
|||
short rs = this.DecodeHuffman(ref acHuffmanTable, stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int s = rs & 15; |
|||
r = rs >> 4; |
|||
if (s == 0) |
|||
{ |
|||
if (r < 15) |
|||
{ |
|||
this.eobrun = this.Receive(r, stream) + (1 << r); |
|||
this.successiveACState = 4; |
|||
} |
|||
else |
|||
{ |
|||
r = 16; |
|||
this.successiveACState = 1; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (s != 1) |
|||
{ |
|||
throw new ImageFormatException("Invalid ACn encoding"); |
|||
} |
|||
|
|||
this.successiveACNextValue = this.ReceiveAndExtend(s, stream); |
|||
this.successiveACState = r > 0 ? 2 : 3; |
|||
} |
|||
|
|||
continue; |
|||
case 1: // Skipping r zero items
|
|||
case 2: |
|||
if (componentBlockDataSpan[offset + z] != 0) |
|||
{ |
|||
int bit = this.ReadBit(stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState); |
|||
} |
|||
else |
|||
{ |
|||
r--; |
|||
if (r == 0) |
|||
{ |
|||
this.successiveACState = this.successiveACState == 2 ? 3 : 0; |
|||
} |
|||
} |
|||
|
|||
break; |
|||
case 3: // Set value for a zero item
|
|||
if (componentBlockDataSpan[offset + z] != 0) |
|||
{ |
|||
int bit = this.ReadBit(stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState); |
|||
} |
|||
else |
|||
{ |
|||
componentBlockDataSpan[offset + z] = (short)(this.successiveACNextValue << this.successiveState); |
|||
this.successiveACState = 0; |
|||
} |
|||
|
|||
break; |
|||
case 4: // Eob
|
|||
if (componentBlockDataSpan[offset + z] != 0) |
|||
{ |
|||
int bit = this.ReadBit(stream); |
|||
if (this.endOfStreamReached || this.unexpectedMarkerReached) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
componentBlockDataSpan[offset + z] += (short)(bit << this.successiveState); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
k++; |
|||
} |
|||
|
|||
if (this.successiveACState == 4) |
|||
{ |
|||
this.eobrun--; |
|||
if (this.eobrun == 0) |
|||
{ |
|||
this.successiveACState = 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace.
|
|||
/// Methods to build the tables are based on libjpeg implementation.
|
|||
/// </summary>
|
|||
internal struct YCbCrToRgbTables |
|||
{ |
|||
/// <summary>
|
|||
/// The red red-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CrRTable = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The blue blue-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CbBTable = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The green red-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CrGTable = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The green blue-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CbGTable = new int[256]; |
|||
|
|||
// Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
|
|||
private const int ScaleBits = 16; |
|||
|
|||
private const int Half = 1 << (ScaleBits - 1); |
|||
|
|||
private const int MinSample = 0; |
|||
|
|||
private const int HalfSample = 128; |
|||
|
|||
private const int MaxSample = 255; |
|||
|
|||
/// <summary>
|
|||
/// Initializes the YCbCr tables
|
|||
/// </summary>
|
|||
public static void Create() |
|||
{ |
|||
for (int i = 0, x = -128; i <= 255; i++, x++) |
|||
{ |
|||
// i is the actual input pixel value, in the range 0..255
|
|||
// The Cb or Cr value we are thinking of is x = i - 128
|
|||
// Cr=>R value is nearest int to 1.402 * x
|
|||
CrRTable[i] = RightShift((Fix(1.402F) * x) + Half); |
|||
|
|||
// Cb=>B value is nearest int to 1.772 * x
|
|||
CbBTable[i] = RightShift((Fix(1.772F) * x) + Half); |
|||
|
|||
// Cr=>G value is scaled-up -0.714136286
|
|||
CrGTable[i] = (-Fix(0.714136286F)) * x; |
|||
|
|||
// Cb => G value is scaled - up - 0.344136286 * x
|
|||
// We also add in Half so that need not do it in inner loop
|
|||
CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Optimized method to pack bytes to the image from the YCbCr color space.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="packed">The packed pixel.</param>
|
|||
/// <param name="y">The y luminance component.</param>
|
|||
/// <param name="cb">The cb chroma component.</param>
|
|||
/// <param name="cr">The cr chroma component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void PackYCbCr<TPixel>(ref TPixel packed, byte y, byte cb, byte cr) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
byte r = (byte)(y + CrRTable[cr]).Clamp(0, 255); |
|||
|
|||
// The values for the G calculation are left scaled up, since we must add them together before rounding.
|
|||
byte g = (byte)(y + RightShift(CbGTable[cb] + CrGTable[cr])).Clamp(0, 255); |
|||
|
|||
byte b = (byte)(y + CbBTable[cb]).Clamp(0, 255); |
|||
|
|||
packed.PackFromRgba32(new Rgba32(r, g, b, 255)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Optimized method to pack bytes to the image from the YccK color space.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="packed">The packed pixel.</param>
|
|||
/// <param name="y">The y luminance component.</param>
|
|||
/// <param name="cb">The cb chroma component.</param>
|
|||
/// <param name="cr">The cr chroma component.</param>
|
|||
/// <param name="k">The keyline component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void PackYccK<TPixel>(ref TPixel packed, byte y, byte cb, byte cr, byte k) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
int c = (MaxSample - (y + CrRTable[cr])).Clamp(0, 255); |
|||
|
|||
// The values for the G calculation are left scaled up, since we must add them together before rounding.
|
|||
int m = (MaxSample - (y + RightShift(CbGTable[cb] + CrGTable[cr]))).Clamp(0, 255); |
|||
|
|||
int cy = (MaxSample - (y + CbBTable[cb])).Clamp(0, 255); |
|||
|
|||
byte r = (byte)((c * k) / MaxSample); |
|||
byte g = (byte)((m * k) / MaxSample); |
|||
byte b = (byte)((cy * k) / MaxSample); |
|||
|
|||
packed.PackFromRgba32(new Rgba32(r, g, b, MaxSample)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Fix(float x) |
|||
{ |
|||
return (int)((x * (1L << ScaleBits)) + 0.5F); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int RightShift(int x) |
|||
{ |
|||
return x >> ScaleBits; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,359 @@ |
|||
// <copyright file="JpegConstants.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace ImageSharp.Formats.Jpeg.Port |
|||
{ |
|||
/// <summary>
|
|||
/// Contains jpeg constant values
|
|||
/// </summary>
|
|||
internal static class JpegConstants |
|||
{ |
|||
/// <summary>
|
|||
/// Contains marker specific constants
|
|||
/// </summary>
|
|||
public static class Markers |
|||
{ |
|||
/// <summary>
|
|||
/// The prefix used for all markers.
|
|||
/// </summary>
|
|||
public const byte Prefix = 0xFF; |
|||
|
|||
/// <summary>
|
|||
/// The Start of Image marker
|
|||
/// </summary>
|
|||
public const ushort SOI = 0xFFD8; |
|||
|
|||
/// <summary>
|
|||
/// The End of Image marker
|
|||
/// </summary>
|
|||
public const ushort EOI = 0xFFD9; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker for marking the jpeg format.
|
|||
/// <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html"/>
|
|||
/// </summary>
|
|||
public const ushort APP0 = 0xFFE0; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker for marking where to store metadata.
|
|||
/// </summary>
|
|||
public const ushort APP1 = 0xFFE1; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker for marking where to store ICC profile information.
|
|||
/// </summary>
|
|||
public const ushort APP2 = 0xFFE2; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP3 = 0xFFE3; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP4 = 0xFFE4; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP5 = 0xFFE5; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP6 = 0xFFE6; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP7 = 0xFFE7; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP8 = 0xFFE8; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP9 = 0xFFE9; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP10 = 0xFFEA; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP11 = 0xFFEB; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP12 = 0xFFEC; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker.
|
|||
/// </summary>
|
|||
public const ushort APP13 = 0xFFED; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
|
|||
/// </summary>
|
|||
public const ushort APP14 = 0xFFEE; |
|||
|
|||
/// <summary>
|
|||
/// Application specific marker used by GraphicConverter to store JPEG quality.
|
|||
/// </summary>
|
|||
public const ushort APP15 = 0xFFEF; |
|||
|
|||
/// <summary>
|
|||
/// The text comment marker
|
|||
/// </summary>
|
|||
public const ushort COM = 0xFFFE; |
|||
|
|||
/// <summary>
|
|||
/// Define Quantization Table(s) marker
|
|||
/// <remarks>
|
|||
/// Specifies one or more quantization tables.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort DQT = 0xFFDB; |
|||
|
|||
/// <summary>
|
|||
/// Start of Frame (baseline DCT)
|
|||
/// <remarks>
|
|||
/// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components,
|
|||
/// and component subsampling (e.g., 4:2:0).
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort SOF0 = 0xFFC0; |
|||
|
|||
/// <summary>
|
|||
/// Start Of Frame (Extended Sequential DCT)
|
|||
/// <remarks>
|
|||
/// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
|
|||
/// and component subsampling (e.g., 4:2:0).
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort SOF1 = 0xFFC1; |
|||
|
|||
/// <summary>
|
|||
/// Start Of Frame (progressive DCT)
|
|||
/// <remarks>
|
|||
/// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components,
|
|||
/// and component subsampling (e.g., 4:2:0).
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort SOF2 = 0xFFC2; |
|||
|
|||
/// <summary>
|
|||
/// Define Huffman Table(s)
|
|||
/// <remarks>
|
|||
/// Specifies one or more Huffman tables.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort DHT = 0xFFC4; |
|||
|
|||
/// <summary>
|
|||
/// Define Restart Interval
|
|||
/// <remarks>
|
|||
/// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort DRI = 0xFFDD; |
|||
|
|||
/// <summary>
|
|||
/// Start of Scan
|
|||
/// <remarks>
|
|||
/// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan.
|
|||
/// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it
|
|||
/// will contain, and is immediately followed by entropy-coded data.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort SOS = 0xFFDA; |
|||
|
|||
/// <summary>
|
|||
/// Define First Restart
|
|||
/// <remarks>
|
|||
/// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
|
|||
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort RST0 = 0xFFD0; |
|||
|
|||
/// <summary>
|
|||
/// Define Eigth Restart
|
|||
/// <remarks>
|
|||
/// Inserted every r macroblocks, where r is the restart interval set by a DRI marker.
|
|||
/// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public const ushort RST7 = 0xFFD7; |
|||
|
|||
/// <summary>
|
|||
/// Contains JFIF specific markers
|
|||
/// </summary>
|
|||
public static class JFif |
|||
{ |
|||
/// <summary>
|
|||
/// Represents J in ASCII
|
|||
/// </summary>
|
|||
public const byte J = 0x4A; |
|||
|
|||
/// <summary>
|
|||
/// Represents F in ASCII
|
|||
/// </summary>
|
|||
public const byte F = 0x46; |
|||
|
|||
/// <summary>
|
|||
/// Represents I in ASCII
|
|||
/// </summary>
|
|||
public const byte I = 0x49; |
|||
|
|||
/// <summary>
|
|||
/// Represents the null "0" marker
|
|||
/// </summary>
|
|||
public const byte Null = 0x0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains Adobe specific markers
|
|||
/// </summary>
|
|||
public static class Adobe |
|||
{ |
|||
/// <summary>
|
|||
/// Represents A in ASCII
|
|||
/// </summary>
|
|||
public const byte A = 0x41; |
|||
|
|||
/// <summary>
|
|||
/// Represents d in ASCII
|
|||
/// </summary>
|
|||
public const byte D = 0x64; |
|||
|
|||
/// <summary>
|
|||
/// Represents b in ASCII
|
|||
/// </summary>
|
|||
public const byte O = 0x6F; |
|||
|
|||
/// <summary>
|
|||
/// Represents b in ASCII
|
|||
/// </summary>
|
|||
public const byte B = 0x62; |
|||
|
|||
/// <summary>
|
|||
/// Represents e in ASCII
|
|||
/// </summary>
|
|||
public const byte E = 0x65; |
|||
|
|||
/// <summary>
|
|||
/// The color transform is unknown.(RGB or CMYK)
|
|||
/// </summary>
|
|||
public const byte ColorTransformUnknown = 0; |
|||
|
|||
/// <summary>
|
|||
/// The color transform is YCbCr (luminance, red chroma, blue chroma)
|
|||
/// </summary>
|
|||
public const byte ColorTransformYCbCr = 1; |
|||
|
|||
/// <summary>
|
|||
/// The color transform is YCCK (luminance, red chroma, blue chroma, keyline)
|
|||
/// </summary>
|
|||
public const byte ColorTransformYcck = 2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains EXIF specific markers
|
|||
/// </summary>
|
|||
public static class Exif |
|||
{ |
|||
/// <summary>
|
|||
/// Represents E in ASCII
|
|||
/// </summary>
|
|||
public const byte E = 0x45; |
|||
|
|||
/// <summary>
|
|||
/// Represents x in ASCII
|
|||
/// </summary>
|
|||
public const byte X = 0x78; |
|||
|
|||
/// <summary>
|
|||
/// Represents i in ASCII
|
|||
/// </summary>
|
|||
public const byte I = 0x69; |
|||
|
|||
/// <summary>
|
|||
/// Represents f in ASCII
|
|||
/// </summary>
|
|||
public const byte F = 0x66; |
|||
|
|||
/// <summary>
|
|||
/// Represents the null "0" marker
|
|||
/// </summary>
|
|||
public const byte Null = 0x0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains ICC specific markers
|
|||
/// </summary>
|
|||
public static class ICC |
|||
{ |
|||
/// <summary>
|
|||
/// Represents I in ASCII
|
|||
/// </summary>
|
|||
public const byte I = 0x49; |
|||
|
|||
/// <summary>
|
|||
/// Represents C in ASCII
|
|||
/// </summary>
|
|||
public const byte C = 0x43; |
|||
|
|||
/// <summary>
|
|||
/// Represents _ in ASCII
|
|||
/// </summary>
|
|||
public const byte UnderScore = 0x5F; |
|||
|
|||
/// <summary>
|
|||
/// Represents P in ASCII
|
|||
/// </summary>
|
|||
public const byte P = 0x50; |
|||
|
|||
/// <summary>
|
|||
/// Represents R in ASCII
|
|||
/// </summary>
|
|||
public const byte R = 0x52; |
|||
|
|||
/// <summary>
|
|||
/// Represents O in ASCII
|
|||
/// </summary>
|
|||
public const byte O = 0x4F; |
|||
|
|||
/// <summary>
|
|||
/// Represents F in ASCII
|
|||
/// </summary>
|
|||
public const byte F = 0x46; |
|||
|
|||
/// <summary>
|
|||
/// Represents L in ASCII
|
|||
/// </summary>
|
|||
public const byte L = 0x4C; |
|||
|
|||
/// <summary>
|
|||
/// Represents E in ASCII
|
|||
/// </summary>
|
|||
public const byte E = 0x45; |
|||
|
|||
/// <summary>
|
|||
/// Represents the null "0" marker
|
|||
/// </summary>
|
|||
public const byte Null = 0x0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,966 @@ |
|||
// <copyright file="JpegDecoderCore.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp.Formats.Jpeg.Port.Components; |
|||
using ImageSharp.Memory; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Performs the jpeg decoding operation.
|
|||
/// Ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/> with additional fixes to handle common encoding errors
|
|||
/// </summary>
|
|||
internal sealed class JpegDecoderCore : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The global configuration
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// Gets the temporary buffer used to store bytes read from the stream.
|
|||
/// </summary>
|
|||
private readonly byte[] temp = new byte[2 * 16 * 4]; |
|||
|
|||
private readonly byte[] markerBuffer = new byte[2]; |
|||
|
|||
private QuantizationTables quantizationTables; |
|||
|
|||
private HuffmanTables dcHuffmanTables; |
|||
|
|||
private HuffmanTables acHuffmanTables; |
|||
|
|||
private Frame frame; |
|||
|
|||
private ComponentBlocks components; |
|||
|
|||
private JpegPixelArea pixelArea; |
|||
|
|||
private ushort resetInterval; |
|||
|
|||
private int imageWidth; |
|||
|
|||
private int imageHeight; |
|||
|
|||
private int numberOfComponents; |
|||
|
|||
/// <summary>
|
|||
/// Whether the image has a EXIF header
|
|||
/// </summary>
|
|||
private bool isExif; |
|||
|
|||
/// <summary>
|
|||
/// Contains information about the JFIF marker
|
|||
/// </summary>
|
|||
private JFif jFif; |
|||
|
|||
/// <summary>
|
|||
/// Contains information about the Adobe marker
|
|||
/// </summary>
|
|||
private Adobe adobe; |
|||
|
|||
/// <summary>
|
|||
/// Initializes static members of the <see cref="JpegDecoderCore"/> class.
|
|||
/// </summary>
|
|||
static JpegDecoderCore() |
|||
{ |
|||
YCbCrToRgbTables.Create(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) |
|||
{ |
|||
this.configuration = configuration ?? Configuration.Default; |
|||
this.IgnoreMetadata = options.IgnoreMetadata; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the input stream.
|
|||
/// </summary>
|
|||
public Stream InputStream { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
|
|||
/// </summary>
|
|||
public bool IgnoreMetadata { get; } |
|||
|
|||
/// <summary>
|
|||
/// Finds the next file marker within the byte stream.
|
|||
/// </summary>
|
|||
/// <param name="marker">The buffer to read file markers to</param>
|
|||
/// <param name="stream">The input stream</param>
|
|||
/// <returns>The <see cref="FileMarker"/></returns>
|
|||
public static FileMarker FindNextFileMarker(byte[] marker, Stream stream) |
|||
{ |
|||
int value = stream.Read(marker, 0, 2); |
|||
|
|||
if (value == 0) |
|||
{ |
|||
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); |
|||
} |
|||
|
|||
if (marker[0] == JpegConstants.Markers.Prefix) |
|||
{ |
|||
// According to Section B.1.1.2:
|
|||
// "Any marker may optionally be preceded by any number of fill bytes, which are bytes assigned code 0xFF."
|
|||
while (marker[1] == JpegConstants.Markers.Prefix) |
|||
{ |
|||
int suffix = stream.ReadByte(); |
|||
if (suffix == -1) |
|||
{ |
|||
return new FileMarker(JpegConstants.Markers.EOI, (int)stream.Length - 2); |
|||
} |
|||
|
|||
marker[1] = (byte)value; |
|||
} |
|||
|
|||
return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); |
|||
} |
|||
|
|||
return new FileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2), true); |
|||
} |
|||
|
|||
/// <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.InputStream = stream; |
|||
|
|||
var metadata = new ImageMetaData(); |
|||
this.ParseStream(metadata, false); |
|||
|
|||
var image = new Image<TPixel>(this.configuration, this.imageWidth, this.imageHeight, metadata); |
|||
this.FillPixelData(image); |
|||
this.AssignResolution(image); |
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.frame?.Dispose(); |
|||
this.components?.Dispose(); |
|||
this.quantizationTables?.Dispose(); |
|||
this.dcHuffmanTables?.Dispose(); |
|||
this.acHuffmanTables?.Dispose(); |
|||
this.pixelArea.Dispose(); |
|||
|
|||
// Set large fields to null.
|
|||
this.frame = null; |
|||
this.components = null; |
|||
this.quantizationTables = null; |
|||
this.dcHuffmanTables = null; |
|||
this.acHuffmanTables = null; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int GetBlockBufferOffset(ref Component component, int row, int col) |
|||
{ |
|||
return 64 * (((component.BlocksPerLine + 1) * row) + col); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses the input stream for file markers
|
|||
/// </summary>
|
|||
/// <param name="metaData">Contains the metadata for an image</param>
|
|||
/// <param name="metadataOnly">Whether to decode metadata only.</param>
|
|||
private void ParseStream(ImageMetaData metaData, bool metadataOnly) |
|||
{ |
|||
// TODO: metadata only logic
|
|||
// Check for the Start Of Image marker.
|
|||
var fileMarker = new FileMarker(this.ReadUint16(), 0); |
|||
if (fileMarker.Marker != JpegConstants.Markers.SOI) |
|||
{ |
|||
throw new ImageFormatException("Missing SOI marker."); |
|||
} |
|||
|
|||
ushort marker = this.ReadUint16(); |
|||
fileMarker = new FileMarker(marker, (int)this.InputStream.Position - 2); |
|||
|
|||
this.quantizationTables = new QuantizationTables(); |
|||
this.dcHuffmanTables = new HuffmanTables(); |
|||
this.acHuffmanTables = new HuffmanTables(); |
|||
|
|||
while (fileMarker.Marker != JpegConstants.Markers.EOI) |
|||
{ |
|||
// Get the marker length
|
|||
int remaining = this.ReadUint16() - 2; |
|||
|
|||
switch (fileMarker.Marker) |
|||
{ |
|||
case JpegConstants.Markers.APP0: |
|||
this.ProcessApplicationHeaderMarker(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.APP1: |
|||
this.ProcessApp1Marker(remaining, metaData); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.APP2: |
|||
this.ProcessApp2Marker(remaining, metaData); |
|||
break; |
|||
case JpegConstants.Markers.APP3: |
|||
case JpegConstants.Markers.APP4: |
|||
case JpegConstants.Markers.APP5: |
|||
case JpegConstants.Markers.APP6: |
|||
case JpegConstants.Markers.APP7: |
|||
case JpegConstants.Markers.APP8: |
|||
case JpegConstants.Markers.APP9: |
|||
case JpegConstants.Markers.APP10: |
|||
case JpegConstants.Markers.APP11: |
|||
case JpegConstants.Markers.APP12: |
|||
case JpegConstants.Markers.APP13: |
|||
this.InputStream.Skip(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.APP14: |
|||
this.ProcessApp14Marker(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.APP15: |
|||
case JpegConstants.Markers.COM: |
|||
this.InputStream.Skip(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.DQT: |
|||
this.ProcessDefineQuantizationTablesMarker(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.SOF0: |
|||
case JpegConstants.Markers.SOF1: |
|||
case JpegConstants.Markers.SOF2: |
|||
this.ProcessStartOfFrameMarker(remaining, fileMarker); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.DHT: |
|||
this.ProcessDefineHuffmanTablesMarker(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.DRI: |
|||
this.ProcessDefineRestartIntervalMarker(remaining); |
|||
break; |
|||
|
|||
case JpegConstants.Markers.SOS: |
|||
this.ProcessStartOfScanMarker(); |
|||
break; |
|||
} |
|||
|
|||
// Read on.
|
|||
fileMarker = FindNextFileMarker(this.markerBuffer, this.InputStream); |
|||
} |
|||
|
|||
this.imageWidth = this.frame.SamplesPerLine; |
|||
this.imageHeight = this.frame.Scanlines; |
|||
this.components = new ComponentBlocks { Components = new Component[this.frame.ComponentCount] }; |
|||
|
|||
for (int i = 0; i < this.components.Components.Length; i++) |
|||
{ |
|||
ref var frameComponent = ref this.frame.Components[i]; |
|||
var component = new Component |
|||
{ |
|||
Scale = new System.Numerics.Vector2( |
|||
frameComponent.HorizontalFactor / (float)this.frame.MaxHorizontalFactor, |
|||
frameComponent.VerticalFactor / (float)this.frame.MaxVerticalFactor), |
|||
BlocksPerLine = frameComponent.BlocksPerLine, |
|||
BlocksPerColumn = frameComponent.BlocksPerColumn |
|||
}; |
|||
|
|||
this.BuildComponentData(ref component, ref frameComponent); |
|||
this.components.Components[i] = component; |
|||
} |
|||
|
|||
this.numberOfComponents = this.components.Components.Length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills the given image with the color data
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The image</param>
|
|||
private void FillPixelData<TPixel>(Image<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
if (this.numberOfComponents > 4) |
|||
{ |
|||
throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.numberOfComponents}"); |
|||
} |
|||
|
|||
this.pixelArea = new JpegPixelArea(image.Width, image.Height, this.numberOfComponents); |
|||
this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height); |
|||
|
|||
if (this.numberOfComponents == 1) |
|||
{ |
|||
this.FillGrayScaleImage(image); |
|||
return; |
|||
} |
|||
|
|||
if (this.numberOfComponents == 3) |
|||
{ |
|||
if (this.adobe.Equals(default(Adobe)) || this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYCbCr) |
|||
{ |
|||
this.FillYCbCrImage(image); |
|||
} |
|||
else if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformUnknown) |
|||
{ |
|||
this.FillRgbImage(image); |
|||
} |
|||
} |
|||
|
|||
if (this.numberOfComponents == 4) |
|||
{ |
|||
if (this.adobe.ColorTransform == JpegConstants.Markers.Adobe.ColorTransformYcck) |
|||
{ |
|||
this.FillYcckImage(image); |
|||
} |
|||
else |
|||
{ |
|||
this.FillCmykImage(image); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Assigns the horizontal and vertical resolution to the image if it has a JFIF header or EXIF metadata.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The image to assign the resolution to.</param>
|
|||
private void AssignResolution<TPixel>(Image<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
if (this.isExif) |
|||
{ |
|||
ExifValue horizontal = image.MetaData.ExifProfile.GetValue(ExifTag.XResolution); |
|||
ExifValue vertical = image.MetaData.ExifProfile.GetValue(ExifTag.YResolution); |
|||
double horizontalValue = horizontal != null ? ((Rational)horizontal.Value).ToDouble() : 0; |
|||
double verticalValue = vertical != null ? ((Rational)vertical.Value).ToDouble() : 0; |
|||
|
|||
if (horizontalValue > 0 && verticalValue > 0) |
|||
{ |
|||
image.MetaData.HorizontalResolution = horizontalValue; |
|||
image.MetaData.VerticalResolution = verticalValue; |
|||
} |
|||
} |
|||
else if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0) |
|||
{ |
|||
image.MetaData.HorizontalResolution = this.jFif.XDensity; |
|||
image.MetaData.VerticalResolution = this.jFif.YDensity; |
|||
} |
|||
} |
|||
|
|||
/// <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) |
|||
{ |
|||
// Skip the application header length
|
|||
this.InputStream.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 13); |
|||
remaining -= 13; |
|||
|
|||
bool isJfif = this.temp[0] == JpegConstants.Markers.JFif.J && |
|||
this.temp[1] == JpegConstants.Markers.JFif.F && |
|||
this.temp[2] == JpegConstants.Markers.JFif.I && |
|||
this.temp[3] == JpegConstants.Markers.JFif.F && |
|||
this.temp[4] == JpegConstants.Markers.JFif.Null; |
|||
|
|||
if (isJfif) |
|||
{ |
|||
this.jFif = new JFif |
|||
{ |
|||
MajorVersion = this.temp[5], |
|||
MinorVersion = this.temp[6], |
|||
DensityUnits = this.temp[7], |
|||
XDensity = (short)((this.temp[8] << 8) | this.temp[9]), |
|||
YDensity = (short)((this.temp[10] << 8) | this.temp[11]) |
|||
}; |
|||
} |
|||
|
|||
// TODO: thumbnail
|
|||
if (remaining > 0) |
|||
{ |
|||
this.InputStream.Skip(remaining); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the App1 marker retrieving any stored metadata
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
/// <param name="metadata">The image.</param>
|
|||
private void ProcessApp1Marker(int remaining, ImageMetaData metadata) |
|||
{ |
|||
if (remaining < 6 || this.IgnoreMetadata) |
|||
{ |
|||
// Skip the application header length
|
|||
this.InputStream.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
byte[] profile = new byte[remaining]; |
|||
this.InputStream.Read(profile, 0, remaining); |
|||
|
|||
if (profile[0] == JpegConstants.Markers.Exif.E && |
|||
profile[1] == JpegConstants.Markers.Exif.X && |
|||
profile[2] == JpegConstants.Markers.Exif.I && |
|||
profile[3] == JpegConstants.Markers.Exif.F && |
|||
profile[4] == JpegConstants.Markers.Exif.Null && |
|||
profile[5] == JpegConstants.Markers.Exif.Null) |
|||
{ |
|||
this.isExif = true; |
|||
metadata.ExifProfile = new ExifProfile(profile); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the App2 marker retrieving any stored ICC profile information
|
|||
/// </summary>
|
|||
/// <param name="remaining">The remaining bytes in the segment block.</param>
|
|||
/// <param name="metadata">The image.</param>
|
|||
private void ProcessApp2Marker(int remaining, ImageMetaData metadata) |
|||
{ |
|||
// Length is 14 though we only need to check 12.
|
|||
const int Icclength = 14; |
|||
if (remaining < Icclength || this.IgnoreMetadata) |
|||
{ |
|||
this.InputStream.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
byte[] identifier = new byte[Icclength]; |
|||
this.InputStream.Read(identifier, 0, Icclength); |
|||
remaining -= Icclength; // We have read it by this point
|
|||
|
|||
if (identifier[0] == JpegConstants.Markers.ICC.I && |
|||
identifier[1] == JpegConstants.Markers.ICC.C && |
|||
identifier[2] == JpegConstants.Markers.ICC.C && |
|||
identifier[3] == JpegConstants.Markers.ICC.UnderScore && |
|||
identifier[4] == JpegConstants.Markers.ICC.P && |
|||
identifier[5] == JpegConstants.Markers.ICC.R && |
|||
identifier[6] == JpegConstants.Markers.ICC.O && |
|||
identifier[7] == JpegConstants.Markers.ICC.F && |
|||
identifier[8] == JpegConstants.Markers.ICC.I && |
|||
identifier[9] == JpegConstants.Markers.ICC.L && |
|||
identifier[10] == JpegConstants.Markers.ICC.E && |
|||
identifier[11] == JpegConstants.Markers.ICC.Null) |
|||
{ |
|||
byte[] profile = new byte[remaining]; |
|||
this.InputStream.Read(profile, 0, remaining); |
|||
|
|||
if (metadata.IccProfile == null) |
|||
{ |
|||
metadata.IccProfile = new IccProfile(profile); |
|||
} |
|||
else |
|||
{ |
|||
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.InputStream.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) |
|||
{ |
|||
if (remaining < 12) |
|||
{ |
|||
// Skip the application header length
|
|||
this.InputStream.Skip(remaining); |
|||
return; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 12); |
|||
remaining -= 12; |
|||
|
|||
bool isAdobe = this.temp[0] == JpegConstants.Markers.Adobe.A && |
|||
this.temp[1] == JpegConstants.Markers.Adobe.D && |
|||
this.temp[2] == JpegConstants.Markers.Adobe.O && |
|||
this.temp[3] == JpegConstants.Markers.Adobe.B && |
|||
this.temp[4] == JpegConstants.Markers.Adobe.E; |
|||
|
|||
if (isAdobe) |
|||
{ |
|||
this.adobe = new Adobe |
|||
{ |
|||
DCTEncodeVersion = (short)((this.temp[5] << 8) | this.temp[6]), |
|||
APP14Flags0 = (short)((this.temp[7] << 8) | this.temp[8]), |
|||
APP14Flags1 = (short)((this.temp[9] << 8) | this.temp[10]), |
|||
ColorTransform = this.temp[11] |
|||
}; |
|||
} |
|||
|
|||
if (remaining > 0) |
|||
{ |
|||
this.InputStream.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--; |
|||
int quantizationTableSpec = this.InputStream.ReadByte(); |
|||
|
|||
switch (quantizationTableSpec >> 4) |
|||
{ |
|||
case 0: |
|||
{ |
|||
// 8 bit values
|
|||
if (remaining < 64) |
|||
{ |
|||
done = true; |
|||
break; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 64); |
|||
remaining -= 64; |
|||
|
|||
Span<short> tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15); |
|||
for (int j = 0; j < 64; j++) |
|||
{ |
|||
tableSpan[QuantizationTables.DctZigZag[j]] = this.temp[j]; |
|||
} |
|||
} |
|||
|
|||
break; |
|||
case 1: |
|||
{ |
|||
// 16 bit values
|
|||
if (remaining < 128) |
|||
{ |
|||
done = true; |
|||
break; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 128); |
|||
remaining -= 128; |
|||
|
|||
Span<short> tableSpan = this.quantizationTables.Tables.GetRowSpan(quantizationTableSpec & 15); |
|||
for (int j = 0; j < 64; j++) |
|||
{ |
|||
tableSpan[QuantizationTables.DctZigZag[j]] = (short)((this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]); |
|||
} |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
throw new ImageFormatException("Bad Tq index 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="frameMarker">The current frame marker.</param>
|
|||
private void ProcessStartOfFrameMarker(int remaining, FileMarker frameMarker) |
|||
{ |
|||
if (this.frame != null) |
|||
{ |
|||
throw new ImageFormatException("Multiple SOF markers. Only single frame jpegs supported."); |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, remaining); |
|||
|
|||
this.frame = new Frame |
|||
{ |
|||
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, |
|||
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, |
|||
Precision = this.temp[0], |
|||
Scanlines = (short)((this.temp[1] << 8) | this.temp[2]), |
|||
SamplesPerLine = (short)((this.temp[3] << 8) | this.temp[4]), |
|||
ComponentCount = this.temp[5] |
|||
}; |
|||
|
|||
int maxH = 0; |
|||
int maxV = 0; |
|||
int index = 6; |
|||
|
|||
// No need to pool this. They max out at 4
|
|||
this.frame.ComponentIds = new byte[this.frame.ComponentCount]; |
|||
this.frame.Components = new FrameComponent[this.frame.ComponentCount]; |
|||
|
|||
for (int i = 0; i < this.frame.Components.Length; i++) |
|||
{ |
|||
int h = this.temp[index + 1] >> 4; |
|||
int v = this.temp[index + 1] & 15; |
|||
|
|||
if (maxH < h) |
|||
{ |
|||
maxH = h; |
|||
} |
|||
|
|||
if (maxV < v) |
|||
{ |
|||
maxV = v; |
|||
} |
|||
|
|||
ref var component = ref this.frame.Components[i]; |
|||
component.Id = this.temp[index]; |
|||
component.HorizontalFactor = h; |
|||
component.VerticalFactor = v; |
|||
component.QuantizationIdentifier = this.temp[index + 2]; |
|||
|
|||
this.frame.ComponentIds[i] = component.Id; |
|||
|
|||
index += 3; |
|||
} |
|||
|
|||
this.frame.MaxHorizontalFactor = maxH; |
|||
this.frame.MaxVerticalFactor = maxV; |
|||
this.PrepareComponents(); |
|||
} |
|||
|
|||
/// <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) |
|||
{ |
|||
if (remaining < 17) |
|||
{ |
|||
throw new ImageFormatException($"DHT has wrong length: {remaining}"); |
|||
} |
|||
|
|||
using (var huffmanData = Buffer<byte>.CreateClean(256)) |
|||
{ |
|||
for (int i = 2; i < remaining;) |
|||
{ |
|||
byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); |
|||
this.InputStream.Read(huffmanData.Array, 0, 16); |
|||
|
|||
using (var codeLengths = Buffer<byte>.CreateClean(17)) |
|||
{ |
|||
int codeLengthSum = 0; |
|||
|
|||
for (int j = 1; j < 17; j++) |
|||
{ |
|||
codeLengthSum += codeLengths[j] = huffmanData[j - 1]; |
|||
} |
|||
|
|||
using (var huffmanValues = Buffer<byte>.CreateClean(256)) |
|||
{ |
|||
this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); |
|||
|
|||
i += 17 + codeLengthSum; |
|||
|
|||
this.BuildHuffmanTable( |
|||
huffmanTableSpec >> 4 == 0 ? this.dcHuffmanTables : this.acHuffmanTables, |
|||
huffmanTableSpec & 15, |
|||
codeLengths.Array, |
|||
huffmanValues.Array); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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: {remaining}"); |
|||
} |
|||
|
|||
this.resetInterval = this.ReadUint16(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the SOS (Start of scan marker).
|
|||
/// </summary>
|
|||
private void ProcessStartOfScanMarker() |
|||
{ |
|||
int selectorsCount = this.InputStream.ReadByte(); |
|||
int componentIndex = -1; |
|||
for (int i = 0; i < selectorsCount; i++) |
|||
{ |
|||
componentIndex = -1; |
|||
int selector = this.InputStream.ReadByte(); |
|||
|
|||
for (int j = 0; j < this.frame.ComponentIds.Length; j++) |
|||
{ |
|||
byte id = this.frame.ComponentIds[j]; |
|||
if (selector == id) |
|||
{ |
|||
componentIndex = j; |
|||
} |
|||
} |
|||
|
|||
if (componentIndex < 0) |
|||
{ |
|||
throw new ImageFormatException("Unknown component selector"); |
|||
} |
|||
|
|||
ref FrameComponent component = ref this.frame.Components[componentIndex]; |
|||
int tableSpec = this.InputStream.ReadByte(); |
|||
component.DCHuffmanTableId = tableSpec >> 4; |
|||
component.ACHuffmanTableId = tableSpec & 15; |
|||
} |
|||
|
|||
this.InputStream.Read(this.temp, 0, 3); |
|||
|
|||
int spectralStart = this.temp[0]; |
|||
int spectralEnd = this.temp[1]; |
|||
int successiveApproximation = this.temp[2]; |
|||
var scanDecoder = default(ScanDecoder); |
|||
|
|||
scanDecoder.DecodeScan( |
|||
this.frame, |
|||
this.InputStream, |
|||
this.dcHuffmanTables, |
|||
this.acHuffmanTables, |
|||
this.frame.Components, |
|||
componentIndex, |
|||
selectorsCount, |
|||
this.resetInterval, |
|||
spectralStart, |
|||
spectralEnd, |
|||
successiveApproximation >> 4, |
|||
successiveApproximation & 15); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Build the data for the given component
|
|||
/// </summary>
|
|||
/// <param name="component">The component</param>
|
|||
/// <param name="frameComponent">The frame component</param>
|
|||
private void BuildComponentData(ref Component component, ref FrameComponent frameComponent) |
|||
{ |
|||
int blocksPerLine = component.BlocksPerLine; |
|||
int blocksPerColumn = component.BlocksPerColumn; |
|||
using (var computationBuffer = Buffer<short>.CreateClean(64)) |
|||
using (var multiplicationBuffer = Buffer<short>.CreateClean(64)) |
|||
{ |
|||
Span<short> quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationIdentifier); |
|||
Span<short> computationBufferSpan = computationBuffer; |
|||
|
|||
// For AA&N IDCT method, multiplier are equal to quantization
|
|||
// coefficients scaled by scalefactor[row]*scalefactor[col], where
|
|||
// scalefactor[0] = 1
|
|||
// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
|
|||
// For integer operation, the multiplier table is to be scaled by 12.
|
|||
Span<short> multiplierSpan = multiplicationBuffer; |
|||
for (int i = 0; i < 64; i++) |
|||
{ |
|||
multiplierSpan[i] = (short)IDCT.Descale(quantizationTable[i] * IDCT.Aanscales[i], 12); |
|||
} |
|||
|
|||
for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) |
|||
{ |
|||
for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) |
|||
{ |
|||
int offset = GetBlockBufferOffset(ref component, blockRow, blockCol); |
|||
IDCT.QuantizeAndInverseFast(ref frameComponent, offset, ref computationBufferSpan, ref multiplierSpan); |
|||
} |
|||
} |
|||
} |
|||
|
|||
component.Output = frameComponent.BlockData; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Builds the huffman tables
|
|||
/// </summary>
|
|||
/// <param name="tables">The tables</param>
|
|||
/// <param name="index">The table index</param>
|
|||
/// <param name="codeLengths">The codelengths</param>
|
|||
/// <param name="values">The values</param>
|
|||
private void BuildHuffmanTable(HuffmanTables tables, int index, byte[] codeLengths, byte[] values) |
|||
{ |
|||
tables[index] = new HuffmanTable(codeLengths, values); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Allocates the frame component blocks
|
|||
/// </summary>
|
|||
private void PrepareComponents() |
|||
{ |
|||
int mcusPerLine = (int)MathF.Ceiling(this.frame.SamplesPerLine / 8F / this.frame.MaxHorizontalFactor); |
|||
int mcusPerColumn = (int)MathF.Ceiling(this.frame.Scanlines / 8F / this.frame.MaxVerticalFactor); |
|||
|
|||
for (int i = 0; i < this.frame.ComponentCount; i++) |
|||
{ |
|||
ref var component = ref this.frame.Components[i]; |
|||
int blocksPerLine = (int)MathF.Ceiling(MathF.Ceiling(this.frame.SamplesPerLine / 8F) * component.HorizontalFactor / this.frame.MaxHorizontalFactor); |
|||
int blocksPerColumn = (int)MathF.Ceiling(MathF.Ceiling(this.frame.Scanlines / 8F) * component.VerticalFactor / this.frame.MaxVerticalFactor); |
|||
int blocksPerLineForMcu = mcusPerLine * component.HorizontalFactor; |
|||
int blocksPerColumnForMcu = mcusPerColumn * component.VerticalFactor; |
|||
|
|||
int blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); |
|||
|
|||
// Pooled. Disposed via frame disposal
|
|||
component.BlockData = Buffer<short>.CreateClean(blocksBufferSize); |
|||
component.BlocksPerLine = blocksPerLine; |
|||
component.BlocksPerColumn = blocksPerColumn; |
|||
} |
|||
|
|||
this.frame.McusPerLine = mcusPerLine; |
|||
this.frame.McusPerColumn = mcusPerColumn; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void FillGrayScaleImage<TPixel>(Image<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
Span<TPixel> imageRowSpan = image.GetRowSpan(y); |
|||
Span<byte> areaRowSpan = this.pixelArea.GetRowSpan(y); |
|||
|
|||
for (int x = 0; x < image.Width; x++) |
|||
{ |
|||
ref byte luminance = ref areaRowSpan[x]; |
|||
ref TPixel pixel = ref imageRowSpan[x]; |
|||
var rgba = new Rgba32(luminance, luminance, luminance); |
|||
pixel.PackFromRgba32(rgba); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void FillYCbCrImage<TPixel>(Image<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
Span<TPixel> imageRowSpan = image.GetRowSpan(y); |
|||
Span<byte> areaRowSpan = this.pixelArea.GetRowSpan(y); |
|||
for (int x = 0, o = 0; x < image.Width; x++, o += 3) |
|||
{ |
|||
ref byte yy = ref areaRowSpan[o]; |
|||
ref byte cb = ref areaRowSpan[o + 1]; |
|||
ref byte cr = ref areaRowSpan[o + 2]; |
|||
ref TPixel pixel = ref imageRowSpan[x]; |
|||
YCbCrToRgbTables.PackYCbCr(ref pixel, yy, cb, cr); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void FillYcckImage<TPixel>(Image<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
Span<TPixel> imageRowSpan = image.GetRowSpan(y); |
|||
Span<byte> areaRowSpan = this.pixelArea.GetRowSpan(y); |
|||
for (int x = 0, o = 0; x < image.Width; x++, o += 4) |
|||
{ |
|||
ref byte yy = ref areaRowSpan[o]; |
|||
ref byte cb = ref areaRowSpan[o + 1]; |
|||
ref byte cr = ref areaRowSpan[o + 2]; |
|||
ref byte k = ref areaRowSpan[o + 3]; |
|||
|
|||
ref TPixel pixel = ref imageRowSpan[x]; |
|||
YCbCrToRgbTables.PackYccK(ref pixel, yy, cb, cr, k); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void FillCmykImage<TPixel>(Image<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
Span<TPixel> imageRowSpan = image.GetRowSpan(y); |
|||
Span<byte> areaRowSpan = this.pixelArea.GetRowSpan(y); |
|||
for (int x = 0, o = 0; x < image.Width; x++, o += 4) |
|||
{ |
|||
ref byte c = ref areaRowSpan[o]; |
|||
ref byte m = ref areaRowSpan[o + 1]; |
|||
ref byte cy = ref areaRowSpan[o + 2]; |
|||
ref byte k = ref areaRowSpan[o + 3]; |
|||
|
|||
byte r = (byte)((c * k) / 255); |
|||
byte g = (byte)((m * k) / 255); |
|||
byte b = (byte)((cy * k) / 255); |
|||
|
|||
ref TPixel pixel = ref imageRowSpan[x]; |
|||
var rgba = new Rgba32(r, g, b); |
|||
pixel.PackFromRgba32(rgba); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private void FillRgbImage<TPixel>(Image<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
Span<TPixel> imageRowSpan = image.GetRowSpan(y); |
|||
Span<byte> areaRowSpan = this.pixelArea.GetRowSpan(y); |
|||
|
|||
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(areaRowSpan, imageRowSpan, image.Width); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="ushort"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private ushort ReadUint16() |
|||
{ |
|||
this.InputStream.Read(this.markerBuffer, 0, 2); |
|||
return (ushort)((this.markerBuffer[0] << 8) | this.markerBuffer[1]); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue