|
|
|
@ -3,6 +3,8 @@ |
|
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
// </copyright>
|
|
|
|
|
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
|
|
|
|
namespace ImageSharp.Formats |
|
|
|
{ |
|
|
|
using System; |
|
|
|
@ -12,22 +14,22 @@ namespace ImageSharp.Formats |
|
|
|
/// <summary>
|
|
|
|
/// Performs the jpeg decoding operation.
|
|
|
|
/// </summary>
|
|
|
|
internal class JpegDecoderCore |
|
|
|
internal class JpegDecoderCore : IDisposable |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// The maximum (inclusive) number of bits in a Huffman code.
|
|
|
|
/// </summary>
|
|
|
|
private const int MaxCodeLength = 16; |
|
|
|
internal const int MaxCodeLength = 16; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum (inclusive) number of codes in a Huffman tree.
|
|
|
|
/// </summary>
|
|
|
|
private const int MaxNCodes = 256; |
|
|
|
internal const int MaxNCodes = 256; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The log-2 size of the Huffman decoder's look-up table.
|
|
|
|
/// </summary>
|
|
|
|
private const int LutSize = 8; |
|
|
|
internal const int LutSize = 8; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum number of color components
|
|
|
|
@ -44,6 +46,8 @@ namespace ImageSharp.Formats |
|
|
|
/// </summary>
|
|
|
|
private const int MaxTh = 3; |
|
|
|
|
|
|
|
private const int ThRowSize = MaxTh + 1; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The maximum number of quantization tables
|
|
|
|
/// </summary>
|
|
|
|
@ -85,7 +89,9 @@ namespace ImageSharp.Formats |
|
|
|
/// <summary>
|
|
|
|
/// The huffman trees
|
|
|
|
/// </summary>
|
|
|
|
private readonly Huffman[,] huffmanTrees; |
|
|
|
//private readonly Huffman[,] huffmanTrees;
|
|
|
|
|
|
|
|
private readonly Huffman[] huffmanTrees; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Quantization tables, in zigzag order.
|
|
|
|
@ -187,13 +193,17 @@ namespace ImageSharp.Formats |
|
|
|
/// </summary>
|
|
|
|
private short verticalResolution; |
|
|
|
|
|
|
|
private int blockIndex; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="JpegDecoderCore"/> class.
|
|
|
|
/// </summary>
|
|
|
|
public JpegDecoderCore() |
|
|
|
{ |
|
|
|
this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1]; |
|
|
|
this.quantizationTables = new Block[MaxTq + 1]; |
|
|
|
//this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
|
|
|
|
this.huffmanTrees = new Huffman[(MaxTc + 1)*(MaxTh + 1)]; |
|
|
|
|
|
|
|
this.quantizationTables = Block.CreateArray(MaxTq + 1); |
|
|
|
this.temp = new byte[2 * Block.BlockSize]; |
|
|
|
this.componentArray = new Component[MaxComponents]; |
|
|
|
this.progCoeffs = new Block[MaxComponents][]; |
|
|
|
@ -201,25 +211,29 @@ namespace ImageSharp.Formats |
|
|
|
this.bytes = new Bytes(); |
|
|
|
|
|
|
|
// TODO: This looks like it could be static.
|
|
|
|
|
|
|
|
for (int i = 0; i < MaxTc + 1; i++) |
|
|
|
{ |
|
|
|
for (int j = 0; j < MaxTh + 1; j++) |
|
|
|
{ |
|
|
|
this.huffmanTrees[i, j] = new Huffman(LutSize, MaxNCodes, MaxCodeLength); |
|
|
|
//this.huffmanTrees[i, j].Init(LutSize, MaxNCodes, MaxCodeLength);
|
|
|
|
this.huffmanTrees[i* ThRowSize + j].Init(LutSize, MaxNCodes, MaxCodeLength); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
for (int i = 0; i < this.quantizationTables.Length; i++) |
|
|
|
{ |
|
|
|
this.quantizationTables[i] = new Block(); |
|
|
|
} |
|
|
|
//for (int i = 0; i < this.quantizationTables.Length; i++)
|
|
|
|
//{
|
|
|
|
// //this.quantizationTables[i] = new Block();
|
|
|
|
// this.quantizationTables[i].Init();
|
|
|
|
//}
|
|
|
|
|
|
|
|
for (int i = 0; i < this.componentArray.Length; i++) |
|
|
|
{ |
|
|
|
this.componentArray[i] = new Component(); |
|
|
|
} |
|
|
|
//for (int i = 0; i < this.componentArray.Length; i++)
|
|
|
|
//{
|
|
|
|
// this.componentArray[i] = new Component();
|
|
|
|
//}
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Decodes the image from the specified this._stream and sets
|
|
|
|
/// the data to image.
|
|
|
|
@ -501,92 +515,96 @@ namespace ImageSharp.Formats |
|
|
|
throw new ImageFormatException("Bad Th value"); |
|
|
|
} |
|
|
|
|
|
|
|
Huffman huffman = this.huffmanTrees[tc, th]; |
|
|
|
ProcessDefineHuffmanTablesMarkerLoop(ref this.huffmanTrees[tc* ThRowSize + th], ref remaining); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Read nCodes and huffman.Valuess (and derive h.Length).
|
|
|
|
// nCodes[i] is the number of codes with code length i.
|
|
|
|
// h.Length is the total number of codes.
|
|
|
|
huffman.Length = 0; |
|
|
|
private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining) |
|
|
|
{ |
|
|
|
|
|
|
|
int[] ncodes = new int[MaxCodeLength]; |
|
|
|
for (int i = 0; i < ncodes.Length; i++) |
|
|
|
{ |
|
|
|
ncodes[i] = this.temp[i + 1]; |
|
|
|
huffman.Length += ncodes[i]; |
|
|
|
} |
|
|
|
// Read nCodes and huffman.Valuess (and derive h.Length).
|
|
|
|
// nCodes[i] is the number of codes with code length i.
|
|
|
|
// h.Length is the total number of codes.
|
|
|
|
huffman.Length = 0; |
|
|
|
|
|
|
|
if (huffman.Length == 0) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Huffman table has zero length"); |
|
|
|
} |
|
|
|
int[] ncodes = new int[MaxCodeLength]; |
|
|
|
for (int i = 0; i < ncodes.Length; i++) |
|
|
|
{ |
|
|
|
ncodes[i] = this.temp[i + 1]; |
|
|
|
huffman.Length += ncodes[i]; |
|
|
|
} |
|
|
|
|
|
|
|
if (huffman.Length > MaxNCodes) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Huffman table has excessive length"); |
|
|
|
} |
|
|
|
if (huffman.Length == 0) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Huffman table has zero length"); |
|
|
|
} |
|
|
|
|
|
|
|
remaining -= huffman.Length + 17; |
|
|
|
if (remaining < 0) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("DHT has wrong length"); |
|
|
|
} |
|
|
|
if (huffman.Length > MaxNCodes) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Huffman table has excessive length"); |
|
|
|
} |
|
|
|
|
|
|
|
remaining -= huffman.Length + 17; |
|
|
|
if (remaining < 0) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("DHT has wrong length"); |
|
|
|
} |
|
|
|
|
|
|
|
this.ReadFull(huffman.Values, 0, huffman.Length); |
|
|
|
this.ReadFull(huffman.Values, 0, huffman.Length); |
|
|
|
|
|
|
|
// Derive the look-up table.
|
|
|
|
for (int i = 0; i < huffman.Lut.Length; i++) |
|
|
|
{ |
|
|
|
huffman.Lut[i] = 0; |
|
|
|
} |
|
|
|
// Derive the look-up table.
|
|
|
|
for (int i = 0; i < huffman.Lut.Length; i++) |
|
|
|
{ |
|
|
|
huffman.Lut[i] = 0; |
|
|
|
} |
|
|
|
|
|
|
|
uint x = 0, code = 0; |
|
|
|
uint x = 0, code = 0; |
|
|
|
|
|
|
|
for (int i = 0; i < LutSize; i++) |
|
|
|
{ |
|
|
|
code <<= 1; |
|
|
|
|
|
|
|
for (int i = 0; i < LutSize; i++) |
|
|
|
for (int j = 0; j < ncodes[i]; j++) |
|
|
|
{ |
|
|
|
code <<= 1; |
|
|
|
// 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)); |
|
|
|
ushort lutValue = (ushort) ((huffman.Values[x] << 8) | (2 + i)); |
|
|
|
|
|
|
|
for (int j = 0; j < ncodes[i]; j++) |
|
|
|
for (int k = 0; k < 1 << (7 - i); k++) |
|
|
|
{ |
|
|
|
// 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)); |
|
|
|
ushort lutValue = (ushort)((huffman.Values[x] << 8) | (2 + i)); |
|
|
|
|
|
|
|
for (int k = 0; k < 1 << (7 - i); k++) |
|
|
|
{ |
|
|
|
huffman.Lut[base2 | k] = lutValue; |
|
|
|
} |
|
|
|
|
|
|
|
code++; |
|
|
|
x++; |
|
|
|
huffman.Lut[base2 | k] = lutValue; |
|
|
|
} |
|
|
|
|
|
|
|
code++; |
|
|
|
x++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Derive minCodes, maxCodes, and indices.
|
|
|
|
int c = 0, index = 0; |
|
|
|
for (int i = 0; i < ncodes.Length; i++) |
|
|
|
// Derive minCodes, maxCodes, and indices.
|
|
|
|
int c = 0, index = 0; |
|
|
|
for (int i = 0; i < ncodes.Length; i++) |
|
|
|
{ |
|
|
|
int nc = ncodes[i]; |
|
|
|
if (nc == 0) |
|
|
|
{ |
|
|
|
int nc = ncodes[i]; |
|
|
|
if (nc == 0) |
|
|
|
{ |
|
|
|
huffman.MinCodes[i] = -1; |
|
|
|
huffman.MaxCodes[i] = -1; |
|
|
|
huffman.Indices[i] = -1; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
huffman.MinCodes[i] = c; |
|
|
|
huffman.MaxCodes[i] = c + nc - 1; |
|
|
|
huffman.Indices[i] = index; |
|
|
|
c += nc; |
|
|
|
index += nc; |
|
|
|
} |
|
|
|
|
|
|
|
c <<= 1; |
|
|
|
huffman.MinCodes[i] = -1; |
|
|
|
huffman.MaxCodes[i] = -1; |
|
|
|
huffman.Indices[i] = -1; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
huffman.MinCodes[i] = c; |
|
|
|
huffman.MaxCodes[i] = c + nc - 1; |
|
|
|
huffman.Indices[i] = index; |
|
|
|
c += nc; |
|
|
|
index += nc; |
|
|
|
} |
|
|
|
|
|
|
|
c <<= 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -595,7 +613,7 @@ namespace ImageSharp.Formats |
|
|
|
/// </summary>
|
|
|
|
/// <param name="huffman">The huffman value</param>
|
|
|
|
/// <returns>The <see cref="byte"/></returns>
|
|
|
|
private byte DecodeHuffman(Huffman huffman) |
|
|
|
private byte DecodeHuffman(ref Huffman huffman) |
|
|
|
{ |
|
|
|
if (huffman.Length == 0) |
|
|
|
{ |
|
|
|
@ -1385,7 +1403,7 @@ namespace ImageSharp.Formats |
|
|
|
byte cr = this.ycbcrImage.CrChannel[co + (x / scale)]; |
|
|
|
|
|
|
|
TColor packed = default(TColor); |
|
|
|
this.PackYcbCr<TColor, TPacked>(ref packed, yy, cb, cr); |
|
|
|
PackYcbCr<TColor, TPacked>(ref packed, yy, cb, cr); |
|
|
|
pixels[x, y] = packed; |
|
|
|
} |
|
|
|
}); |
|
|
|
@ -1453,6 +1471,8 @@ namespace ImageSharp.Formats |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private Block scanWorkerBlock = Block.Create(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Processes the SOS (Start of scan marker).
|
|
|
|
/// </summary>
|
|
|
|
@ -1479,7 +1499,8 @@ namespace ImageSharp.Formats |
|
|
|
this.ReadFull(this.temp, 0, remaining); |
|
|
|
byte scanComponentCount = this.temp[0]; |
|
|
|
|
|
|
|
if (remaining != 4 + (2 * scanComponentCount)) |
|
|
|
int scanComponentCountX2 = 2 * scanComponentCount; |
|
|
|
if (remaining != 4 + scanComponentCountX2) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("SOS length inconsistent with number of components"); |
|
|
|
} |
|
|
|
@ -1489,51 +1510,7 @@ namespace ImageSharp.Formats |
|
|
|
|
|
|
|
for (int i = 0; i < scanComponentCount; i++) |
|
|
|
{ |
|
|
|
// Component selector.
|
|
|
|
int cs = this.temp[1 + (2 * i)]; |
|
|
|
int compIndex = -1; |
|
|
|
for (int j = 0; j < this.componentCount; j++) |
|
|
|
{ |
|
|
|
Component compv = this.componentArray[j]; |
|
|
|
if (cs == compv.Identifier) |
|
|
|
{ |
|
|
|
compIndex = j; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (compIndex < 0) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Unknown component selector"); |
|
|
|
} |
|
|
|
|
|
|
|
scan[i].Index = (byte)compIndex; |
|
|
|
|
|
|
|
// Section B.2.3 states that "the value of Cs_j shall be different from
|
|
|
|
// the values of Cs_1 through Cs_(j-1)". Since we have previously
|
|
|
|
// verified that a frame's component identifiers (C_i values in section
|
|
|
|
// B.2.2) are unique, it suffices to check that the implicit indexes
|
|
|
|
// into comp are unique.
|
|
|
|
for (int j = 0; j < i; j++) |
|
|
|
{ |
|
|
|
if (scan[i].Index == scan[j].Index) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Repeated component selector"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
totalHv += this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor; |
|
|
|
|
|
|
|
scan[i].DcTableSelector = (byte)(this.temp[2 + (2 * i)] >> 4); |
|
|
|
if (scan[i].DcTableSelector > MaxTh) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Bad DC table selector value"); |
|
|
|
} |
|
|
|
|
|
|
|
scan[i].AcTableSelector = (byte)(this.temp[2 + (2 * i)] & 0x0f); |
|
|
|
if (scan[i].AcTableSelector > MaxTh) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Bad AC table selector value"); |
|
|
|
} |
|
|
|
ProcessScanImpl(i, ref scan[i], scan, ref totalHv); |
|
|
|
} |
|
|
|
|
|
|
|
// Section B.2.3 states that if there is more than one component then the
|
|
|
|
@ -1564,10 +1541,10 @@ namespace ImageSharp.Formats |
|
|
|
|
|
|
|
if (this.isProgressive) |
|
|
|
{ |
|
|
|
zigStart = this.temp[1 + (2 * scanComponentCount)]; |
|
|
|
zigEnd = this.temp[2 + (2 * scanComponentCount)]; |
|
|
|
ah = this.temp[3 + (2 * scanComponentCount)] >> 4; |
|
|
|
al = this.temp[3 + (2 * scanComponentCount)] & 0x0f; |
|
|
|
zigStart = this.temp[1 + scanComponentCountX2]; |
|
|
|
zigEnd = this.temp[2 + scanComponentCountX2]; |
|
|
|
ah = this.temp[3 + scanComponentCountX2] >> 4; |
|
|
|
al = this.temp[3 + scanComponentCountX2] & 0x0f; |
|
|
|
|
|
|
|
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || Block.BlockSize <= zigEnd) |
|
|
|
{ |
|
|
|
@ -1603,11 +1580,11 @@ namespace ImageSharp.Formats |
|
|
|
int compIndex = scan[i].Index; |
|
|
|
if (this.progCoeffs[compIndex] == null) |
|
|
|
{ |
|
|
|
this.progCoeffs[compIndex] = new Block[mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor]; |
|
|
|
this.progCoeffs[compIndex] = Block.CreateArray(mxx * myy * this.componentArray[compIndex].HorizontalFactor * this.componentArray[compIndex].VerticalFactor); |
|
|
|
|
|
|
|
for (int j = 0; j < this.progCoeffs[compIndex].Length; j++) |
|
|
|
{ |
|
|
|
this.progCoeffs[compIndex][j] = new Block(); |
|
|
|
this.progCoeffs[compIndex][j].Init(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1619,7 +1596,7 @@ namespace ImageSharp.Formats |
|
|
|
byte expectedRst = JpegConstants.Markers.RST0; |
|
|
|
|
|
|
|
// b is the decoded coefficients block, in natural (not zig-zag) order.
|
|
|
|
Block b; |
|
|
|
//Block b;
|
|
|
|
int[] dc = new int[MaxComponents]; |
|
|
|
|
|
|
|
// bx and by are the location of the current block, in units of 8x8
|
|
|
|
@ -1635,7 +1612,7 @@ namespace ImageSharp.Formats |
|
|
|
int compIndex = scan[i].Index; |
|
|
|
int hi = this.componentArray[compIndex].HorizontalFactor; |
|
|
|
int vi = this.componentArray[compIndex].VerticalFactor; |
|
|
|
Block qt = this.quantizationTables[this.componentArray[compIndex].Selector]; |
|
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < hi * vi; j++) |
|
|
|
{ |
|
|
|
@ -1678,168 +1655,27 @@ namespace ImageSharp.Formats |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Load the previous partially decoded coefficients, if applicable.
|
|
|
|
b = this.isProgressive ? this.progCoeffs[compIndex][((@by * mxx) * hi) + bx] : new Block(); |
|
|
|
|
|
|
|
if (ah != 0) |
|
|
|
var qtIndex = this.componentArray[compIndex].Selector; |
|
|
|
|
|
|
|
if (this.isProgressive) // Load the previous partially decoded coefficients, if applicable.
|
|
|
|
{ |
|
|
|
this.Refine(b, this.huffmanTrees[AcTable, scan[i].AcTableSelector], zigStart, zigEnd, 1 << al); |
|
|
|
blockIndex = ((@by * mxx) * hi) + bx; |
|
|
|
ProcessBlockImpl(ah, |
|
|
|
ref this.progCoeffs[compIndex][blockIndex], |
|
|
|
scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi, bx, |
|
|
|
ref this.quantizationTables[qtIndex] |
|
|
|
); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
int zig = zigStart; |
|
|
|
if (zig == 0) |
|
|
|
{ |
|
|
|
zig++; |
|
|
|
|
|
|
|
// Decode the DC coefficient, as specified in section F.2.2.1.
|
|
|
|
byte value = this.DecodeHuffman(this.huffmanTrees[DcTable, scan[i].DcTableSelector]); |
|
|
|
if (value > 16) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Excessive DC component"); |
|
|
|
} |
|
|
|
|
|
|
|
int deltaDC = this.ReceiveExtend(value); |
|
|
|
dc[compIndex] += deltaDC; |
|
|
|
b[0] = dc[compIndex] << al; |
|
|
|
} |
|
|
|
|
|
|
|
if (zig <= zigEnd && this.eobRun > 0) |
|
|
|
{ |
|
|
|
this.eobRun--; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Decode the AC coefficients, as specified in section F.2.2.2.
|
|
|
|
Huffman huffv = this.huffmanTrees[AcTable, scan[i].AcTableSelector]; |
|
|
|
for (; zig <= zigEnd; zig++) |
|
|
|
{ |
|
|
|
byte value = this.DecodeHuffman(huffv); |
|
|
|
byte val0 = (byte)(value >> 4); |
|
|
|
byte val1 = (byte)(value & 0x0f); |
|
|
|
if (val1 != 0) |
|
|
|
{ |
|
|
|
zig += val0; |
|
|
|
if (zig > zigEnd) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
int ac = this.ReceiveExtend(val1); |
|
|
|
b[Unzig[zig]] = ac << al; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
if (val0 != 0x0f) |
|
|
|
{ |
|
|
|
this.eobRun = (ushort)(1 << val0); |
|
|
|
if (val0 != 0) |
|
|
|
{ |
|
|
|
this.eobRun |= (ushort)this.DecodeBits(val0); |
|
|
|
} |
|
|
|
|
|
|
|
this.eobRun--; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
zig += 0x0f; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (this.isProgressive) |
|
|
|
{ |
|
|
|
if (zigEnd != Block.BlockSize - 1 || al != 0) |
|
|
|
{ |
|
|
|
// We haven't completely decoded this 8x8 block. Save the coefficients.
|
|
|
|
this.progCoeffs[compIndex][((by * mxx) * hi) + bx] = b; |
|
|
|
|
|
|
|
// At this point, we could execute the rest of the loop body to dequantize and
|
|
|
|
// perform the inverse DCT, to save early stages of a progressive image to the
|
|
|
|
// *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
|
|
|
|
// the jpeg.Decode function does not return until the entire image is decoded,
|
|
|
|
// so we "continue" here to avoid wasted computation.
|
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Dequantize, perform the inverse DCT and store the block to the image.
|
|
|
|
for (int zig = 0; zig < Block.BlockSize; zig++) |
|
|
|
{ |
|
|
|
b[Unzig[zig]] *= qt[zig]; |
|
|
|
} |
|
|
|
|
|
|
|
IDCT.Transform(b); |
|
|
|
|
|
|
|
byte[] dst; |
|
|
|
int offset; |
|
|
|
int stride; |
|
|
|
|
|
|
|
if (this.componentCount == 1) |
|
|
|
{ |
|
|
|
dst = this.grayImage.Pixels; |
|
|
|
stride = this.grayImage.Stride; |
|
|
|
offset = this.grayImage.Offset + (8 * ((by * this.grayImage.Stride) + bx)); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
switch (compIndex) |
|
|
|
{ |
|
|
|
case 0: |
|
|
|
dst = this.ycbcrImage.YChannel; |
|
|
|
stride = this.ycbcrImage.YStride; |
|
|
|
offset = this.ycbcrImage.YOffset + (8 * ((by * this.ycbcrImage.YStride) + bx)); |
|
|
|
break; |
|
|
|
|
|
|
|
case 1: |
|
|
|
dst = this.ycbcrImage.CbChannel; |
|
|
|
stride = this.ycbcrImage.CStride; |
|
|
|
offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx)); |
|
|
|
break; |
|
|
|
|
|
|
|
case 2: |
|
|
|
dst = this.ycbcrImage.CrChannel; |
|
|
|
stride = this.ycbcrImage.CStride; |
|
|
|
offset = this.ycbcrImage.COffset + (8 * ((by * this.ycbcrImage.CStride) + bx)); |
|
|
|
break; |
|
|
|
|
|
|
|
case 3: |
|
|
|
|
|
|
|
dst = this.blackPixels; |
|
|
|
stride = this.blackStride; |
|
|
|
offset = 8 * ((by * this.blackStride) + bx); |
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
throw new ImageFormatException("Too many components"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Level shift by +128, clip to [0, 255], and write to dst.
|
|
|
|
for (int y = 0; y < 8; y++) |
|
|
|
{ |
|
|
|
int y8 = y * 8; |
|
|
|
int yStride = y * stride; |
|
|
|
|
|
|
|
for (int x = 0; x < 8; x++) |
|
|
|
{ |
|
|
|
int c = b[y8 + x]; |
|
|
|
if (c < -128) |
|
|
|
{ |
|
|
|
c = 0; |
|
|
|
} |
|
|
|
else if (c > 127) |
|
|
|
{ |
|
|
|
c = 255; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
c += 128; |
|
|
|
} |
|
|
|
|
|
|
|
dst[yStride + x + offset] = (byte)c; |
|
|
|
} |
|
|
|
//var b = Block.Create();
|
|
|
|
scanWorkerBlock.Clear(); |
|
|
|
|
|
|
|
ProcessBlockImpl(ah, ref scanWorkerBlock, scan, i, zigStart, zigEnd, al, dc, compIndex, @by, mxx, hi, |
|
|
|
bx, ref this.quantizationTables[qtIndex] |
|
|
|
); |
|
|
|
|
|
|
|
//b.Dispose();
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -1882,6 +1718,237 @@ namespace ImageSharp.Formats |
|
|
|
// for my
|
|
|
|
} |
|
|
|
|
|
|
|
private void ProcessBlockImpl(int ah, ref Block b, Scan[] scan, int i, int zigStart, int zigEnd, int al, |
|
|
|
int[] dc, int compIndex, int @by, int mxx, int hi, int bx, ref Block qt) |
|
|
|
{ |
|
|
|
if (ah != 0) |
|
|
|
{ |
|
|
|
this.Refine(ref b, ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector], zigStart, zigEnd, 1 << al); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
int zig = zigStart; |
|
|
|
if (zig == 0) |
|
|
|
{ |
|
|
|
zig++; |
|
|
|
|
|
|
|
// Decode the DC coefficient, as specified in section F.2.2.1.
|
|
|
|
byte value = this.DecodeHuffman(ref this.huffmanTrees[DcTable * ThRowSize + scan[i].DcTableSelector]); |
|
|
|
if (value > 16) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Excessive DC component"); |
|
|
|
} |
|
|
|
|
|
|
|
int deltaDC = this.ReceiveExtend(value); |
|
|
|
dc[compIndex] += deltaDC; |
|
|
|
b[0] = dc[compIndex] << al; |
|
|
|
} |
|
|
|
|
|
|
|
if (zig <= zigEnd && this.eobRun > 0) |
|
|
|
{ |
|
|
|
this.eobRun--; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Decode the AC coefficients, as specified in section F.2.2.2.
|
|
|
|
//Huffman huffv = ;
|
|
|
|
for (; zig <= zigEnd; zig++) |
|
|
|
{ |
|
|
|
byte value = this.DecodeHuffman(ref this.huffmanTrees[AcTable * ThRowSize + scan[i].AcTableSelector]); |
|
|
|
byte val0 = (byte) (value >> 4); |
|
|
|
byte val1 = (byte) (value & 0x0f); |
|
|
|
if (val1 != 0) |
|
|
|
{ |
|
|
|
zig += val0; |
|
|
|
if (zig > zigEnd) |
|
|
|
{ |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
int ac = this.ReceiveExtend(val1); |
|
|
|
b[Unzig[zig]] = ac << al; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
if (val0 != 0x0f) |
|
|
|
{ |
|
|
|
this.eobRun = (ushort) (1 << val0); |
|
|
|
if (val0 != 0) |
|
|
|
{ |
|
|
|
this.eobRun |= (ushort) this.DecodeBits(val0); |
|
|
|
} |
|
|
|
|
|
|
|
this.eobRun--; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
zig += 0x0f; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (this.isProgressive) |
|
|
|
{ |
|
|
|
if (zigEnd != Block.BlockSize - 1 || al != 0) |
|
|
|
{ |
|
|
|
// We haven't completely decoded this 8x8 block. Save the coefficients.
|
|
|
|
|
|
|
|
this.progCoeffs[compIndex][((@by*mxx)*hi) + bx] = b.Clone(); |
|
|
|
|
|
|
|
// At this point, we could execute the rest of the loop body to dequantize and
|
|
|
|
// perform the inverse DCT, to save early stages of a progressive image to the
|
|
|
|
// *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
|
|
|
|
// the jpeg.Decode function does not return until the entire image is decoded,
|
|
|
|
// so we "continue" here to avoid wasted computation.
|
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Dequantize, perform the inverse DCT and store the block to the image.
|
|
|
|
for (int zig = 0; zig < Block.BlockSize; zig++) |
|
|
|
{ |
|
|
|
b[Unzig[zig]] *= qt[zig]; |
|
|
|
} |
|
|
|
|
|
|
|
IDCT.Transform(ref b); |
|
|
|
|
|
|
|
// ******* Other experimental variants: *************
|
|
|
|
|
|
|
|
// FluxJpeg:
|
|
|
|
// https://github.com/antonfirsov/ImageSharp/blob/master/src/ImageSharp46/Formats/Jpg/Components/FloatIDCT.cs
|
|
|
|
// FloatIDCT.Transform(ref b);
|
|
|
|
|
|
|
|
// SIMD-based:
|
|
|
|
// https://github.com/antonfirsov/ImageSharp/blob/master/src/ImageSharp46/Formats/Jpg/Components/MagicDCT.cs
|
|
|
|
// MagicDCT.IDCT(ref b);
|
|
|
|
|
|
|
|
byte[] dst; |
|
|
|
int offset; |
|
|
|
int stride; |
|
|
|
|
|
|
|
if (this.componentCount == 1) |
|
|
|
{ |
|
|
|
dst = this.grayImage.Pixels; |
|
|
|
stride = this.grayImage.Stride; |
|
|
|
offset = this.grayImage.Offset + (8*((@by*this.grayImage.Stride) + bx)); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
switch (compIndex) |
|
|
|
{ |
|
|
|
case 0: |
|
|
|
dst = this.ycbcrImage.YChannel; |
|
|
|
stride = this.ycbcrImage.YStride; |
|
|
|
offset = this.ycbcrImage.YOffset + (8*((@by*this.ycbcrImage.YStride) + bx)); |
|
|
|
break; |
|
|
|
|
|
|
|
case 1: |
|
|
|
dst = this.ycbcrImage.CbChannel; |
|
|
|
stride = this.ycbcrImage.CStride; |
|
|
|
offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx)); |
|
|
|
break; |
|
|
|
|
|
|
|
case 2: |
|
|
|
dst = this.ycbcrImage.CrChannel; |
|
|
|
stride = this.ycbcrImage.CStride; |
|
|
|
offset = this.ycbcrImage.COffset + (8*((@by*this.ycbcrImage.CStride) + bx)); |
|
|
|
break; |
|
|
|
|
|
|
|
case 3: |
|
|
|
|
|
|
|
dst = this.blackPixels; |
|
|
|
stride = this.blackStride; |
|
|
|
offset = 8*((@by*this.blackStride) + bx); |
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
throw new ImageFormatException("Too many components"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Level shift by +128, clip to [0, 255], and write to dst.
|
|
|
|
for (int y = 0; y < 8; y++) |
|
|
|
{ |
|
|
|
int y8 = y*8; |
|
|
|
int yStride = y*stride; |
|
|
|
|
|
|
|
for (int x = 0; x < 8; x++) |
|
|
|
{ |
|
|
|
int c = b[y8 + x]; |
|
|
|
if (c < -128) |
|
|
|
{ |
|
|
|
c = 0; |
|
|
|
} |
|
|
|
else if (c > 127) |
|
|
|
{ |
|
|
|
c = 255; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
c += 128; |
|
|
|
} |
|
|
|
|
|
|
|
dst[yStride + x + offset] = (byte) c; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void ProcessScanImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv) |
|
|
|
{ |
|
|
|
// Component selector.
|
|
|
|
int cs = this.temp[1 + (2 * i)]; |
|
|
|
int compIndex = -1; |
|
|
|
for (int j = 0; j < this.componentCount; j++) |
|
|
|
{ |
|
|
|
//Component compv = ;
|
|
|
|
if (cs == this.componentArray[j].Identifier) |
|
|
|
{ |
|
|
|
compIndex = j; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (compIndex < 0) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Unknown component selector"); |
|
|
|
} |
|
|
|
|
|
|
|
currentScan.Index = (byte)compIndex; |
|
|
|
|
|
|
|
ProcessComponentImpl(i, ref currentScan, scan, ref totalHv, ref this.componentArray[compIndex]); |
|
|
|
} |
|
|
|
|
|
|
|
private void ProcessComponentImpl(int i, ref Scan currentScan, Scan[] scan, ref int totalHv, ref Component currentComponent) |
|
|
|
{ |
|
|
|
// Section B.2.3 states that "the value of Cs_j shall be different from
|
|
|
|
// the values of Cs_1 through Cs_(j-1)". Since we have previously
|
|
|
|
// verified that a frame's component identifiers (C_i values in section
|
|
|
|
// B.2.2) are unique, it suffices to check that the implicit indexes
|
|
|
|
// into comp are unique.
|
|
|
|
for (int j = 0; j < i; j++) |
|
|
|
{ |
|
|
|
if (currentScan.Index == scan[j].Index) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Repeated component selector"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
totalHv += currentComponent.HorizontalFactor*currentComponent.VerticalFactor; |
|
|
|
|
|
|
|
currentScan.DcTableSelector = (byte) (this.temp[2 + (2*i)] >> 4); |
|
|
|
if (currentScan.DcTableSelector > MaxTh) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Bad DC table selector value"); |
|
|
|
} |
|
|
|
|
|
|
|
currentScan.AcTableSelector = (byte) (this.temp[2 + (2*i)] & 0x0f); |
|
|
|
if (currentScan.AcTableSelector > MaxTh) |
|
|
|
{ |
|
|
|
throw new ImageFormatException("Bad AC table selector value"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
|
|
|
|
/// </summary>
|
|
|
|
@ -1890,7 +1957,7 @@ namespace ImageSharp.Formats |
|
|
|
/// <param name="zigStart">The zig-zag start index</param>
|
|
|
|
/// <param name="zigEnd">The zig-zag end index</param>
|
|
|
|
/// <param name="delta">The low transform offset</param>
|
|
|
|
private void Refine(Block b, Huffman h, int zigStart, int zigEnd, int delta) |
|
|
|
private void Refine(ref Block b, ref Huffman h, int zigStart, int zigEnd, int delta) |
|
|
|
{ |
|
|
|
// Refining a DC component is trivial.
|
|
|
|
if (zigStart == 0) |
|
|
|
@ -1917,7 +1984,7 @@ namespace ImageSharp.Formats |
|
|
|
{ |
|
|
|
bool done = false; |
|
|
|
int z = 0; |
|
|
|
byte val = this.DecodeHuffman(h); |
|
|
|
byte val = this.DecodeHuffman(ref h); |
|
|
|
int val0 = val >> 4; |
|
|
|
int val1 = val & 0x0f; |
|
|
|
|
|
|
|
@ -2107,7 +2174,8 @@ namespace ImageSharp.Formats |
|
|
|
/// <param name="y">The y luminance component.</param>
|
|
|
|
/// <param name="cb">The cb chroma component.</param>
|
|
|
|
/// <param name="cr">The cr chroma component.</param>
|
|
|
|
private void PackYcbCr<TColor, TPacked>(ref TColor packed, byte y, byte cb, byte cr) |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private static void PackYcbCr<TColor, TPacked>(ref TColor packed, byte y, byte cb, byte cr) |
|
|
|
where TColor : struct, IPackedPixel<TPacked> |
|
|
|
where TPacked : struct |
|
|
|
{ |
|
|
|
@ -2199,5 +2267,24 @@ namespace ImageSharp.Formats |
|
|
|
private class EOFException : Exception |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
scanWorkerBlock.Dispose(); |
|
|
|
Block.DisposeAll(this.quantizationTables); |
|
|
|
|
|
|
|
foreach (Block[] blocks in progCoeffs) |
|
|
|
{ |
|
|
|
if (blocks != null) |
|
|
|
{ |
|
|
|
Block.DisposeAll(blocks); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
for (int i = 0; i < huffmanTrees.Length; i++) |
|
|
|
{ |
|
|
|
huffmanTrees[i].Dispose(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|