From 4a2acc91e3084bc8dee129738739e4e913d1a9ab Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Sat, 24 Dec 2016 08:43:41 +0100 Subject: [PATCH] removed old Block + related classes, added EncodeJpegMultiple & MultiImageBenchmarkBase --- .../Formats/Jpg/Components/Block.cs | 272 ------------------ .../Formats/Jpg/Components/Block8x8F.cs | 20 +- src/ImageSharp/Formats/Jpg/Components/FDCT.cs | 162 ----------- src/ImageSharp/Formats/Jpg/Components/IDCT.cs | 169 ----------- src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs | 24 +- src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs | 6 +- .../Image/DecodeJpegMultiple.cs | 96 +------ .../Image/EncodeJpegMultiple.cs | 50 ++++ .../Image/MultiImageBenchmarkBase.cs | 185 ++++++++++++ .../Jpg/ReferenceImplementationsTests.cs | 21 +- 10 files changed, 266 insertions(+), 739 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpg/Components/Block.cs delete mode 100644 src/ImageSharp/Formats/Jpg/Components/FDCT.cs delete mode 100644 src/ImageSharp/Formats/Jpg/Components/IDCT.cs create mode 100644 tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs create mode 100644 tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs diff --git a/src/ImageSharp/Formats/Jpg/Components/Block.cs b/src/ImageSharp/Formats/Jpg/Components/Block.cs deleted file mode 100644 index 0531153cd..000000000 --- a/src/ImageSharp/Formats/Jpg/Components/Block.cs +++ /dev/null @@ -1,272 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - using System; - using System.Buffers; - using System.Runtime.CompilerServices; - - /// - /// Represents an 8x8 block of coefficients to transform and encode. - /// - internal struct Block : IDisposable - { - /// - /// Gets the size of the block. - /// - public const int BlockSize = 64; - - /// - /// Gets the array of block data. - /// - public int[] Data; - - /// - /// A pool of reusable buffers. - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(BlockSize, 50); - - /// - /// Gets a value indicating whether the block is initialized - /// - public bool IsInitialized => this.Data != null; - - /// - /// Gets the pixel data at the given block index. - /// - /// The index of the data to return. - /// - /// The . - /// - public int this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return this.Data[index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Data[index] = value; - } - } - - /// - /// Creates a new block - /// - /// The - public static Block Create() - { - Block block = default(Block); - block.Init(); - return block; - } - - /// - /// Returns an array of blocks of the given length. - /// - /// The number to create. - /// The - public static Block[] CreateArray(int count) - { - Block[] result = new Block[count]; - for (int i = 0; i < result.Length; i++) - { - result[i].Init(); - } - - return result; - } - - /// - /// Disposes of the collection of blocks - /// - /// The blocks. - public static void DisposeAll(Block[] blocks) - { - for (int i = 0; i < blocks.Length; i++) - { - blocks[i].Dispose(); - } - } - - /// - /// Initializes the new block. - /// - public void Init() - { - this.Data = ArrayPool.Rent(BlockSize); - } - - /// - 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; - } - } - - /// - /// Clears the block data - /// - public void Clear() - { - for (int i = 0; i < this.Data.Length; i++) - { - this.Data[i] = 0; - } - } - - /// - /// Clones the current block - /// - /// The - public Block Clone() - { - Block clone = Create(); - Array.Copy(this.Data, clone.Data, BlockSize); - return clone; - } - } - - /// - /// 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 - /// - internal struct BlockF : IDisposable - { - /// - /// Size of the block. - /// - public const int BlockSize = 64; - - /// - /// The array of block data. - /// - public float[] Data; - - /// - /// A pool of reusable buffers. - /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(BlockSize, 50); - - /// - /// Gets a value indicating whether the block is initialized - /// - public bool IsInitialized => this.Data != null; - - /// - /// Gets the pixel data at the given block index. - /// - /// The index of the data to return. - /// - /// The . - /// - public float this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return this.Data[index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Data[index] = value; - } - } - - /// - /// Creates a new block - /// - /// The - public static BlockF Create() - { - var block = default(BlockF); - block.Init(); - return block; - } - - /// - /// Returns an array of blocks of the given length. - /// - /// The number to create. - /// The - public static BlockF[] CreateArray(int count) - { - BlockF[] result = new BlockF[count]; - for (int i = 0; i < result.Length; i++) - { - result[i].Init(); - } - - return result; - } - - /// - /// Disposes of the collection of blocks - /// - /// The blocks. - public static void DisposeAll(BlockF[] blocks) - { - for (int i = 0; i < blocks.Length; i++) - { - blocks[i].Dispose(); - } - } - - /// - /// Clears the block data - /// - public void Clear() - { - for (int i = 0; i < this.Data.Length; i++) - { - this.Data[i] = 0; - } - } - - /// - /// Clones the current block - /// - /// The - public BlockF Clone() - { - BlockF clone = Create(); - Array.Copy(this.Data, clone.Data, BlockSize); - return clone; - } - - /// - /// Initializes the new block. - /// - public void Init() - { - // this.Data = new int[BlockSize]; - this.Data = ArrayPool.Rent(BlockSize); - } - - /// - 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; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs index 89b838289..66ef146e4 100644 --- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs +++ b/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); } - /// - /// TODO: Should be removed when BlockF goes away - /// - /// Legacy block - public void LoadFrom(ref BlockF legacyBlock) - { - this.LoadFrom(legacyBlock.Data); - } - - /// - /// TODO: Should be removed when BlockF goes away - /// - /// Legacy block - public void CopyTo(ref BlockF legacyBlock) - { - this.CopyTo(legacyBlock.Data); - } - /// /// Level shift by +128, clip to [0, 255], and write to buffer. /// diff --git a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs b/src/ImageSharp/Formats/Jpg/Components/FDCT.cs deleted file mode 100644 index 650656ab2..000000000 --- a/src/ImageSharp/Formats/Jpg/Components/FDCT.cs +++ /dev/null @@ -1,162 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Performs a fast, forward discrete cosine transform against the given block - /// decomposing it into 64 orthogonal basis signals. - /// - 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 - - /// - /// The number of bits - /// - private const int Bits = 13; - - /// - /// The number of bits to shift by on the first pass. - /// - private const int Pass1Bits = 2; - - /// - /// The value to shift by - /// - private const int CenterJSample = 128; - - /// - /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. - /// - /// The block of coefficients. - 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); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs b/src/ImageSharp/Formats/Jpg/Components/IDCT.cs deleted file mode 100644 index 8c3a5a238..000000000 --- a/src/ImageSharp/Formats/Jpg/Components/IDCT.cs +++ /dev/null @@ -1,169 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Formats -{ - /// - /// Performs a 2-D Inverse Discrete Cosine Transformation. - /// - 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) - - /// - /// Performs a 2-D Inverse Discrete Cosine Transformation. - /// - /// 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. - /// - /// 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. - /// - /// The source block of coefficients - 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; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs index 960405530..b74ae2317 100644 --- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs +++ b/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. diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs index d6c387911..8c5ed8b90 100644 --- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs +++ b/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); diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs index a3c2eba73..68797c778 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs @@ -1,106 +1,38 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// +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 fileNamesToBytes; - - public enum JpegTestingMode + protected override IEnumerable InputImageSubfolders => new[] { - All, - - SmallImagesOnly, - - LargeImagesOnly, + "Formats/Jpg/" + }; - CalliphoraOnly, - } - - [Params(JpegTestingMode.All, JpegTestingMode.SmallImagesOnly, JpegTestingMode.LargeImagesOnly, - JpegTestingMode.CalliphoraOnly)] - public JpegTestingMode Mode { get; set; } - - private IEnumerable> 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 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); - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/EncodeJpegMultiple.cs new file mode 100644 index 000000000..d6af8c842 --- /dev/null +++ b/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 InputImageSubfolders => new[] + { + "Formats/Bmp/", + "Formats/Jpg/baseline" + }; + + protected override IEnumerable 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; + }); + } + + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs new file mode 100644 index 000000000..d605bf931 --- /dev/null +++ b/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 FileNamesToBytes = new Dictionary(); + + protected Dictionary FileNamesToImageSharpImages = new Dictionary(); + protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); + + 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 FileFilters { get; } + + protected IEnumerable FilterWords => new string[] { }; + + protected virtual IEnumerable Folders => this.InputImageSubfolders.Select(f => Path.Combine(this.BaseFolder, f)); + + protected virtual int LargeImageThresholdInBytes => 100000; + + protected IEnumerable> EnumeratePairsByBenchmarkSettings( + Dictionary input, + Predicate 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> FileNames2Bytes + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToBytes, + arr => arr.Length < this.LargeImageThresholdInBytes); + + protected abstract IEnumerable 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 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> FileNames2ImageSharpImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToImageSharpImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); + + protected IEnumerable> FileNames2SystemDrawingImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToSystemDrawingImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); + + protected virtual int LargeImageThresholdInPixels => 700000; + + protected void ForEachImageSharpImage(Func 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 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}"); + } + } + } + } + + + } + + +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 265d6b5f2..d117cfd0a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/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 src = new MutableSpan(classic.Data).ConvertToFloat32MutableSpan(); - - MutableSpan dest1 = new MutableSpan(64); - MutableSpan dest2 = new MutableSpan(64); - MutableSpan temp = new MutableSpan(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)); - } + } } } \ No newline at end of file