Browse Source

copied pdfjs classes from the jpeg-port branch

af/merge-core
Anton Firszov 9 years ago
parent
commit
5f18f802dc
  1. 74
      src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs
  2. 44
      src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs
  3. 34
      src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs
  4. 59
      src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs
  5. 89
      src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs
  6. 74
      src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs
  7. 212
      src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs
  8. 42
      src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs
  9. 511
      src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs
  10. 79
      src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs
  11. 147
      src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs
  12. 67
      src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs
  13. 947
      src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs
  14. 128
      src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs
  15. 359
      src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs
  16. 966
      src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs

74
src/ImageSharp/Formats/Jpeg/Port/Components/Adobe.cs

@ -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;
}
}
}
}

44
src/ImageSharp/Formats/Jpeg/Port/Components/Component.cs

@ -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;
}
}
}

34
src/ImageSharp/Formats/Jpeg/Port/Components/ComponentBlocks.cs

@ -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;
}
}
}
}

59
src/ImageSharp/Formats/Jpeg/Port/Components/FileMarker.cs

@ -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");
}
}
}

89
src/ImageSharp/Formats/Jpeg/Port/Components/Frame.cs

@ -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;
}
}
}
}

74
src/ImageSharp/Formats/Jpeg/Port/Components/FrameComponent.cs

@ -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;
}
}
}

212
src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTable.cs

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

42
src/ImageSharp/Formats/Jpeg/Port/Components/HuffmanTables.cs

@ -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();
}
}
}
}

511
src/ImageSharp/Formats/Jpeg/Port/Components/IDCT.cs

@ -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 &amp; 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 &amp; Mitchell
/// JPEG textbook(see REFERENCES section in file README.ijg). The following
/// code is based directly on figure 4-8 in P&amp;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&amp;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;
}
}
}

79
src/ImageSharp/Formats/Jpeg/Port/Components/JFif.cs

@ -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;
}
}
}
}

147
src/ImageSharp/Formats/Jpeg/Port/Components/JpegPixelArea.cs

@ -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.");
}
}
}
}

67
src/ImageSharp/Formats/Jpeg/Port/Components/QuantizationTables.cs

@ -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;
}
}
}
}

947
src/ImageSharp/Formats/Jpeg/Port/Components/ScanDecoder.cs

@ -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;
}
}
}
}
}

128
src/ImageSharp/Formats/Jpeg/Port/Components/YCbCrToRgbTables.cs

@ -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;
}
}
}

359
src/ImageSharp/Formats/Jpeg/Port/JpegConstants.cs

@ -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;
}
}
}
}

966
src/ImageSharp/Formats/Jpeg/Port/JpegDecoderCore.cs

@ -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…
Cancel
Save