Browse Source

removed old Block + related classes, added EncodeJpegMultiple & MultiImageBenchmarkBase

pull/58/head
antonfirsov 9 years ago
parent
commit
4a2acc91e3
  1. 272
      src/ImageSharp/Formats/Jpg/Components/Block.cs
  2. 20
      src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
  3. 162
      src/ImageSharp/Formats/Jpg/Components/FDCT.cs
  4. 169
      src/ImageSharp/Formats/Jpg/Components/IDCT.cs
  5. 24
      src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
  6. 6
      src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
  7. 96
      tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs
  8. 50
      tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs
  9. 185
      tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs
  10. 21
      tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs

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

@ -1,272 +0,0 @@
// <copyright file="Block.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an 8x8 block of coefficients to transform and encode.
/// </summary>
internal struct Block : IDisposable
{
/// <summary>
/// Gets the size of the block.
/// </summary>
public const int BlockSize = 64;
/// <summary>
/// Gets the array of block data.
/// </summary>
public int[] Data;
/// <summary>
/// A pool of reusable buffers.
/// </summary>
private static readonly ArrayPool<int> ArrayPool = ArrayPool<int>.Create(BlockSize, 50);
/// <summary>
/// Gets a value indicating whether the block is initialized
/// </summary>
public bool IsInitialized => this.Data != null;
/// <summary>
/// Gets the pixel data at the given block index.
/// </summary>
/// <param name="index">The index of the data to return.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public int this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Data[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.Data[index] = value;
}
}
/// <summary>
/// Creates a new block
/// </summary>
/// <returns>The <see cref="Block"/></returns>
public static Block Create()
{
Block block = default(Block);
block.Init();
return block;
}
/// <summary>
/// Returns an array of blocks of the given length.
/// </summary>
/// <param name="count">The number to create.</param>
/// <returns>The <see cref="T:Block[]"/></returns>
public static Block[] CreateArray(int count)
{
Block[] result = new Block[count];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
/// <summary>
/// Disposes of the collection of blocks
/// </summary>
/// <param name="blocks">The blocks.</param>
public static void DisposeAll(Block[] blocks)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
}
/// <summary>
/// Initializes the new block.
/// </summary>
public void Init()
{
this.Data = ArrayPool.Rent(BlockSize);
}
/// <inheritdoc />
public void Dispose()
{
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
if (this.Data != null)
{
ArrayPool.Return(this.Data, true);
this.Data = null;
}
}
/// <summary>
/// Clears the block data
/// </summary>
public void Clear()
{
for (int i = 0; i < this.Data.Length; i++)
{
this.Data[i] = 0;
}
}
/// <summary>
/// Clones the current block
/// </summary>
/// <returns>The <see cref="Block"/></returns>
public Block Clone()
{
Block clone = Create();
Array.Copy(this.Data, clone.Data, BlockSize);
return clone;
}
}
/// <summary>
/// TODO: Should be removed, when JpegEncoderCore is refactored to use Block8x8F
/// Temporal class to make refactoring easier.
/// 1. Refactor Block -> BlockF
/// 2. Test
/// 3. Refactor BlockF -> Block8x8F
/// </summary>
internal struct BlockF : IDisposable
{
/// <summary>
/// Size of the block.
/// </summary>
public const int BlockSize = 64;
/// <summary>
/// The array of block data.
/// </summary>
public float[] Data;
/// <summary>
/// A pool of reusable buffers.
/// </summary>
private static readonly ArrayPool<float> ArrayPool = ArrayPool<float>.Create(BlockSize, 50);
/// <summary>
/// Gets a value indicating whether the block is initialized
/// </summary>
public bool IsInitialized => this.Data != null;
/// <summary>
/// Gets the pixel data at the given block index.
/// </summary>
/// <param name="index">The index of the data to return.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public float this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Data[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.Data[index] = value;
}
}
/// <summary>
/// Creates a new block
/// </summary>
/// <returns>The <see cref="BlockF"/></returns>
public static BlockF Create()
{
var block = default(BlockF);
block.Init();
return block;
}
/// <summary>
/// Returns an array of blocks of the given length.
/// </summary>
/// <param name="count">The number to create.</param>
/// <returns>The <see cref="T:BlockF[]"/></returns>
public static BlockF[] CreateArray(int count)
{
BlockF[] result = new BlockF[count];
for (int i = 0; i < result.Length; i++)
{
result[i].Init();
}
return result;
}
/// <summary>
/// Disposes of the collection of blocks
/// </summary>
/// <param name="blocks">The blocks.</param>
public static void DisposeAll(BlockF[] blocks)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i].Dispose();
}
}
/// <summary>
/// Clears the block data
/// </summary>
public void Clear()
{
for (int i = 0; i < this.Data.Length; i++)
{
this.Data[i] = 0;
}
}
/// <summary>
/// Clones the current block
/// </summary>
/// <returns>The <see cref="Block"/></returns>
public BlockF Clone()
{
BlockF clone = Create();
Array.Copy(this.Data, clone.Data, BlockSize);
return clone;
}
/// <summary>
/// Initializes the new block.
/// </summary>
public void Init()
{
// this.Data = new int[BlockSize];
this.Data = ArrayPool.Rent(BlockSize);
}
/// <inheritdoc />
public void Dispose()
{
// TODO: Refactor Block.Dispose() callers to always use 'using' or 'finally' statement!
if (this.Data != null)
{
ArrayPool.Return(this.Data, true);
this.Data = null;
}
}
}
}

20
src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs

@ -229,7 +229,7 @@ namespace ImageSharp.Formats
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
for (int zig = 0; zig < BlockF.BlockSize; zig++)
for (int zig = 0; zig < ScalarCount; zig++)
{
float* unzigPos = b + unzigPtr[zig];
float val = *unzigPos;
@ -280,24 +280,6 @@ namespace ImageSharp.Formats
this = default(Block8x8F);
}
/// <summary>
/// TODO: Should be removed when BlockF goes away
/// </summary>
/// <param name="legacyBlock">Legacy block</param>
public void LoadFrom(ref BlockF legacyBlock)
{
this.LoadFrom(legacyBlock.Data);
}
/// <summary>
/// TODO: Should be removed when BlockF goes away
/// </summary>
/// <param name="legacyBlock">Legacy block</param>
public void CopyTo(ref BlockF legacyBlock)
{
this.CopyTo(legacyBlock.Data);
}
/// <summary>
/// Level shift by +128, clip to [0, 255], and write to buffer.
/// </summary>

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

@ -1,162 +0,0 @@
// <copyright file="FDCT.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Performs a fast, forward discrete cosine transform against the given block
/// decomposing it into 64 orthogonal basis signals.
/// </summary>
internal class FDCT
{
// Trigonometric constants in 13-bit fixed point format.
// TODO: Rename and describe these.
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private const int Fix_0_298631336 = 2446;
private const int Fix_0_390180644 = 3196;
private const int Fix_0_541196100 = 4433;
private const int Fix_0_765366865 = 6270;
private const int Fix_0_899976223 = 7373;
private const int Fix_1_175875602 = 9633;
private const int Fix_1_501321110 = 12299;
private const int Fix_1_847759065 = 15137;
private const int Fix_1_961570560 = 16069;
private const int Fix_2_053119869 = 16819;
private const int Fix_2_562915447 = 20995;
private const int Fix_3_072711026 = 25172;
#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
/// <summary>
/// The number of bits
/// </summary>
private const int Bits = 13;
/// <summary>
/// The number of bits to shift by on the first pass.
/// </summary>
private const int Pass1Bits = 2;
/// <summary>
/// The value to shift by
/// </summary>
private const int CenterJSample = 128;
/// <summary>
/// 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(ref Block block)
{
// Pass 1: process rows.
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
int x0 = block[y8];
int x1 = block[y8 + 1];
int x2 = block[y8 + 2];
int x3 = block[y8 + 3];
int x4 = block[y8 + 4];
int x5 = block[y8 + 5];
int x6 = block[y8 + 6];
int x7 = block[y8 + 7];
int tmp0 = x0 + x7;
int tmp1 = x1 + x6;
int tmp2 = x2 + x5;
int tmp3 = x3 + x4;
int tmp10 = tmp0 + tmp3;
int tmp12 = tmp0 - tmp3;
int tmp11 = tmp1 + tmp2;
int tmp13 = tmp1 - tmp2;
tmp0 = x0 - x7;
tmp1 = x1 - x6;
tmp2 = x2 - x5;
tmp3 = x3 - x4;
block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits;
block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits;
int z1 = (tmp12 + tmp13) * Fix_0_541196100;
z1 += 1 << (Bits - Pass1Bits - 1);
block[y8 + 2] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits - Pass1Bits);
block[y8 + 6] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits - Pass1Bits);
tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp0 + tmp2;
tmp13 = tmp1 + tmp3;
z1 = (tmp12 + tmp13) * Fix_1_175875602;
z1 += 1 << (Bits - Pass1Bits - 1);
tmp0 = tmp0 * Fix_1_501321110;
tmp1 = tmp1 * Fix_3_072711026;
tmp2 = tmp2 * Fix_2_053119869;
tmp3 = tmp3 * Fix_0_298631336;
tmp10 = tmp10 * -Fix_0_899976223;
tmp11 = tmp11 * -Fix_2_562915447;
tmp12 = tmp12 * -Fix_0_390180644;
tmp13 = tmp13 * -Fix_1_961570560;
tmp12 += z1;
tmp13 += z1;
block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits);
block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits);
block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits);
block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits);
}
// Pass 2: process columns.
// We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8.
for (int x = 0; x < 8; x++)
{
int tmp0 = block[x] + block[56 + x];
int tmp1 = block[8 + x] + block[48 + x];
int tmp2 = block[16 + x] + block[40 + x];
int tmp3 = block[24 + x] + block[32 + x];
int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1));
int tmp12 = tmp0 - tmp3;
int tmp11 = tmp1 + tmp2;
int tmp13 = tmp1 - tmp2;
tmp0 = block[x] - block[56 + x];
tmp1 = block[8 + x] - block[48 + x];
tmp2 = block[16 + x] - block[40 + x];
tmp3 = block[24 + x] - block[32 + x];
block[x] = (tmp10 + tmp11) >> Pass1Bits;
block[32 + x] = (tmp10 - tmp11) >> Pass1Bits;
int z1 = (tmp12 + tmp13) * Fix_0_541196100;
z1 += 1 << (Bits + Pass1Bits - 1);
block[16 + x] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits + Pass1Bits);
block[48 + x] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits + Pass1Bits);
tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp0 + tmp2;
tmp13 = tmp1 + tmp3;
z1 = (tmp12 + tmp13) * Fix_1_175875602;
z1 += 1 << (Bits + Pass1Bits - 1);
tmp0 = tmp0 * Fix_1_501321110;
tmp1 = tmp1 * Fix_3_072711026;
tmp2 = tmp2 * Fix_2_053119869;
tmp3 = tmp3 * Fix_0_298631336;
tmp10 = tmp10 * -Fix_0_899976223;
tmp11 = tmp11 * -Fix_2_562915447;
tmp12 = tmp12 * -Fix_0_390180644;
tmp13 = tmp13 * -Fix_1_961570560;
tmp12 += z1;
tmp13 += z1;
block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits);
block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits);
block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits);
block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits);
}
}
}
}

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

@ -1,169 +0,0 @@
// <copyright file="IDCT.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Performs a 2-D Inverse Discrete Cosine Transformation.
/// </summary>
internal class IDCT
{
private const int W1 = 2841; // 2048*sqrt(2)*cos(1*pi/16)
private const int W2 = 2676; // 2048*sqrt(2)*cos(2*pi/16)
private const int W3 = 2408; // 2048*sqrt(2)*cos(3*pi/16)
private const int W5 = 1609; // 2048*sqrt(2)*cos(5*pi/16)
private const int W6 = 1108; // 2048*sqrt(2)*cos(6*pi/16)
private const int W7 = 565; // 2048*sqrt(2)*cos(7*pi/16)
private const int W1pw7 = W1 + W7;
private const int W1mw7 = W1 - W7;
private const int W2pw6 = W2 + W6;
private const int W2mw6 = W2 - W6;
private const int W3pw5 = W3 + W5;
private const int W3mw5 = W3 - W5;
private const int R2 = 181; // 256/sqrt(2)
/// <summary>
/// Performs a 2-D Inverse Discrete Cosine Transformation.
/// <para>
/// The input coefficients should already have been multiplied by the
/// appropriate quantization table. We use fixed-point computation, with the
/// number of bits for the fractional component varying over the intermediate
/// stages.
/// </para>
/// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the
/// discrete W transform and for the discrete Fourier transform", IEEE Trans. on
/// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
/// </summary>
/// <param name="src">The source block of coefficients</param>
public static void Transform(ref Block src)
{
// Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++)
{
int y8 = y * 8;
// If all the AC components are zero, then the IDCT is trivial.
if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 &&
src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0)
{
int dc = src[y8 + 0] << 3;
src[y8 + 0] = dc;
src[y8 + 1] = dc;
src[y8 + 2] = dc;
src[y8 + 3] = dc;
src[y8 + 4] = dc;
src[y8 + 5] = dc;
src[y8 + 6] = dc;
src[y8 + 7] = dc;
continue;
}
// Prescale.
int x0 = (src[y8 + 0] << 11) + 128;
int x1 = src[y8 + 4] << 11;
int x2 = src[y8 + 6];
int x3 = src[y8 + 2];
int x4 = src[y8 + 1];
int x5 = src[y8 + 7];
int x6 = src[y8 + 5];
int x7 = src[y8 + 3];
// Stage 1.
int x8 = W7 * (x4 + x5);
x4 = x8 + (W1mw7 * x4);
x5 = x8 - (W1pw7 * x5);
x8 = W3 * (x6 + x7);
x6 = x8 - (W3mw5 * x6);
x7 = x8 - (W3pw5 * x7);
// Stage 2.
x8 = x0 + x1;
x0 -= x1;
x1 = W6 * (x3 + x2);
x2 = x1 - (W2pw6 * x2);
x3 = x1 + (W2mw6 * x3);
x1 = x4 + x6;
x4 -= x6;
x6 = x5 + x7;
x5 -= x7;
// Stage 3.
x7 = x8 + x3;
x8 -= x3;
x3 = x0 + x2;
x0 -= x2;
x2 = ((R2 * (x4 + x5)) + 128) >> 8;
x4 = ((R2 * (x4 - x5)) + 128) >> 8;
// Stage 4.
src[y8 + 0] = (x7 + x1) >> 8;
src[y8 + 1] = (x3 + x2) >> 8;
src[y8 + 2] = (x0 + x4) >> 8;
src[y8 + 3] = (x8 + x6) >> 8;
src[y8 + 4] = (x8 - x6) >> 8;
src[y8 + 5] = (x0 - x4) >> 8;
src[y8 + 6] = (x3 - x2) >> 8;
src[y8 + 7] = (x7 - x1) >> 8;
}
// Vertical 1-D IDCT.
for (int x = 0; x < 8; x++)
{
// Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial.
// However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so
// we do not bother to check for the all-zero case.
// Prescale.
int y0 = (src[x] << 8) + 8192;
int y1 = src[32 + x] << 8;
int y2 = src[48 + x];
int y3 = src[16 + x];
int y4 = src[8 + x];
int y5 = src[56 + x];
int y6 = src[40 + x];
int y7 = src[24 + x];
// Stage 1.
int y8 = (W7 * (y4 + y5)) + 4;
y4 = (y8 + (W1mw7 * y4)) >> 3;
y5 = (y8 - (W1pw7 * y5)) >> 3;
y8 = (W3 * (y6 + y7)) + 4;
y6 = (y8 - (W3mw5 * y6)) >> 3;
y7 = (y8 - (W3pw5 * y7)) >> 3;
// Stage 2.
y8 = y0 + y1;
y0 -= y1;
y1 = (W6 * (y3 + y2)) + 4;
y2 = (y1 - (W2pw6 * y2)) >> 3;
y3 = (y1 + (W2mw6 * y3)) >> 3;
y1 = y4 + y6;
y4 -= y6;
y6 = y5 + y7;
y5 -= y7;
// Stage 3.
y7 = y8 + y3;
y8 -= y3;
y3 = y0 + y2;
y0 -= y2;
y2 = ((R2 * (y4 + y5)) + 128) >> 8;
y4 = ((R2 * (y4 - y5)) + 128) >> 8;
// Stage 4.
src[x] = (y7 + y1) >> 14;
src[8 + x] = (y3 + y2) >> 14;
src[16 + x] = (y0 + y4) >> 14;
src[24 + x] = (y8 + y6) >> 14;
src[32 + x] = (y8 - y6) >> 14;
src[40 + x] = (y0 - y4) >> 14;
src[48 + x] = (y3 - y2) >> 14;
src[56 + x] = (y7 - y1) >> 14;
}
}
}
}

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

@ -189,7 +189,7 @@ namespace ImageSharp.Formats
this.huffmanTrees = new Huffman[(MaxTc + 1) * (MaxTh + 1)];
this.quantizationTables = new Block8x8F[MaxTq + 1];
this.temp = new byte[2 * BlockF.BlockSize];
this.temp = new byte[2 * Block8x8F.ScalarCount];
this.componentArray = new Component[MaxComponents];
this.progCoeffs = new Block8x8F[MaxComponents][];
this.bits = default(Bits);
@ -1053,32 +1053,32 @@ namespace ImageSharp.Formats
switch (x >> 4)
{
case 0:
if (remaining < BlockF.BlockSize)
if (remaining < Block8x8F.ScalarCount)
{
done = true;
break;
}
remaining -= BlockF.BlockSize;
this.ReadFull(this.temp, 0, BlockF.BlockSize);
remaining -= Block8x8F.ScalarCount;
this.ReadFull(this.temp, 0, Block8x8F.ScalarCount);
for (int i = 0; i < BlockF.BlockSize; i++)
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
this.quantizationTables[tq][i] = this.temp[i];
}
break;
case 1:
if (remaining < 2 * BlockF.BlockSize)
if (remaining < 2 * Block8x8F.ScalarCount)
{
done = true;
break;
}
remaining -= 2 * BlockF.BlockSize;
this.ReadFull(this.temp, 0, 2 * BlockF.BlockSize);
remaining -= 2 * Block8x8F.ScalarCount;
this.ReadFull(this.temp, 0, 2 * Block8x8F.ScalarCount);
for (int i = 0; i < BlockF.BlockSize; i++)
for (int i = 0; i < Block8x8F.ScalarCount; i++)
{
this.quantizationTables[tq][i] = (this.temp[2 * i] << 8) | this.temp[(2 * i) + 1];
}
@ -1473,7 +1473,7 @@ namespace ImageSharp.Formats
// significant bit.
// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
int zigStart = 0;
int zigEnd = BlockF.BlockSize - 1;
int zigEnd = Block8x8F.ScalarCount - 1;
int ah = 0;
int al = 0;
@ -1484,7 +1484,7 @@ namespace ImageSharp.Formats
ah = this.temp[3 + scanComponentCountX2] >> 4;
al = this.temp[3 + scanComponentCountX2] & 0x0f;
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= BlockF.BlockSize)
if ((zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || zigEnd >= Block8x8F.ScalarCount)
{
throw new ImageFormatException("Bad spectral selection bounds");
}
@ -1788,7 +1788,7 @@ namespace ImageSharp.Formats
if (this.isProgressive)
{
if (zigEnd != BlockF.BlockSize - 1 || al != 0)
if (zigEnd != Block8x8F.ScalarCount - 1 || al != 0)
{
// We haven't completely decoded this 8x8 block. Save the coefficients.

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

@ -561,7 +561,7 @@ namespace ImageSharp.Formats
HuffIndex h = (HuffIndex)((2 * (int)index) + 1);
int runLength = 0;
for (int zig = 1; zig < Block.BlockSize; zig++)
for (int zig = 1; zig < Block8x8F.ScalarCount; zig++)
{
float ac = d[zig];
@ -679,12 +679,12 @@ namespace ImageSharp.Formats
private void WriteDefineQuantizationTables()
{
// Marker + quantization table lengths
int markerlen = 2 + (QuantizationTableCount * (1 + Block.BlockSize));
int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount));
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
// Loop through and collect the tables as one array.
// This allows us to reduce the number of writes to the stream.
byte[] dqt = new byte[(QuantizationTableCount * Block.BlockSize) + QuantizationTableCount];
byte[] dqt = new byte[(QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount];
int offset = 0;
WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable);

96
tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs

@ -1,106 +1,38 @@
// <copyright file="DecodeJpegMultiple.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Collections.Generic;
namespace ImageSharp.Benchmarks.Image
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Image = ImageSharp.Image;
using ImageSharpSize = ImageSharp.Size;
public class DecodeJpegMultiple
public class DecodeJpegMultiple : MultiImageBenchmarkBase
{
private const string Folder = "../ImageSharp.Tests/TestImages/Formats/Jpg/";
private Dictionary<string, byte[]> fileNamesToBytes;
public enum JpegTestingMode
protected override IEnumerable<string> InputImageSubfolders => new[]
{
All,
SmallImagesOnly,
LargeImagesOnly,
"Formats/Jpg/"
};
CalliphoraOnly,
}
[Params(JpegTestingMode.All, JpegTestingMode.SmallImagesOnly, JpegTestingMode.LargeImagesOnly,
JpegTestingMode.CalliphoraOnly)]
public JpegTestingMode Mode { get; set; }
private IEnumerable<KeyValuePair<string, byte[]>> RequestedImages
{
get
{
int thresholdInBytes = 100000;
switch (this.Mode)
{
case JpegTestingMode.All:
return this.fileNamesToBytes;
case JpegTestingMode.SmallImagesOnly:
return this.fileNamesToBytes.Where(kv => kv.Value.Length < thresholdInBytes);
case JpegTestingMode.LargeImagesOnly:
return this.fileNamesToBytes.Where(kv => kv.Value.Length >= thresholdInBytes);
case JpegTestingMode.CalliphoraOnly:
return new[] { this.fileNamesToBytes.First(kv => kv.Key.ToLower().Contains("calliphora")) };
default:
throw new ArgumentOutOfRangeException();
}
}
}
protected override IEnumerable<string> FileFilters => new[] { "*.jpg" };
[Benchmark(Description = "DecodeJpegMultiple - ImageSharp")]
public ImageSharpSize JpegImageSharp()
public void DecodeJpegImageSharp()
{
ImageSharpSize lastSize = new ImageSharpSize();
foreach (var kv in this.RequestedImages)
{
using (MemoryStream memoryStream = new MemoryStream(kv.Value))
{
Image image = new Image(memoryStream);
lastSize = new ImageSharpSize(image.Width, image.Height);
}
}
return lastSize;
this.ForEachStream(
ms => new ImageSharp.Image(ms)
);
}
[Benchmark(Baseline = true, Description = "DecodeJpegMultiple - System.Drawing")]
public Size JpegSystemDrawing()
public void DecodeJpegSystemDrawing()
{
Size lastSize = new Size();
foreach (var kv in this.RequestedImages)
{
using (MemoryStream memoryStream = new MemoryStream(kv.Value))
{
using (System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream))
{
lastSize = image.Size;
}
}
}
return lastSize;
this.ForEachStream(
System.Drawing.Image.FromStream
);
}
[Setup]
public void ReadImages()
{
if (this.fileNamesToBytes != null) return;
var allFiles = Directory.EnumerateFiles(Folder, "*.jpg", SearchOption.AllDirectories).ToArray();
this.fileNamesToBytes = allFiles.ToDictionary(fn => fn, File.ReadAllBytes);
}
}
}

50
tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs

@ -0,0 +1,50 @@
namespace ImageSharp.Benchmarks.Image
{
using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Jobs;
using BenchmarkDotNet.Engines;
using ImageSharp.Formats;
public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{
protected override IEnumerable<string> InputImageSubfolders => new[]
{
"Formats/Bmp/",
"Formats/Jpg/baseline"
};
protected override IEnumerable<string> FileFilters => new[] { "*.bmp", "*.jpg" };
[Benchmark(Description = "EncodeJpegMultiple - ImageSharp")]
public void EncodeJpegImageSharp()
{
this.ForEachImageSharpImage(
img =>
{
MemoryStream ms = new MemoryStream();
img.Save(ms, new JpegEncoder());
return ms;
});
}
[Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")]
public void EncodeJpegSystemDrawing()
{
this.ForEachSystemDrawingImage(
img =>
{
MemoryStream ms = new MemoryStream();
img.Save(ms, ImageFormat.Jpeg);
return ms;
});
}
}
}

185
tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs

@ -0,0 +1,185 @@
namespace ImageSharp.Benchmarks.Image
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Image = ImageSharp.Image;
public abstract class MultiImageBenchmarkBase
{
protected Dictionary<string, byte[]> FileNamesToBytes = new Dictionary<string, byte[]>();
protected Dictionary<string, Image> FileNamesToImageSharpImages = new Dictionary<string, Image>();
protected Dictionary<string, System.Drawing.Bitmap> FileNamesToSystemDrawingImages = new Dictionary<string, System.Drawing.Bitmap>();
public enum TestingMode
{
All,
SmallImagesOnly,
LargeImagesOnly
}
[Params(TestingMode.All, TestingMode.SmallImagesOnly, TestingMode.LargeImagesOnly)]
public TestingMode Mode { get; set; }
protected virtual string BaseFolder => "../ImageSharp.Tests/TestImages/";
protected abstract IEnumerable<string> FileFilters { get; }
protected IEnumerable<string> FilterWords => new string[] { };
protected virtual IEnumerable<string> Folders => this.InputImageSubfolders.Select(f => Path.Combine(this.BaseFolder, f));
protected virtual int LargeImageThresholdInBytes => 100000;
protected IEnumerable<KeyValuePair<string, T>> EnumeratePairsByBenchmarkSettings<T>(
Dictionary<string, T> input,
Predicate<T> checkIfSmall)
{
switch (this.Mode)
{
case TestingMode.All:
return input;
case TestingMode.SmallImagesOnly:
return input.Where(kv => checkIfSmall(kv.Value));
case TestingMode.LargeImagesOnly:
return input.Where(kv => !checkIfSmall(kv.Value));
default:
throw new ArgumentOutOfRangeException();
}
}
protected IEnumerable<KeyValuePair<string, byte[]>> FileNames2Bytes
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToBytes,
arr => arr.Length < this.LargeImageThresholdInBytes);
protected abstract IEnumerable<string> InputImageSubfolders { get; }
[Setup]
public void ReadImages()
{
//Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated);
this.ReadImagesImpl();
}
protected virtual void ReadImagesImpl()
{
foreach (string folder in this.Folders)
{
var allFiles =
this.FileFilters.SelectMany(
f =>
Directory.EnumerateFiles(folder, f, SearchOption.AllDirectories)
.Where(fn => !this.FilterWords.Any(w => fn.ToLower().Contains(w)))).ToArray();
foreach (var fn in allFiles)
{
this.FileNamesToBytes[fn] = File.ReadAllBytes(fn);
}
}
}
protected void ForEachStream(Func<MemoryStream, object> operation)
{
foreach (var kv in this.FileNames2Bytes)
{
using (MemoryStream memoryStream = new MemoryStream(kv.Value))
{
try
{
var obj = operation(memoryStream);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
}
}
}
public abstract class WithImagesPreloaded : MultiImageBenchmarkBase
{
protected override void ReadImagesImpl()
{
base.ReadImagesImpl();
foreach (var kv in this.FileNamesToBytes)
{
byte[] bytes = kv.Value;
string fn = kv.Key;
using (var ms1 = new MemoryStream(bytes))
{
this.FileNamesToImageSharpImages[fn] = new Image(ms1);
}
this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes));
}
}
protected IEnumerable<KeyValuePair<string, ImageSharp.Image>> FileNames2ImageSharpImages
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToImageSharpImages,
img => img.Width * img.Height < this.LargeImageThresholdInPixels);
protected IEnumerable<KeyValuePair<string, System.Drawing.Bitmap>> FileNames2SystemDrawingImages
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToSystemDrawingImages,
img => img.Width * img.Height < this.LargeImageThresholdInPixels);
protected virtual int LargeImageThresholdInPixels => 700000;
protected void ForEachImageSharpImage(Func<Image, object> operation)
{
foreach (var kv in this.FileNames2ImageSharpImages)
{
try
{
var obj = operation(kv.Value);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
}
}
protected void ForEachSystemDrawingImage(Func<System.Drawing.Bitmap, object> operation)
{
foreach (var kv in this.FileNames2SystemDrawingImages)
{
try
{
var obj = operation(kv.Value);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
}
}
}
}
}

21
tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs

@ -120,25 +120,6 @@ namespace ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual, new ApproximateFloatComparer(1f));
}
}
[Theory]
[InlineData(42)]
[InlineData(1)]
[InlineData(2)]
public void Fdct_SimdReferenceImplementation_IsEquivalentToFloatingPointReferenceImplementation(int seed)
{
Block classic = new Block() { Data = Create8x8RandomIntData(-200, 200, seed) };
MutableSpan<float> src = new MutableSpan<int>(classic.Data).ConvertToFloat32MutableSpan();
MutableSpan<float> dest1 = new MutableSpan<float>(64);
MutableSpan<float> dest2 = new MutableSpan<float>(64);
MutableSpan<float> temp = new MutableSpan<float>(64);
ReferenceImplementations.fDCT2D_llm(src, dest1, temp, downscaleBy8: true, offsetSourceByNeg128: false);
ReferenceImplementations.fDCT8x8_llm_sse(src, dest2, temp);
Assert.Equal(dest1.Data, dest2.Data, new ApproximateFloatComparer(1f));
}
}
}
}
Loading…
Cancel
Save