Browse Source

Merge after rebase:

Merge branch 'without46' of https://github.com/antonfirsov/ImageSharp into without46

Conflicts:
	src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
	src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
	src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
	src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
	src/ImageSharp/Formats/Png/Filters/SubFilter.cs
	src/ImageSharp/Formats/Png/Filters/UpFilter.cs
	src/ImageSharp/Formats/Png/PngDecoderCore.cs
	src/ImageSharp/Formats/Png/PngEncoderCore.cs
af/merge-core
Anton Firszov 9 years ago
parent
commit
e4032239fe
  1. 79
      src/ImageSharp/Formats/Jpg/Components/Block.cs
  2. 10
      src/ImageSharp/Formats/Jpg/Components/Component.cs
  3. 2
      src/ImageSharp/Formats/Jpg/Components/FDCT.cs
  4. 65
      src/ImageSharp/Formats/Jpg/Components/Huffman.cs
  5. 5
      src/ImageSharp/Formats/Jpg/Components/IDCT.cs
  6. 6
      src/ImageSharp/Formats/Jpg/JpegDecoder.cs
  7. 691
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  8. 60
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  9. 22
      tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs
  10. 2
      tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs
  11. 85
      tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
  12. 2
      tests/ImageSharp.Tests/Formats/Png/PngTests.cs
  13. 9
      tests/ImageSharp.Tests/TestImages.cs
  14. 3
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/geneserath.jpg

79
src/ImageSharp/Formats/Jpg/Components/Block.cs

@ -3,13 +3,19 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace ImageSharp.Formats
{
/// <summary>
/// Represents an 8x8 block of coefficients to transform and encode.
/// </summary>
internal class Block
public struct Block : IDisposable
{
private static ArrayPool<int> IntArrayPool = ArrayPool<int>.Create(BlockSize, 50);
/// <summary>
/// Gets the size of the block.
/// </summary>
@ -18,16 +24,41 @@ namespace ImageSharp.Formats
/// <summary>
/// The array of block data.
/// </summary>
private readonly int[] data;
public int[] Data;
/// <summary>
/// Initializes a new instance of the <see cref="Block"/> class.
/// </summary>
public Block()
//public Block()
//{
// this.data = new int[BlockSize];
//}
public void Init()
{
//this.Data = new int[BlockSize];
this.Data = IntArrayPool.Rent(BlockSize);
}
public static Block Create()
{
this.data = new int[BlockSize];
var block = new Block();
block.Init();
return block;
}
public static Block[] CreateArray(int size)
{
Block[] result = new Block[size];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
public bool IsInitialized => this.Data != null;
/// <summary>
/// Gets the pixel data at the given block index.
/// </summary>
@ -37,8 +68,44 @@ namespace ImageSharp.Formats
/// </returns>
public int this[int index]
{
get { return this.data[index]; }
set { this.data[index] = value; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return this.Data[index]; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set { this.Data[index] = value; }
}
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
public void Dispose()
{
if (Data != null)
{
IntArrayPool.Return(Data, true);
Data = null;
}
}
public static void DisposeAll(Block[] blocks)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
}
public void Clear()
{
for (int i = 0; i < Data.Length; i++)
{
Data[i] = 0;
}
}
public Block Clone()
{
Block clone = Create();
Array.Copy(Data, clone.Data, BlockSize);
return clone;
}
}
}

10
src/ImageSharp/Formats/Jpg/Components/Component.cs

@ -8,26 +8,26 @@ namespace ImageSharp.Formats
/// <summary>
/// Represents a single color component
/// </summary>
internal class Component
internal struct Component
{
/// <summary>
/// Gets or sets the horizontal sampling factor.
/// </summary>
public int HorizontalFactor { get; set; }
public int HorizontalFactor;
/// <summary>
/// Gets or sets the vertical sampling factor.
/// </summary>
public int VerticalFactor { get; set; }
public int VerticalFactor;
/// <summary>
/// Gets or sets the identifier
/// </summary>
public byte Identifier { get; set; }
public byte Identifier;
/// <summary>
/// Gets or sets the quantization table destination selector.
/// </summary>
public byte Selector { get; set; }
public byte Selector;
}
}

2
src/ImageSharp/Formats/Jpg/Components/FDCT.cs

@ -45,7 +45,7 @@ namespace ImageSharp.Formats
/// Performs a forward DCT on an 8x8 block of coefficients, including a level shift.
/// </summary>
/// <param name="block">The block of coefficients.</param>
public static void Transform(Block block)
public static void Transform(ref Block block)
{
// Pass 1: process rows.
for (int y = 0; y < 8; y++)

65
src/ImageSharp/Formats/Jpg/Components/Huffman.cs

@ -3,33 +3,47 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System;
using System.Buffers;
namespace ImageSharp.Formats
{
/// <summary>
/// Represents a Huffman tree
/// </summary>
internal class Huffman
internal struct Huffman : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="Huffman"/> class.
/// </summary>
/// <param name="lutSize">The log-2 size of the Huffman decoder's look-up table.</param>
/// <param name="maxNCodes">The maximum (inclusive) number of codes in a Huffman tree.</param>
/// <param name="maxCodeLength">The maximum (inclusive) number of bits in a Huffman code.</param>
public Huffman(int lutSize, int maxNCodes, int maxCodeLength)
private static ArrayPool<ushort> UshortBuffer =
//ArrayPool<ushort>.Shared;
ArrayPool<ushort>.Create(1 << JpegDecoderCore.LutSize, 50);
private static ArrayPool<byte> ByteBuffer =
//ArrayPool<byte>.Shared;
ArrayPool<byte>.Create(JpegDecoderCore.MaxNCodes, 50);
private static readonly ArrayPool<int> IntBuffer =
//ArrayPool<int>.Shared;
ArrayPool<int>.Create(JpegDecoderCore.MaxCodeLength, 50);
public void Init(int lutSize, int maxNCodes, int maxCodeLength)
{
this.Lut = new ushort[1 << lutSize];
this.Values = new byte[maxNCodes];
this.MinCodes = new int[maxCodeLength];
this.MaxCodes = new int[maxCodeLength];
this.Indices = new int[maxCodeLength];
this.Length = 0;
//this.Lut = new ushort[1 << lutSize];
//this.Values = new byte[maxNCodes];
//this.MinCodes = new int[maxCodeLength];
//this.MaxCodes = new int[maxCodeLength];
//this.Indices = new int[maxCodeLength];
this.Lut = UshortBuffer.Rent(1 << lutSize);
this.Values = ByteBuffer.Rent(maxNCodes);
this.MinCodes = IntBuffer.Rent(maxCodeLength);
this.MaxCodes = IntBuffer.Rent(maxCodeLength); ;
this.Indices = IntBuffer.Rent(maxCodeLength); ;
}
/// <summary>
/// Gets or sets the number of codes in the tree.
/// </summary>
public int Length { get; set; }
public int Length;
/// <summary>
/// Gets the look-up table for the next LutSize bits in the bit-stream.
@ -37,28 +51,39 @@ namespace ImageSharp.Formats
/// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits.
/// </summary>
public ushort[] Lut { get; }
public ushort[] Lut;
/// <summary>
/// Gets the the decoded values, sorted by their encoding.
/// </summary>
public byte[] Values { get; }
public byte[] Values;
/// <summary>
/// Gets the array of minimum codes.
/// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
/// </summary>
public int[] MinCodes { get; }
public int[] MinCodes;
/// <summary>
/// Gets the array of maximum codes.
/// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
/// </summary>
public int[] MaxCodes { get; }
public int[] MaxCodes;
/// <summary>
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
/// </summary>
public int[] Indices { get; }
public int[] Indices;
public void Dispose()
{
UshortBuffer.Return(Lut, true);
ByteBuffer.Return(Values, true);
IntBuffer.Return(MinCodes, true);
IntBuffer.Return(MaxCodes, true);
IntBuffer.Return(Indices, true);
}
}
}

5
src/ImageSharp/Formats/Jpg/Components/IDCT.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Numerics;
namespace ImageSharp.Formats
{
/// <summary>
@ -39,7 +41,7 @@ namespace ImageSharp.Formats
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
/// </summary>
/// <param name="src">The source block of coefficients</param>
public static void Transform(Block src)
public static void Transform(ref Block src)
{
// Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++)
@ -165,5 +167,6 @@ namespace ImageSharp.Formats
src[56 + x] = (y7 - y1) >> 14;
}
}
}
}

6
src/ImageSharp/Formats/Jpg/JpegDecoder.cs

@ -83,8 +83,10 @@ namespace ImageSharp.Formats
Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream");
JpegDecoderCore decoder = new JpegDecoderCore();
decoder.Decode(image, stream, false);
using (JpegDecoderCore decoder = new JpegDecoderCore())
{
decoder.Decode(image, stream, false);
}
}
/// <summary>

691
src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs

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

60
src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs

@ -487,9 +487,9 @@ namespace ImageSharp.Formats
/// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param>
/// <returns>The <see cref="int"/></returns>
private int WriteBlock(Block block, QuantIndex index, int prevDC)
private int WriteBlock(ref Block block, QuantIndex index, int prevDC)
{
FDCT.Transform(block);
FDCT.Transform(ref block);
// Emit the DC delta.
int dc = Round(block[0], 8 * this.quant[(int)index][0]);
@ -540,7 +540,8 @@ namespace ImageSharp.Formats
/// <param name="cbBlock">The red chroma block.</param>
/// <param name="crBlock">The blue chroma block.</param>
// ReSharper disable StyleCop.SA1305
private void ToYCbCr<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock)
private void ToYCbCr<TColor, TPacked>(PixelAccessor<TColor, TPacked> pixels, int x, int y,
ref Block yBlock, ref Block cbBlock, ref Block crBlock)
// ReSharper restore StyleCop.SA1305
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
@ -577,7 +578,7 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="destination">The destination block array</param>
/// <param name="source">The source block array.</param>
private void Scale16X16To8X8(Block destination, Block[] source)
private void Scale16X16To8X8(ref Block destination, Block[] source)
{
for (int i = 0; i < 4; i++)
{
@ -847,9 +848,9 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
Block b = new Block();
Block cb = new Block();
Block cr = new Block();
Block b = Block.Create();
Block cb = Block.Create();
Block cr = Block.Create();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@ -858,12 +859,15 @@ namespace ImageSharp.Formats
{
for (int x = 0; x < pixels.Width; x += 8)
{
this.ToYCbCr(pixels, x, y, b, cb, cr);
prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY);
prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb);
prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr);
this.ToYCbCr(pixels, x, y, ref b, ref cb, ref cr);
prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
prevDCCb = this.WriteBlock(ref cb, QuantIndex.Chrominance, prevDCCb);
prevDCCr = this.WriteBlock(ref cr, QuantIndex.Chrominance, prevDCCr);
}
}
b.Dispose();
cb.Dispose();
cr.Dispose();
}
/// <summary>
@ -877,23 +881,13 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
Block b = new Block();
Block[] cb = new Block[4];
Block[] cr = new Block[4];
Block b = Block.Create();
Block[] cb = Block.CreateArray(4);
Block[] cr = Block.CreateArray(4);
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
for (int i = 0; i < 4; i++)
{
cb[i] = new Block();
}
for (int i = 0; i < 4; i++)
{
cr[i] = new Block();
}
for (int y = 0; y < pixels.Height; y += 16)
{
for (int x = 0; x < pixels.Width; x += 16)
@ -903,16 +897,20 @@ namespace ImageSharp.Formats
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]);
prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY);
this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]);
prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
}
this.Scale16X16To8X8(b, cb);
prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb);
this.Scale16X16To8X8(b, cr);
prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr);
this.Scale16X16To8X8(ref b, cb);
prevDCCb = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCb);
this.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(ref b, QuantIndex.Chrominance, prevDCCr);
}
}
b.Dispose();
Block.DisposeAll(cb);
Block.DisposeAll(cr);
}
/// <summary>

22
tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs

@ -26,17 +26,17 @@ namespace ImageSharp.Benchmarks.Image
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public Size JpegSystemDrawing()
{
using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes))
{
using (Image image = Image.FromStream(memoryStream))
{
return image.Size;
}
}
}
//[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
//public Size JpegSystemDrawing()
//{
// using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes))
// {
// using (Image image = Image.FromStream(memoryStream))
// {
// return image.Size;
// }
// }
//}
[Benchmark(Description = "ImageSharp Jpeg")]
public CoreSize JpegCore()

2
tests/ImageSharp.Tests/Formats/Bmp/BitmapTests.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
using ImageSharp.Formats;
namespace ImageSharp.Tests
{
using System.IO;

85
tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ImageSharp.Formats;
using Xunit;
using Xunit.Abstractions;
namespace ImageSharp.Tests.Formats.Jpg
{
public class JpegTests
{
public const string TestOutputDirectory = "TestOutput/Jpeg";
private ITestOutputHelper Output { get; }
public JpegTests(ITestOutputHelper output)
{
Output = output;
}
protected string CreateTestOutputFile(string fileName)
{
if (!Directory.Exists(TestOutputDirectory))
{
Directory.CreateDirectory(TestOutputDirectory);
}
//string id = Guid.NewGuid().ToString().Substring(0, 4);
string ext = Path.GetExtension(fileName);
fileName = Path.GetFileNameWithoutExtension(fileName);
return $"{TestOutputDirectory}/{fileName}{ext}";
}
protected Stream CreateOutputStream(string fileName)
{
fileName = CreateTestOutputFile(fileName);
Output?.WriteLine("Opened for write: "+fileName);
return File.OpenWrite(fileName);
}
public static IEnumerable<object[]> AllJpegFiles
=> TestImages.Jpeg.All.Select(fn => new object[] {fn});
[Theory]
[MemberData(nameof(AllJpegFiles))]
public void OpenJpeg_SaveBmp(string jpegPath)
{
string bmpFileName = Path.GetFileNameWithoutExtension(jpegPath) + ".bmp";
using (var inputStream = File.OpenRead(jpegPath))
{
var image = new Image(inputStream);
using (var outputStream = CreateOutputStream(bmpFileName))
{
image.Save(outputStream, new BmpFormat());
}
}
}
public static IEnumerable<object[]> AllBmpFiles
=> TestImages.Jpeg.All.Select(fn => new object[] {fn});
[Theory]
[MemberData(nameof(AllBmpFiles))]
public void OpenBmp_SaveJpeg(string bmpPath)
{
string jpegPath = Path.GetFileNameWithoutExtension(bmpPath) + ".jpeg";
using (var inputStream = File.OpenRead(bmpPath))
{
var image = new Image(inputStream);
using (var outputStream = CreateOutputStream(jpegPath))
{
image.Save(outputStream, new JpegFormat());
}
}
}
}
}

2
tests/ImageSharp.Tests/Formats/Png/PngTests.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
using ImageSharp.Formats;
namespace ImageSharp.Tests
{
using System.IO;

9
tests/ImageSharp.Tests/TestImages.cs

@ -34,13 +34,20 @@ namespace ImageSharp.Tests
{
private static readonly string folder = "TestImages/Formats/Jpg/";
public static string Cmyk => folder + "cmyk.jpg";
public static string Exif => folder + "exif.jpeg";
public static string Exif => folder + "exif.jpg";
public static string Floorplan => folder + "Floorplan.jpeg";
public static string Calliphora => folder + "Calliphora.jpg";
public static string Turtle => folder + "turtle.jpg";
public static string Fb => folder + "fb.jpg";
public static string Progress => folder + "progress.jpg";
public static string GammaDalaiLamaGray => folder + "gamma_dalai_lama_gray.jpg";
public static string Geneserath => folder + "geneserath.jpg";
public static readonly string[] All = new[]
{
Cmyk, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray, Geneserath
};
}
public static class Bmp

3
tests/ImageSharp.Tests/TestImages/Formats/Jpg/geneserath.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c71e5b1ba181a5b17ba9814c2025650de592efabf4062bd77baa3c8e774df007
size 223841
Loading…
Cancel
Save