mirror of https://github.com/SixLabors/ImageSharp
10 changed files with 266 additions and 739 deletions
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
}); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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}"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
} |
|||
Loading…
Reference in new issue