diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings
index 22f46979a..5acec071a 100644
--- a/ImageSharp.sln.DotSettings
+++ b/ImageSharp.sln.DotSettings
@@ -340,6 +340,7 @@
</Patterns>
True
True
+ AC
DC
FDCT
IDCT
@@ -348,6 +349,7 @@
RGB
RLE
XY
+ XYZ
$object$_On$event$
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
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.Generated.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs
index 3b48dc572..211b66dac 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.cs
@@ -1,11 +1,16 @@
-//
-
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+// ReSharper disable InconsistentNaming
+//
+#pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
internal partial struct Block8x8F
{
diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt
index 951d62d91..be198a6fa 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt
+++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.Generated.tt
@@ -1,11 +1,16 @@
-<#@ template debug="false" hostspecific="false" language="C#" #>
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+// ReSharper disable InconsistentNaming
+<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
//
-
+#pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -14,7 +19,7 @@ using System.Runtime.CompilerServices;
char[] coordz = {'X', 'Y', 'Z', 'W'};
#>
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
internal partial struct Block8x8F
{
diff --git a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
index 8b86e3bd5..723ccd6b8 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Block8x8F.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
// ReSharper disable InconsistentNaming
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
using System;
using System.Numerics;
@@ -15,6 +15,11 @@ namespace ImageSharp.Formats
///
internal partial struct Block8x8F
{
+ // Most of the static methods of this struct are instance methods by actual semantics: they use Block8x8F* as their first parameter.
+ // Example: GetScalarAt() and SetScalarAt() are really just other (optimized) versions of the indexer.
+ // It's much cleaner, easier and safer to work with the code, if the methods with same semantics are next to each other.
+#pragma warning disable SA1204 // StaticElementsMustAppearBeforeInstanceElements
+
///
/// Vector count
///
@@ -51,25 +56,8 @@ namespace ImageSharp.Formats
public Vector4 V7R;
#pragma warning restore SA1600 // ElementsMustBeDocumented
-#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
- private static readonly Vector4 C_1_175876 = new Vector4(1.175876f);
- private static readonly Vector4 C_1_961571 = new Vector4(-1.961571f);
- private static readonly Vector4 C_0_390181 = new Vector4(-0.390181f);
- private static readonly Vector4 C_0_899976 = new Vector4(-0.899976f);
- private static readonly Vector4 C_2_562915 = new Vector4(-2.562915f);
- private static readonly Vector4 C_0_298631 = new Vector4(0.298631f);
- private static readonly Vector4 C_2_053120 = new Vector4(2.053120f);
- private static readonly Vector4 C_3_072711 = new Vector4(3.072711f);
- private static readonly Vector4 C_1_501321 = new Vector4(1.501321f);
- private static readonly Vector4 C_0_541196 = new Vector4(0.541196f);
- private static readonly Vector4 C_1_847759 = new Vector4(-1.847759f);
- private static readonly Vector4 C_0_765367 = new Vector4(0.765367f);
-
- private static readonly Vector4 C_0_125 = new Vector4(0.1250f);
-#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
-
///
- /// Index into the block
+ /// Get/Set scalar elements at a given index
///
/// The index
/// The float value at the specified index
@@ -97,25 +85,39 @@ namespace ImageSharp.Formats
}
///
- /// Load raw 32bit floating point data from source
+ /// Pointer-based "Indexer" (getter part)
///
/// Block pointer
- /// Source
+ /// Index
+ /// The scaleVec value at the specified index
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void LoadFrom(Block8x8F* blockPtr, MutableSpan source)
+ public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
{
- Marshal.Copy(source.Data, source.Offset, (IntPtr)blockPtr, ScalarCount);
+ float* fp = (float*)blockPtr;
+ return fp[idx];
}
///
- /// Copy raw 32bit floating point data to dest
+ /// Pointer-based "Indexer" (setter part)
///
/// Block pointer
- /// Destination
+ /// Index
+ /// Value
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest)
+ public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
{
- Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount);
+ float* fp = (float*)blockPtr;
+ fp[idx] = value;
+ }
+
+ ///
+ /// Fill the block with defaults (zeroes)
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Clear()
+ {
+ // The cheapest way to do this in C#:
+ this = default(Block8x8F);
}
///
@@ -132,118 +134,82 @@ namespace ImageSharp.Formats
}
///
- /// Copy raw 32bit floating point data to dest
+ /// Load raw 32bit floating point data from source
///
- /// Destination
+ /// Block pointer
+ /// Source
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe void CopyTo(MutableSpan dest)
+ public static unsafe void LoadFrom(Block8x8F* blockPtr, MutableSpan source)
{
- fixed (void* ptr = &this.V0L)
- {
- Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount);
- }
+ Marshal.Copy(source.Data, source.Offset, (IntPtr)blockPtr, ScalarCount);
}
///
- /// Copy raw 32bit floating point data to dest
+ /// Load raw 32bit floating point data from source
///
- /// Destination
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe void CopyTo(float[] dest)
+ /// Source
+ public unsafe void LoadFrom(MutableSpan source)
{
- fixed (void* ptr = &this.V0L)
+ fixed (Vector4* ptr = &this.V0L)
{
- Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount);
+ float* fp = (float*)ptr;
+ for (int i = 0; i < ScalarCount; i++)
+ {
+ fp[i] = source[i];
+ }
}
}
///
- /// Multiply in place
+ /// Copy raw 32bit floating point data to dest
///
- /// Scalar to multiply by
+ /// Destination
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void MultiplyAllInplace(Vector4 scalar)
+ public unsafe void CopyTo(MutableSpan dest)
{
- this.V0L *= scalar;
- this.V0R *= scalar;
- this.V1L *= scalar;
- this.V1R *= scalar;
- this.V2L *= scalar;
- this.V2R *= scalar;
- this.V3L *= scalar;
- this.V3R *= scalar;
- this.V4L *= scalar;
- this.V4R *= scalar;
- this.V5L *= scalar;
- this.V5R *= scalar;
- this.V6L *= scalar;
- this.V6R *= scalar;
- this.V7L *= scalar;
- this.V7R *= scalar;
+ fixed (void* ptr = &this.V0L)
+ {
+ Marshal.Copy((IntPtr)ptr, dest.Data, dest.Offset, ScalarCount);
+ }
}
///
- /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
+ /// Convert salars to byte-s and copy to dest
///
+ /// Pointer to block
/// Destination
- /// Temporary block provided by the caller
- public void TransformIDCTInto(ref Block8x8F dest, ref Block8x8F tempBlockPtr)
- {
- this.TransposeInto(ref tempBlockPtr);
- tempBlockPtr.IDCT8x4_LeftPart(ref dest);
- tempBlockPtr.IDCT8x4_RightPart(ref dest);
-
- dest.TransposeInto(ref tempBlockPtr);
-
- tempBlockPtr.IDCT8x4_LeftPart(ref dest);
- tempBlockPtr.IDCT8x4_RightPart(ref dest);
-
- dest.MultiplyAllInplace(C_0_125);
- }
-
- ///
- /// Pointer-based "Indexer" (getter part)
- ///
- /// Block pointer
- /// Index
- /// The scalar value at the specified index
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
+ public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest)
{
- float* fp = (float*)blockPtr;
- return fp[idx];
+ float* fPtr = (float*)blockPtr;
+ for (int i = 0; i < ScalarCount; i++)
+ {
+ dest[i] = (byte)*fPtr;
+ fPtr++;
+ }
}
///
- /// Pointer-based "Indexer" (setter part)
+ /// Copy raw 32bit floating point data to dest
///
/// Block pointer
- /// Index
- /// Value
+ /// Destination
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
+ public static unsafe void CopyTo(Block8x8F* blockPtr, MutableSpan dest)
{
- float* fp = (float*)blockPtr;
- fp[idx] = value;
+ Marshal.Copy((IntPtr)blockPtr, dest.Data, dest.Offset, ScalarCount);
}
///
- /// Un-zig
+ /// Copy raw 32bit floating point data to dest
///
- /// Block pointer
- /// Qt pointer
- /// Unzig pointer
+ /// Destination
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static unsafe void UnZig(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
+ public unsafe void CopyTo(float[] dest)
{
- float* b = (float*)blockPtr;
- float* qtp = (float*)qtPtr;
- for (int zig = 0; zig < BlockF.BlockSize; zig++)
+ fixed (void* ptr = &this.V0L)
{
- float* unzigPos = b + unzigPtr[zig];
- float val = *unzigPos;
- val *= qtp[zig];
- *unzigPos = val;
+ Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount);
}
}
@@ -251,7 +217,7 @@ namespace ImageSharp.Formats
/// Copy raw 32bit floating point data to dest
///
/// Destination
- internal unsafe void CopyTo(MutableSpan dest)
+ public unsafe void CopyTo(MutableSpan dest)
{
fixed (Vector4* ptr = &this.V0L)
{
@@ -264,160 +230,73 @@ namespace ImageSharp.Formats
}
///
- /// Load raw 32bit floating point data from source
- ///
- /// Source
- internal unsafe void LoadFrom(MutableSpan source)
- {
- fixed (Vector4* ptr = &this.V0L)
- {
- float* fp = (float*)ptr;
- for (int i = 0; i < ScalarCount; i++)
- {
- fp[i] = source[i];
- }
- }
- }
-
- ///
- /// Do IDCT internal operations on the left part of the block. Original source:
- /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
+ /// Multiply all elements of the block.
///
- /// Destination block
+ /// Vector to multiply by
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void IDCT8x4_LeftPart(ref Block8x8F d)
+ public void MultiplyAllInplace(Vector4 scaleVec)
{
- Vector4 my1 = this.V1L;
- Vector4 my7 = this.V7L;
- Vector4 mz0 = my1 + my7;
-
- Vector4 my3 = this.V3L;
- Vector4 mz2 = my3 + my7;
- Vector4 my5 = this.V5L;
- Vector4 mz1 = my3 + my5;
- Vector4 mz3 = my1 + my5;
-
- Vector4 mz4 = (mz0 + mz1) * C_1_175876;
-
- mz2 = (mz2 * C_1_961571) + mz4;
- mz3 = (mz3 * C_0_390181) + mz4;
- mz0 = mz0 * C_0_899976;
- mz1 = mz1 * C_2_562915;
-
- Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
- Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
- Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
- Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
-
- Vector4 my2 = this.V2L;
- Vector4 my6 = this.V6L;
- mz4 = (my2 + my6) * C_0_541196;
- Vector4 my0 = this.V0L;
- Vector4 my4 = this.V4L;
- mz0 = my0 + my4;
- mz1 = my0 - my4;
-
- mz2 = mz4 + (my6 * C_1_847759);
- mz3 = mz4 + (my2 * C_0_765367);
-
- my0 = mz0 + mz3;
- my3 = mz0 - mz3;
- my1 = mz1 + mz2;
- my2 = mz1 - mz2;
-
- d.V0L = my0 + mb0;
- d.V7L = my0 - mb0;
- d.V1L = my1 + mb1;
- d.V6L = my1 - mb1;
- d.V2L = my2 + mb2;
- d.V5L = my2 - mb2;
- d.V3L = my3 + mb3;
- d.V4L = my3 - mb3;
+ this.V0L *= scaleVec;
+ this.V0R *= scaleVec;
+ this.V1L *= scaleVec;
+ this.V1R *= scaleVec;
+ this.V2L *= scaleVec;
+ this.V2R *= scaleVec;
+ this.V3L *= scaleVec;
+ this.V3R *= scaleVec;
+ this.V4L *= scaleVec;
+ this.V4R *= scaleVec;
+ this.V5L *= scaleVec;
+ this.V5R *= scaleVec;
+ this.V6L *= scaleVec;
+ this.V6R *= scaleVec;
+ this.V7L *= scaleVec;
+ this.V7R *= scaleVec;
}
///
- /// Do IDCT internal operations on the right part of the block.
- /// Original source:
- /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
+ /// Adds a vector to all elements of the block.
///
- /// Destination Block pointer
+ /// The added vector
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void IDCT8x4_RightPart(ref Block8x8F destBlockPtr)
+ public void AddToAllInplace(Vector4 diff)
{
- Vector4 my1 = this.V1R;
- Vector4 my7 = this.V7R;
- Vector4 mz0 = my1 + my7;
-
- Vector4 my3 = this.V3R;
- Vector4 mz2 = my3 + my7;
- Vector4 my5 = this.V5R;
- Vector4 mz1 = my3 + my5;
- Vector4 mz3 = my1 + my5;
-
- Vector4 mz4 = (mz0 + mz1) * C_1_175876;
-
- mz2 = (mz2 * C_1_961571) + mz4;
- mz3 = (mz3 * C_0_390181) + mz4;
- mz0 = mz0 * C_0_899976;
- mz1 = mz1 * C_2_562915;
-
- Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
- Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
- Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
- Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
-
- Vector4 my2 = this.V2R;
- Vector4 my6 = this.V6R;
- mz4 = (my2 + my6) * C_0_541196;
- Vector4 my0 = this.V0R;
- Vector4 my4 = this.V4R;
- mz0 = my0 + my4;
- mz1 = my0 - my4;
-
- mz2 = mz4 + (my6 * C_1_847759);
- mz3 = mz4 + (my2 * C_0_765367);
-
- my0 = mz0 + mz3;
- my3 = mz0 - mz3;
- my1 = mz1 + mz2;
- my2 = mz1 - mz2;
-
- destBlockPtr.V0R = my0 + mb0;
- destBlockPtr.V7R = my0 - mb0;
- destBlockPtr.V1R = my1 + mb1;
- destBlockPtr.V6R = my1 - mb1;
- destBlockPtr.V2R = my2 + mb2;
- destBlockPtr.V5R = my2 - mb2;
- destBlockPtr.V3R = my3 + mb3;
- destBlockPtr.V4R = my3 - mb3;
+ this.V0L += diff;
+ this.V0R += diff;
+ this.V1L += diff;
+ this.V1R += diff;
+ this.V2L += diff;
+ this.V2R += diff;
+ this.V3L += diff;
+ this.V3R += diff;
+ this.V4L += diff;
+ this.V4R += diff;
+ this.V5L += diff;
+ this.V5R += diff;
+ this.V6L += diff;
+ this.V6R += diff;
+ this.V7L += diff;
+ this.V7R += diff;
}
///
- /// Fill the block with defaults (zeroes)
+ /// Un-zig
///
+ /// Block pointer
+ /// Qt pointer
+ /// Unzig pointer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void Clear()
+ public static unsafe void UnZig(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{
- // The cheapest way to do this in C#:
- this = default(Block8x8F);
- }
-
- ///
- /// TODO: Should be removed when BlockF goes away
- ///
- /// Legacy block
- internal void LoadFrom(ref BlockF legacyBlock)
- {
- this.LoadFrom(legacyBlock.Data);
- }
-
- ///
- /// TODO: Should be removed when BlockF goes away
- ///
- /// Legacy block
- internal void CopyTo(ref BlockF legacyBlock)
- {
- this.CopyTo(legacyBlock.Data);
+ float* b = (float*)blockPtr;
+ float* qtp = (float*)qtPtr;
+ for (int zig = 0; zig < ScalarCount; zig++)
+ {
+ float* unzigPos = b + unzigPtr[zig];
+ float val = *unzigPos;
+ val *= qtp[zig];
+ *unzigPos = val;
+ }
}
///
@@ -427,7 +306,7 @@ namespace ImageSharp.Formats
/// Stride offset
/// Temp Block pointer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal unsafe void CopyColorsTo(MutableSpan buffer, int stride, Block8x8F* tempBlockPtr)
+ public unsafe void CopyColorsTo(MutableSpan buffer, int stride, Block8x8F* tempBlockPtr)
{
this.TransformByteConvetibleColorValuesInto(ref *tempBlockPtr);
@@ -446,5 +325,53 @@ namespace ImageSharp.Formats
src += 8;
}
}
+
+ ///
+ /// Unzig the elements of src into dest, while dividing them by elements of qt and rounding the values
+ ///
+ /// Source block
+ /// Destination block
+ /// Quantization table
+ /// Pointer to elements
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void UnZigDivRound(Block8x8F* src, Block8x8F* dest, Block8x8F* qt, int* unzigPtr)
+ {
+ float* s = (float*)src;
+ float* d = (float*)dest;
+ float* q = (float*)qt;
+
+ for (int zig = 0; zig < ScalarCount; zig++)
+ {
+ float val = s[unzigPtr[zig]] / q[zig];
+ val = (int)val;
+ d[zig] = val;
+ }
+ }
+
+ ///
+ /// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block.
+ ///
+ /// The destination block.
+ /// The source block.
+ public static unsafe void Scale16X16To8X8(Block8x8F* destination, Block8x8F* source)
+ {
+ float* d = (float*)destination;
+ for (int i = 0; i < 4; i++)
+ {
+ int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
+
+ float* iSource = (float*)(source + i);
+
+ for (int y = 0; y < 4; y++)
+ {
+ for (int x = 0; x < 4; x++)
+ {
+ int j = (16 * y) + (2 * x);
+ float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
+ d[(8 * y) + x + dstOff] = (sum + 2) / 4;
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/BlockQuad.cs b/src/ImageSharp/Formats/Jpg/Components/BlockQuad.cs
new file mode 100644
index 000000000..63453da21
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/BlockQuad.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats.Jpg.Components
+{
+ ///
+ /// Poor man's stackalloc: Contains a value-type buffer sized for 4 instances.
+ /// Useful for decoder/encoder operations allocating a block for each Jpeg component.
+ ///
+ internal unsafe struct BlockQuad
+ {
+ ///
+ /// The value-type buffer sized for 4 instances.
+ ///
+ public fixed float Data[4 * Block8x8F.ScalarCount];
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/DCT.cs b/src/ImageSharp/Formats/Jpg/Components/DCT.cs
new file mode 100644
index 000000000..186a23862
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/DCT.cs
@@ -0,0 +1,340 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+// ReSharper disable InconsistentNaming
+
+namespace ImageSharp.Formats.Jpg
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Contains forward and inverse DCT implementations
+ ///
+ internal static class DCT
+ {
+#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
+ private static readonly Vector4 C_1_175876 = new Vector4(1.175876f);
+
+ private static readonly Vector4 C_1_961571 = new Vector4(-1.961571f);
+
+ private static readonly Vector4 C_0_390181 = new Vector4(-0.390181f);
+
+ private static readonly Vector4 C_0_899976 = new Vector4(-0.899976f);
+
+ private static readonly Vector4 C_2_562915 = new Vector4(-2.562915f);
+
+ private static readonly Vector4 C_0_298631 = new Vector4(0.298631f);
+
+ private static readonly Vector4 C_2_053120 = new Vector4(2.053120f);
+
+ private static readonly Vector4 C_3_072711 = new Vector4(3.072711f);
+
+ private static readonly Vector4 C_1_501321 = new Vector4(1.501321f);
+
+ private static readonly Vector4 C_0_541196 = new Vector4(0.541196f);
+
+ private static readonly Vector4 C_1_847759 = new Vector4(-1.847759f);
+
+ private static readonly Vector4 C_0_765367 = new Vector4(0.765367f);
+
+ private static readonly Vector4 C_0_125 = new Vector4(0.1250f);
+#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
+ private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f);
+
+ ///
+ /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
+ ///
+ /// Source
+ /// Destination
+ /// Temporary block provided by the caller
+ public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp)
+ {
+ src.TransposeInto(ref temp);
+ IDCT8x4_LeftPart(ref temp, ref dest);
+ IDCT8x4_RightPart(ref temp, ref dest);
+
+ dest.TransposeInto(ref temp);
+
+ IDCT8x4_LeftPart(ref temp, ref dest);
+ IDCT8x4_RightPart(ref temp, ref dest);
+
+ dest.MultiplyAllInplace(C_0_125);
+ }
+
+ ///
+ /// Do IDCT internal operations on the left part of the block. Original src:
+ /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
+ ///
+ /// The source block
+ /// Destination block
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d)
+ {
+ Vector4 my1 = s.V1L;
+ Vector4 my7 = s.V7L;
+ Vector4 mz0 = my1 + my7;
+
+ Vector4 my3 = s.V3L;
+ Vector4 mz2 = my3 + my7;
+ Vector4 my5 = s.V5L;
+ Vector4 mz1 = my3 + my5;
+ Vector4 mz3 = my1 + my5;
+
+ Vector4 mz4 = (mz0 + mz1) * C_1_175876;
+
+ mz2 = (mz2 * C_1_961571) + mz4;
+ mz3 = (mz3 * C_0_390181) + mz4;
+ mz0 = mz0 * C_0_899976;
+ mz1 = mz1 * C_2_562915;
+
+ Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
+ Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
+ Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
+ Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
+
+ Vector4 my2 = s.V2L;
+ Vector4 my6 = s.V6L;
+ mz4 = (my2 + my6) * C_0_541196;
+ Vector4 my0 = s.V0L;
+ Vector4 my4 = s.V4L;
+ mz0 = my0 + my4;
+ mz1 = my0 - my4;
+
+ mz2 = mz4 + (my6 * C_1_847759);
+ mz3 = mz4 + (my2 * C_0_765367);
+
+ my0 = mz0 + mz3;
+ my3 = mz0 - mz3;
+ my1 = mz1 + mz2;
+ my2 = mz1 - mz2;
+
+ d.V0L = my0 + mb0;
+ d.V7L = my0 - mb0;
+ d.V1L = my1 + mb1;
+ d.V6L = my1 - mb1;
+ d.V2L = my2 + mb2;
+ d.V5L = my2 - mb2;
+ d.V3L = my3 + mb3;
+ d.V4L = my3 - mb3;
+ }
+
+ ///
+ /// Do IDCT internal operations on the right part of the block.
+ /// Original src:
+ /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
+ ///
+ /// The source block
+ /// The destination block
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d)
+ {
+ Vector4 my1 = s.V1R;
+ Vector4 my7 = s.V7R;
+ Vector4 mz0 = my1 + my7;
+
+ Vector4 my3 = s.V3R;
+ Vector4 mz2 = my3 + my7;
+ Vector4 my5 = s.V5R;
+ Vector4 mz1 = my3 + my5;
+ Vector4 mz3 = my1 + my5;
+
+ Vector4 mz4 = (mz0 + mz1) * C_1_175876;
+
+ mz2 = (mz2 * C_1_961571) + mz4;
+ mz3 = (mz3 * C_0_390181) + mz4;
+ mz0 = mz0 * C_0_899976;
+ mz1 = mz1 * C_2_562915;
+
+ Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
+ Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
+ Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
+ Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
+
+ Vector4 my2 = s.V2R;
+ Vector4 my6 = s.V6R;
+ mz4 = (my2 + my6) * C_0_541196;
+ Vector4 my0 = s.V0R;
+ Vector4 my4 = s.V4R;
+ mz0 = my0 + my4;
+ mz1 = my0 - my4;
+
+ mz2 = mz4 + (my6 * C_1_847759);
+ mz3 = mz4 + (my2 * C_0_765367);
+
+ my0 = mz0 + mz3;
+ my3 = mz0 - mz3;
+ my1 = mz1 + mz2;
+ my2 = mz1 - mz2;
+
+ d.V0R = my0 + mb0;
+ d.V7R = my0 - mb0;
+ d.V1R = my1 + mb1;
+ d.V6R = my1 - mb1;
+ d.V2R = my2 + mb2;
+ d.V5R = my2 - mb2;
+ d.V3R = my3 + mb3;
+ d.V4R = my3 - mb3;
+ }
+
+ ///
+ /// Original:
+ ///
+ /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15
+ ///
+ ///
+ /// Source
+ /// Destination
+ public static void FDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d)
+ {
+ Vector4 c0 = s.V0L;
+ Vector4 c1 = s.V7L;
+ Vector4 t0 = c0 + c1;
+ Vector4 t7 = c0 - c1;
+
+ c1 = s.V6L;
+ c0 = s.V1L;
+ Vector4 t1 = c0 + c1;
+ Vector4 t6 = c0 - c1;
+
+ c1 = s.V5L;
+ c0 = s.V2L;
+ Vector4 t2 = c0 + c1;
+ Vector4 t5 = c0 - c1;
+
+ c0 = s.V3L;
+ c1 = s.V4L;
+ Vector4 t3 = c0 + c1;
+ Vector4 t4 = c0 - c1;
+
+ c0 = t0 + t3;
+ Vector4 c3 = t0 - t3;
+ c1 = t1 + t2;
+ Vector4 c2 = t1 - t2;
+
+ d.V0L = c0 + c1;
+ d.V4L = c0 - c1;
+
+ Vector4 w0 = new Vector4(0.541196f);
+ Vector4 w1 = new Vector4(1.306563f);
+
+ d.V2L = (w0 * c2) + (w1 * c3);
+ d.V6L = (w0 * c3) - (w1 * c2);
+
+ w0 = new Vector4(1.175876f);
+ w1 = new Vector4(0.785695f);
+ c3 = (w0 * t4) + (w1 * t7);
+ c0 = (w0 * t7) - (w1 * t4);
+
+ w0 = new Vector4(1.387040f);
+ w1 = new Vector4(0.275899f);
+ c2 = (w0 * t5) + (w1 * t6);
+ c1 = (w0 * t6) - (w1 * t5);
+
+ d.V3L = c0 - c2;
+ d.V5L = c3 - c1;
+
+ Vector4 invsqrt2 = new Vector4(0.707107f);
+ c0 = (c0 + c2) * invsqrt2;
+ c3 = (c3 + c1) * invsqrt2;
+
+ d.V1L = c0 + c3;
+ d.V7L = c0 - c3;
+ }
+
+ ///
+ /// Original:
+ ///
+ /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15
+ ///
+ ///
+ /// Source
+ /// Destination
+ public static void FDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d)
+ {
+ Vector4 c0 = s.V0R;
+ Vector4 c1 = s.V7R;
+ Vector4 t0 = c0 + c1;
+ Vector4 t7 = c0 - c1;
+
+ c1 = s.V6R;
+ c0 = s.V1R;
+ Vector4 t1 = c0 + c1;
+ Vector4 t6 = c0 - c1;
+
+ c1 = s.V5R;
+ c0 = s.V2R;
+ Vector4 t2 = c0 + c1;
+ Vector4 t5 = c0 - c1;
+
+ c0 = s.V3R;
+ c1 = s.V4R;
+ Vector4 t3 = c0 + c1;
+ Vector4 t4 = c0 - c1;
+
+ c0 = t0 + t3;
+ Vector4 c3 = t0 - t3;
+ c1 = t1 + t2;
+ Vector4 c2 = t1 - t2;
+
+ d.V0R = c0 + c1;
+ d.V4R = c0 - c1;
+
+ Vector4 w0 = new Vector4(0.541196f);
+ Vector4 w1 = new Vector4(1.306563f);
+
+ d.V2R = (w0 * c2) + (w1 * c3);
+ d.V6R = (w0 * c3) - (w1 * c2);
+
+ w0 = new Vector4(1.175876f);
+ w1 = new Vector4(0.785695f);
+ c3 = (w0 * t4) + (w1 * t7);
+ c0 = (w0 * t7) - (w1 * t4);
+
+ w0 = new Vector4(1.387040f);
+ w1 = new Vector4(0.275899f);
+ c2 = (w0 * t5) + (w1 * t6);
+ c1 = (w0 * t6) - (w1 * t5);
+
+ d.V3R = c0 - c2;
+ d.V5R = c3 - c1;
+
+ c0 = (c0 + c2) * InvSqrt2;
+ c3 = (c3 + c1) * InvSqrt2;
+
+ d.V1R = c0 + c3;
+ d.V7R = c0 - c3;
+ }
+
+ ///
+ /// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
+ ///
+ /// Source
+ /// Destination
+ /// Temporary block provided by the caller
+ /// If true, a constant -128.0 offset is applied for all values before FDCT
+ public static void TransformFDCT(
+ ref Block8x8F src,
+ ref Block8x8F dest,
+ ref Block8x8F temp,
+ bool offsetSourceByNeg128 = true)
+ {
+ src.TransposeInto(ref temp);
+ if (offsetSourceByNeg128)
+ {
+ temp.AddToAllInplace(new Vector4(-128));
+ }
+
+ FDCT8x4_LeftPart(ref temp, ref dest);
+ FDCT8x4_RightPart(ref temp, ref dest);
+
+ dest.TransposeInto(ref temp);
+
+ FDCT8x4_LeftPart(ref temp, ref dest);
+ FDCT8x4_RightPart(ref temp, ref dest);
+
+ dest.MultiplyAllInplace(C_0_125);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Bits.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs
similarity index 95%
rename from src/ImageSharp/Formats/Jpg/Components/Bits.cs
rename to src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs
index d7449f4a3..88aa8a3fe 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Bits.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bits.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
using System.Runtime.CompilerServices;
@@ -47,7 +47,7 @@ namespace ImageSharp.Formats
// Grab the decode bytes, use them and then set them
// back on the decoder.
- var decoderBytes = decoder.Bytes;
+ Bytes decoderBytes = decoder.Bytes;
byte c = decoderBytes.ReadByteStuffedByte(decoder.InputStream, out errorCode);
decoder.Bytes = decoderBytes;
@@ -84,7 +84,7 @@ namespace ImageSharp.Formats
{
if (this.UnreadBits < t)
{
- var errorCode = this.EnsureNBits(t, decoder);
+ JpegDecoderCore.ErrorCodes errorCode = this.EnsureNBits(t, decoder);
if (errorCode != JpegDecoderCore.ErrorCodes.NoError)
{
throw new JpegDecoderCore.MissingFF00Exception();
diff --git a/src/ImageSharp/Formats/Jpg/Components/Bytes.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs
similarity index 99%
rename from src/ImageSharp/Formats/Jpg/Components/Bytes.cs
rename to src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs
index 127ba478b..b91420b42 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Bytes.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Bytes.cs
@@ -2,7 +2,7 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
using System;
using System.Buffers;
diff --git a/src/ImageSharp/Formats/Jpg/Components/Component.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs
similarity index 96%
rename from src/ImageSharp/Formats/Jpg/Components/Component.cs
rename to src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs
index f70dbff3f..5b53db190 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Component.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/Component.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
///
/// Represents a single color component
diff --git a/src/ImageSharp/Formats/Jpg/Components/GrayImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs
similarity index 98%
rename from src/ImageSharp/Formats/Jpg/Components/GrayImage.cs
rename to src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs
index 5f5f015ad..caa30e62d 100644
--- a/src/ImageSharp/Formats/Jpg/Components/GrayImage.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/GrayImage.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
///
/// Represents a grayscale image
diff --git a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs
similarity index 95%
rename from src/ImageSharp/Formats/Jpg/Components/Huffman.cs
rename to src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs
index c0d5a5caa..a148cc558 100644
--- a/src/ImageSharp/Formats/Jpg/Components/Huffman.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/HuffmanTree.cs
@@ -1,8 +1,8 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
using System;
using System.Buffers;
@@ -10,7 +10,7 @@ namespace ImageSharp.Formats
///
/// Represents a Huffman tree
///
- internal struct Huffman : IDisposable
+ internal struct HuffmanTree : IDisposable
{
///
/// Gets or sets the number of codes in the tree.
diff --git a/src/ImageSharp/Formats/Jpg/Components/YCbCrImage.cs b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
similarity index 99%
rename from src/ImageSharp/Formats/Jpg/Components/YCbCrImage.cs
rename to src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
index 603324bcc..cba9c4461 100644
--- a/src/ImageSharp/Formats/Jpg/Components/YCbCrImage.cs
+++ b/src/ImageSharp/Formats/Jpg/Components/Decoder/YCbCrImage.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
///
/// Represents an image made up of three color components (luminance, blue chroma, red chroma)
diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs
new file mode 100644
index 000000000..3875cc12f
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffIndex.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats.Jpg
+{
+ ///
+ /// Enumerates the Huffman tables
+ ///
+ internal enum HuffIndex
+ {
+ ///
+ /// The DC luminance huffman table index
+ ///
+ LuminanceDC = 0,
+
+ // ReSharper disable UnusedMember.Local
+
+ ///
+ /// The AC luminance huffman table index
+ ///
+ LuminanceAC = 1,
+
+ ///
+ /// The DC chrominance huffman table index
+ ///
+ ChrominanceDC = 2,
+
+ ///
+ /// The AC chrominance huffman table index
+ ///
+ ChrominanceAC = 3,
+
+ // ReSharper restore UnusedMember.Local
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs
new file mode 100644
index 000000000..d0003b919
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanLut.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpg
+{
+ ///
+ /// A compiled look-up table representation of a huffmanSpec.
+ /// Each value maps to a uint32 of which the 8 most significant bits hold the
+ /// codeword size in bits and the 24 least significant bits hold the codeword.
+ /// The maximum codeword size is 16 bits.
+ ///
+ internal struct HuffmanLut
+ {
+ ///
+ /// The compiled representations of theHuffmanSpec.
+ ///
+ public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4];
+
+ ///
+ /// Initializes static members of the struct.
+ ///
+ static HuffmanLut()
+ {
+ // Initialize the Huffman tables
+ for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++)
+ {
+ TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]);
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// dasd
+ public HuffmanLut(HuffmanSpec spec)
+ {
+ int maxValue = 0;
+
+ foreach (byte v in spec.Values)
+ {
+ if (v > maxValue)
+ {
+ maxValue = v;
+ }
+ }
+
+ this.Values = new uint[maxValue + 1];
+
+ int code = 0;
+ int k = 0;
+
+ for (int i = 0; i < spec.Count.Length; i++)
+ {
+ int bits = (i + 1) << 24;
+ for (int j = 0; j < spec.Count[i]; j++)
+ {
+ this.Values[spec.Values[k]] = (uint)(bits | code);
+ code++;
+ k++;
+ }
+
+ code <<= 1;
+ }
+ }
+
+ ///
+ /// Gets the collection of huffman values.
+ ///
+ public uint[] Values { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs
new file mode 100644
index 000000000..a0eea6e71
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/HuffmanSpec.cs
@@ -0,0 +1,136 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats.Jpg
+{
+ ///
+ /// The Huffman encoding specifications.
+ ///
+ internal struct HuffmanSpec
+ {
+#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines
+
+ ///
+ /// The Huffman encoding specifications.
+ /// This encoder uses the same Huffman encoding for all images.
+ ///
+ public static readonly HuffmanSpec[] TheHuffmanSpecs =
+ {
+ // Luminance DC.
+ new HuffmanSpec(
+ new byte[]
+ {
+ 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0
+ },
+ new byte[]
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+ }),
+ new HuffmanSpec(
+ new byte[]
+ {
+ 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0,
+ 0, 1, 125
+ },
+ new byte[]
+ {
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11,
+ 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13,
+ 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32,
+ 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1,
+ 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
+ 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56,
+ 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
+ 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
+ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+ 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+ 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
+ 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
+ 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
+ 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+ 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4,
+ 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
+ 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa
+ }),
+ new HuffmanSpec(
+ new byte[]
+ {
+ 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0
+ },
+ new byte[]
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+ }),
+
+ // Chrominance AC.
+ new HuffmanSpec(
+ new byte[]
+ {
+ 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0,
+ 1, 2, 119
+ },
+ new byte[]
+ {
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
+ 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+ 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81,
+ 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1,
+ 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62,
+ 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,
+ 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
+ 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46,
+ 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55,
+ 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64,
+ 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73,
+ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
+ 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
+ 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
+ 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2,
+ 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+ 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
+ 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3,
+ 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
+ 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa
+ })
+ };
+#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines
+ ///
+ /// Gets count[i] - The number of codes of length i bits.
+ ///
+ public readonly byte[] Count;
+
+ ///
+ /// Gets value[i] - The decoded value of the codeword at the given index.
+ ///
+ public readonly byte[] Values;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// The number of codes.
+ ///
+ ///
+ /// The decoded values.
+ ///
+ public HuffmanSpec(byte[] count, byte[] values)
+ {
+ this.Count = count;
+ this.Values = values;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs b/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs
new file mode 100644
index 000000000..5a469e0e9
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Components/Encoder/QuantIndex.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats.Jpg
+{
+ ///
+ /// Enumerates the quantization tables
+ ///
+ internal enum QuantIndex
+ {
+ ///
+ /// The luminance quantization table index
+ ///
+ Luminance = 0,
+
+ ///
+ /// The chrominance quantization table index
+ ///
+ Chrominance = 1,
+ }
+}
\ No newline at end of file
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/Components/MutableSpanExtensions.cs b/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs
deleted file mode 100644
index 42edcd3c4..000000000
--- a/src/ImageSharp/Formats/Jpg/Components/MutableSpanExtensions.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp.Formats.Jpg.Components
-{
- using System.Numerics;
- using System.Runtime.CompilerServices;
-
- ///
- /// MutableSpan Extensions
- ///
- internal static class MutableSpanExtensions
- {
- ///
- /// Slice
- ///
- /// The type of the data in the span
- /// The data array
- /// The offset
- /// The new
- public static MutableSpan Slice(this T[] array, int offset) => new MutableSpan(array, offset);
-
- ///
- /// Save to a Vector4
- ///
- /// The data
- /// The vector to save to
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void SaveTo(this MutableSpan data, ref Vector4 v)
- {
- v.X = data[0];
- v.Y = data[1];
- v.Z = data[2];
- v.W = data[3];
- }
-
- ///
- /// Save to a Vector4
- ///
- /// The data
- /// The vector to save to
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void SaveTo(this MutableSpan data, ref Vector4 v)
- {
- v.X = data[0];
- v.Y = data[1];
- v.Z = data[2];
- v.W = data[3];
- }
-
- ///
- /// Load from Vector4
- ///
- /// The data
- /// The vector to load from
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void LoadFrom(this MutableSpan data, ref Vector4 v)
- {
- data[0] = v.X;
- data[1] = v.Y;
- data[2] = v.Z;
- data[3] = v.W;
- }
-
- ///
- /// Load from Vector4
- ///
- /// The data
- /// The vector to load from
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void LoadFrom(this MutableSpan data, ref Vector4 v)
- {
- data[0] = (int)v.X;
- data[1] = (int)v.Y;
- data[2] = (int)v.Z;
- data[3] = (int)v.W;
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
index e302e064c..761ad891e 100644
--- a/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegDecoderCore.cs
@@ -10,6 +10,7 @@ namespace ImageSharp.Formats
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
+ using ImageSharp.Formats.Jpg;
///
/// Performs the jpeg decoding operation.
@@ -63,19 +64,6 @@ namespace ImageSharp.Formats
///
private const int AcTable = 1;
- ///
- /// Unzig maps from the zigzag ordering to the natural ordering. For example,
- /// unzig[3] is the column and row of the fourth element in zigzag order. The
- /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
- ///
- private static readonly int[] Unzig =
- {
- 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33,
- 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50,
- 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46,
- 53, 60, 61, 54, 47, 55, 62, 63,
- };
-
///
/// The component array
///
@@ -89,7 +77,7 @@ namespace ImageSharp.Formats
///
/// The huffman trees
///
- private readonly Huffman[] huffmanTrees;
+ private readonly HuffmanTree[] huffmanTrees;
///
/// Quantization tables, in zigzag order.
@@ -199,10 +187,10 @@ namespace ImageSharp.Formats
public JpegDecoderCore()
{
// this.huffmanTrees = new Huffman[MaxTc + 1, MaxTh + 1];
- this.huffmanTrees = new Huffman[(MaxTc + 1) * (MaxTh + 1)];
+ this.huffmanTrees = new HuffmanTree[(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);
@@ -559,42 +547,42 @@ namespace ImageSharp.Formats
}
}
- private void ProcessDefineHuffmanTablesMarkerLoop(ref Huffman huffman, ref int remaining)
+ private void ProcessDefineHuffmanTablesMarkerLoop(ref HuffmanTree huffmanTree, ref int 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;
+ huffmanTree.Length = 0;
int[] ncodes = new int[MaxCodeLength];
for (int i = 0; i < ncodes.Length; i++)
{
ncodes[i] = this.temp[i + 1];
- huffman.Length += ncodes[i];
+ huffmanTree.Length += ncodes[i];
}
- if (huffman.Length == 0)
+ if (huffmanTree.Length == 0)
{
throw new ImageFormatException("Huffman table has zero length");
}
- if (huffman.Length > MaxNCodes)
+ if (huffmanTree.Length > MaxNCodes)
{
throw new ImageFormatException("Huffman table has excessive length");
}
- remaining -= huffman.Length + 17;
+ remaining -= huffmanTree.Length + 17;
if (remaining < 0)
{
throw new ImageFormatException("DHT has wrong length");
}
- this.ReadFull(huffman.Values, 0, huffman.Length);
+ this.ReadFull(huffmanTree.Values, 0, huffmanTree.Length);
// Derive the look-up table.
- for (int i = 0; i < huffman.Lut.Length; i++)
+ for (int i = 0; i < huffmanTree.Lut.Length; i++)
{
- huffman.Lut[i] = 0;
+ huffmanTree.Lut[i] = 0;
}
uint x = 0, code = 0;
@@ -611,11 +599,11 @@ namespace ImageSharp.Formats
// 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));
+ ushort lutValue = (ushort)((huffmanTree.Values[x] << 8) | (2 + i));
for (int k = 0; k < 1 << (7 - i); k++)
{
- huffman.Lut[base2 | k] = lutValue;
+ huffmanTree.Lut[base2 | k] = lutValue;
}
code++;
@@ -630,15 +618,15 @@ namespace ImageSharp.Formats
int nc = ncodes[i];
if (nc == 0)
{
- huffman.MinCodes[i] = -1;
- huffman.MaxCodes[i] = -1;
- huffman.Indices[i] = -1;
+ huffmanTree.MinCodes[i] = -1;
+ huffmanTree.MaxCodes[i] = -1;
+ huffmanTree.Indices[i] = -1;
}
else
{
- huffman.MinCodes[i] = c;
- huffman.MaxCodes[i] = c + nc - 1;
- huffman.Indices[i] = index;
+ huffmanTree.MinCodes[i] = c;
+ huffmanTree.MaxCodes[i] = c + nc - 1;
+ huffmanTree.Indices[i] = index;
c += nc;
index += nc;
}
@@ -650,23 +638,23 @@ namespace ImageSharp.Formats
///
/// Returns the next Huffman-coded value from the bit-stream, decoded according to the given value.
///
- /// The huffman value
+ /// The huffman value
/// The
- private byte DecodeHuffman(ref Huffman huffman)
+ private byte DecodeHuffman(ref HuffmanTree huffmanTree)
{
// Copy stuff to the stack:
- if (huffman.Length == 0)
+ if (huffmanTree.Length == 0)
{
throw new ImageFormatException("Uninitialized Huffman table");
}
if (this.bits.UnreadBits < 8)
{
- var errorCode = this.bits.EnsureNBits(8, this);
+ ErrorCodes errorCode = this.bits.EnsureNBits(8, this);
if (errorCode == ErrorCodes.NoError)
{
- ushort v = huffman.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff];
+ ushort v = huffmanTree.Lut[(this.bits.Accumulator >> (this.bits.UnreadBits - LutSize)) & 0xff];
if (v != 0)
{
@@ -687,7 +675,7 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits == 0)
{
- var errorCode = this.bits.EnsureNBits(1, this);
+ ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
if (errorCode != ErrorCodes.NoError)
{
throw new MissingFF00Exception();
@@ -702,9 +690,9 @@ namespace ImageSharp.Formats
this.bits.UnreadBits--;
this.bits.Mask >>= 1;
- if (code <= huffman.MaxCodes[i])
+ if (code <= huffmanTree.MaxCodes[i])
{
- return huffman.Values[huffman.Indices[i] + code - huffman.MinCodes[i]];
+ return huffmanTree.Values[huffmanTree.Indices[i] + code - huffmanTree.MinCodes[i]];
}
code <<= 1;
@@ -721,7 +709,7 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits == 0)
{
- var errorCode = this.bits.EnsureNBits(1, this);
+ ErrorCodes errorCode = this.bits.EnsureNBits(1, this);
if (errorCode != ErrorCodes.NoError)
{
throw new MissingFF00Exception();
@@ -743,7 +731,7 @@ namespace ImageSharp.Formats
{
if (this.bits.UnreadBits < count)
{
- var errorCode = this.bits.EnsureNBits(count, this);
+ ErrorCodes errorCode = this.bits.EnsureNBits(count, this);
if (errorCode != ErrorCodes.NoError)
{
throw new MissingFF00Exception();
@@ -1066,32 +1054,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];
}
@@ -1486,7 +1474,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;
@@ -1497,7 +1485,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");
}
@@ -1531,7 +1519,7 @@ namespace ImageSharp.Formats
int compIndex = scan[i].Index;
if (this.progCoeffs[compIndex] == null)
{
- var size = mxx * myy * this.componentArray[compIndex].HorizontalFactor
+ int size = mxx * myy * this.componentArray[compIndex].HorizontalFactor
* this.componentArray[compIndex].VerticalFactor;
this.progCoeffs[compIndex] = new Block8x8F[size];
@@ -1556,10 +1544,9 @@ namespace ImageSharp.Formats
Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F);
- // Tricky way to copy contents of the Unzig static variable to the stack:
- StackallocUnzigData unzigOnStack = default(StackallocUnzigData);
- int* unzigPtr = unzigOnStack.Data;
- Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
+ UnzigData unzig = UnzigData.Create();
+
+ int* unzigPtr = unzig.Data;
for (int my = 0; my < myy; my++)
{
@@ -1612,7 +1599,7 @@ namespace ImageSharp.Formats
}
}
- var qtIndex = this.componentArray[compIndex].Selector;
+ int qtIndex = this.componentArray[compIndex].Selector;
// TODO: Find a way to clean up this mess
fixed (Block8x8F* qtp = &this.quantizationTables[qtIndex])
@@ -1727,7 +1714,7 @@ namespace ImageSharp.Formats
int bx,
Block8x8F* qt)
{
- var huffmannIdx = (AcTable * ThRowSize) + scan[i].AcTableSelector;
+ int huffmannIdx = (AcTable * ThRowSize) + scan[i].AcTableSelector;
if (ah != 0)
{
this.Refine(b, ref this.huffmanTrees[huffmannIdx], unzigPtr, zigStart, zigEnd, 1 << al);
@@ -1802,7 +1789,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.
@@ -1823,7 +1810,7 @@ namespace ImageSharp.Formats
// Dequantize, perform the inverse DCT and store the block to the image.
Block8x8F.UnZig(b, qt, unzigPtr);
- b->TransformIDCTInto(ref *temp1, ref *temp2);
+ DCT.TransformIDCT(ref *b, ref *temp1, ref *temp2);
byte[] dst;
int offset;
@@ -1941,7 +1928,7 @@ namespace ImageSharp.Formats
/// The zig-zag start index
/// The zig-zag end index
/// The low transform offset
- private void Refine(Block8x8F* b, ref Huffman h, int* unzigPtr, int zigStart, int zigEnd, int delta)
+ private void Refine(Block8x8F* b, ref HuffmanTree h, int* unzigPtr, int zigStart, int zigEnd, int delta)
{
// Refining a DC component is trivial.
if (zigStart == 0)
@@ -2013,7 +2000,7 @@ namespace ImageSharp.Formats
int blah = zig;
- zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta);
+ zig = this.RefineNonZeroes(b, zig, zigEnd, val0, delta, unzigPtr);
if (zig > zigEnd)
{
throw new ImageFormatException($"Too many coefficients {zig} > {zigEnd}");
@@ -2030,7 +2017,7 @@ namespace ImageSharp.Formats
if (this.eobRun > 0)
{
this.eobRun--;
- this.RefineNonZeroes(b, zig, zigEnd, -1, delta);
+ this.RefineNonZeroes(b, zig, zigEnd, -1, delta, unzigPtr);
}
}
@@ -2043,12 +2030,13 @@ namespace ImageSharp.Formats
/// The zig-zag end index
/// The non-zero entry
/// The low transform offset
+ /// Pointer to the Jpeg Unzig data (data part of )
/// The
- private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta)
+ private int RefineNonZeroes(Block8x8F* b, int zig, int zigEnd, int nz, int delta, int* unzigPtr)
{
for (; zig <= zigEnd; zig++)
{
- int u = Unzig[zig];
+ int u = unzigPtr[zig];
float bu = Block8x8F.GetScalarAt(b, u);
// TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary?
@@ -2247,11 +2235,6 @@ namespace ImageSharp.Formats
public byte AcTableSelector { get; set; }
}
- private struct StackallocUnzigData
- {
- internal fixed int Data[64];
- }
-
///
/// The missing ff00 exception.
///
diff --git a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
index c1c4e9b57..a121a0bc2 100644
--- a/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpg/JpegEncoderCore.cs
@@ -2,11 +2,16 @@
// 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.IO;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.Formats.Jpg;
+ using ImageSharp.Formats.Jpg.Components;
///
/// Image encoder for writing an image to a stream as a jpeg.
@@ -19,123 +24,57 @@ namespace ImageSharp.Formats
private const int QuantizationTableCount = 2;
///
- /// Maps from the zig-zag ordering to the natural ordering. For example,
- /// unzig[3] is the column and row of the fourth element in zig-zag order. The
- /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
+ /// Counts the number of bits needed to hold an integer.
///
- private static readonly int[] Unzig =
- {
- 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26,
- 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57,
- 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31,
- 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
- };
+ private static readonly uint[] BitCountLut =
+ {
+ 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8,
+ };
-#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines
///
- /// The Huffman encoding specifications.
- /// This encoder uses the same Huffman encoding for all images.
+ /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
+ /// - the marker length "\x00\x0c",
+ /// - the number of components "\x03",
+ /// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
+ /// - component 2 uses DC table 1 and AC table 1 "\x02\x11",
+ /// - component 3 uses DC table 1 and AC table 1 "\x03\x11",
+ /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
+ /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
+ /// should be 0x00, 0x3f, 0x00<<4 | 0x00.
///
- private static readonly HuffmanSpec[] TheHuffmanSpecs =
- {
- // Luminance DC.
- new HuffmanSpec(
- new byte[]
- {
- 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
- },
- new byte[]
- {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
- }),
- new HuffmanSpec(
- new byte[]
- {
- 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125
- },
- new byte[]
- {
- 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
- 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71,
- 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1,
- 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72,
- 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
- 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
- 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
- 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
- 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
- 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
- 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
- 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
- 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3,
- 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
- 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
- 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
- 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
- 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
- }),
- new HuffmanSpec(
- new byte[]
- {
- 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
- },
- new byte[]
- {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
- }),
+ private static readonly byte[] SosHeaderYCbCr =
+ {
+ JpegConstants.Markers.XFF, JpegConstants.Markers.SOS,
- // Chrominance AC.
- new HuffmanSpec(
- new byte[]
- {
- 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119
- },
- new byte[]
- {
- 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
- 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
- 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1,
- 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1,
- 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18,
- 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
- 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
- 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
- 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
- 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
- 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
- 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
- 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
- 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
- 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
- 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
- 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
- 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
- })
- };
-#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines
+ // Marker
+ 0x00, 0x0c,
- ///
- /// The compiled representations of theHuffmanSpec.
- ///
- private static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4];
+ // Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
+ 0x03, // Number of components in a scan, 3
+ 0x01, // Component Id Y
+ 0x00, // DC/AC Huffman table
+ 0x02, // Component Id Cb
+ 0x11, // DC/AC Huffman table
+ 0x03, // Component Id Cr
+ 0x11, // DC/AC Huffman table
+ 0x00, // Ss - Start of spectral selection.
+ 0x3f, // Se - End of spectral selection.
+ 0x00
- ///
- /// Counts the number of bits needed to hold an integer.
- ///
- private readonly byte[] bitCountLut =
- {
- 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5,
- 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
- 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8,
- };
+ // Ah + Ah (Successive approximation bit position high + low)
+ };
///
/// The unscaled quantization tables in zig-zag order. Each
@@ -143,24 +82,25 @@ namespace ImageSharp.Formats
/// The values are derived from section K.1 after converting from natural to
/// zig-zag order.
///
- private readonly byte[,] unscaledQuant =
- {
- {
- // Luminance.
- 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40,
- 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51,
- 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81,
- 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92,
- 101, 103, 99,
- },
+ private static readonly byte[,] UnscaledQuant =
{
- // Chrominance.
- 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99,
- 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
- 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
- 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
- }
- };
+ {
+ // Luminance.
+ 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
+ 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
+ 57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
+ 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
+ 100, 120, 92, 101, 103, 99,
+ },
+ {
+ // Chrominance.
+ 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ }
+ };
///
/// A scratch buffer to reduce allocations.
@@ -173,42 +113,11 @@ namespace ImageSharp.Formats
private readonly byte[] emitBuffer = new byte[64];
///
- /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths + identifier.
+ /// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths +
+ /// identifier.
///
private readonly byte[] huffmanBuffer = new byte[179];
- ///
- /// The scaled quantization tables, in zig-zag order.
- ///
- private readonly byte[][] quant = new byte[QuantizationTableCount][];
-
- ///
- /// The SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
- /// - the marker length "\x00\x0c",
- /// - the number of components "\x03",
- /// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
- /// - component 2 uses DC table 1 and AC table 1 "\x02\x11",
- /// - component 3 uses DC table 1 and AC table 1 "\x03\x11",
- /// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
- /// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
- /// should be 0x00, 0x3f, 0x00<<4 | 0x00.
- ///
- private readonly byte[] sosHeaderYCbCr =
- {
- JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, // Marker
- 0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
- 0x03, // Number of components in a scan, 3
- 0x01, // Component Id Y
- 0x00, // DC/AC Huffman table
- 0x02, // Component Id Cb
- 0x11, // DC/AC Huffman table
- 0x03, // Component Id Cr
- 0x11, // DC/AC Huffman table
- 0x00, // Ss - Start of spectral selection.
- 0x3f, // Se - End of spectral selection.
- 0x00 // Ah + Ah (Successive approximation bit position high + low)
- };
-
///
/// The accumulated bits to write to the stream.
///
@@ -220,68 +129,24 @@ namespace ImageSharp.Formats
private uint bitCount;
///
- /// The output stream. All attempted writes after the first error become no-ops.
+ /// The scaled chrominance table, in zig-zag order.
///
- private Stream outputStream;
+ private Block8x8F chrominanceQuantTable;
///
- /// The subsampling method to use.
+ /// The scaled luminance table, in zig-zag order.
///
- private JpegSubsample subsample;
+ private Block8x8F luminanceQuantTable;
///
- /// Initializes static members of the class.
- ///
- static JpegEncoderCore()
- {
- // Initialize the Huffman tables
- for (int i = 0; i < TheHuffmanSpecs.Length; i++)
- {
- TheHuffmanLut[i] = new HuffmanLut(TheHuffmanSpecs[i]);
- }
- }
-
- ///
- /// Enumerates the Huffman tables
+ /// The output stream. All attempted writes after the first error become no-ops.
///
- private enum HuffIndex
- {
- ///
- /// The DC luminance huffman table index
- ///
- LuminanceDC = 0,
-
- ///
- /// The AC luminance huffman table index
- ///
- LuminanceAC = 1,
-
- ///
- /// The DC chrominance huffman table index
- ///
- ChrominanceDC = 2,
-
- ///
- /// The AC chrominance huffman table index
- ///
- ChrominanceAC = 3,
- }
+ private Stream outputStream;
///
- /// Enumerates the quantization tables
+ /// The subsampling method to use.
///
- private enum QuantIndex
- {
- ///
- /// The luminance quantization table index
- ///
- Luminance = 0,
-
- ///
- /// The chrominance quantization table index
- ///
- Chrominance = 1,
- }
+ private JpegSubsample subsample;
///
/// Encode writes the image to the jpeg baseline format with the given options.
@@ -306,11 +171,6 @@ namespace ImageSharp.Formats
this.outputStream = stream;
this.subsample = sample;
- for (int i = 0; i < QuantizationTableCount; i++)
- {
- this.quant[i] = new byte[Block.BlockSize];
- }
-
if (quality < 1)
{
quality = 1;
@@ -333,25 +193,8 @@ namespace ImageSharp.Formats
}
// Initialize the quantization tables.
- for (int i = 0; i < QuantizationTableCount; i++)
- {
- for (int j = 0; j < Block.BlockSize; j++)
- {
- int x = this.unscaledQuant[i, j];
- x = ((x * scale) + 50) / 100;
- if (x < 1)
- {
- x = 1;
- }
-
- if (x > 255)
- {
- x = 255;
- }
-
- this.quant[i][j] = (byte)x;
- }
- }
+ InitQuantizationTable(0, scale, ref this.luminanceQuantTable);
+ InitQuantizationTable(1, scale, ref this.chrominanceQuantTable);
// Compute number of components based on input image type.
int componentCount = 3;
@@ -384,24 +227,101 @@ namespace ImageSharp.Formats
}
///
- /// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero.
+ /// Writes data to "Define Quantization Tables" block for QuantIndex
///
- /// The value to divide.
- /// The value to divide by.
- /// The
- private static int Round(int dividend, int divisor)
+ /// The "Define Quantization Tables" block
+ /// Offset in dqt
+ /// The quantization index
+ /// The quantazation table to copy data from
+ private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F q)
{
- if (dividend >= 0)
+ dqt[offset++] = (byte)i;
+ for (int j = 0; j < Block8x8F.ScalarCount; j++)
{
- return (dividend + (divisor >> 1)) / divisor;
+ dqt[offset++] = (byte)q[j];
}
+ }
+
+ private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
+ {
+ for (int j = 0; j < Block8x8F.ScalarCount; j++)
+ {
+ int x = UnscaledQuant[i, j];
+ x = ((x * scale) + 50) / 100;
+ if (x < 1)
+ {
+ x = 1;
+ }
- return -((-dividend + (divisor >> 1)) / divisor);
+ if (x > 255)
+ {
+ x = 255;
+ }
+
+ quant[j] = x;
+ }
+ }
+
+ ///
+ /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values.
+ ///
+ /// The pixel format.
+ /// The pixel accessor.
+ /// The x-position within the image.
+ /// The y-position within the image.
+ /// The luminance block.
+ /// The red chroma block.
+ /// The blue chroma block.
+ /// Temporal provided by the caller
+ private static void ToYCbCr(
+ PixelAccessor pixels,
+ int x,
+ int y,
+ Block8x8F* yBlock,
+ Block8x8F* cbBlock,
+ Block8x8F* crBlock,
+ PixelArea rgbBytes)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ float* yBlockRaw = (float*)yBlock;
+ float* cbBlockRaw = (float*)cbBlock;
+ float* crBlockRaw = (float*)crBlock;
+
+ rgbBytes.Reset();
+ pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x);
+
+ byte* data = (byte*)rgbBytes.DataPointer;
+
+ for (int j = 0; j < 8; j++)
+ {
+ int j8 = j * 8;
+ for (int i = 0; i < 8; i++)
+ {
+ Vector3 v = new Vector3(data[0], data[1], data[2]);
+
+ // Convert returned bytes into the YCbCr color space. Assume RGBA
+ float yy = (0.299F * v.X) + (0.587F * v.Y) + (0.114F * v.Z);
+ float cb = 128 + ((-0.168736F * v.X) - (0.331264F * v.Y) + (0.5F * v.Z));
+ float cr = 128 + ((0.5F * v.X) - (0.418688F * v.Y) - (0.081312F * v.Z));
+
+ int index = j8 + i;
+
+ yBlockRaw[index] = yy;
+ cbBlockRaw[index] = cb;
+ crBlockRaw[index] = cr;
+
+ data += 3;
+ }
+ }
}
///
/// Emits the least significant count of bits of bits to the bit-stream.
- /// The precondition is bits < 1<<nBits && nBits <= 16.
+ /// The precondition is bits
+ ///
+ /// < 1<<nBits && nBits <= 16
+ ///
+ /// .
///
/// The packed bits.
/// The number of bits
@@ -444,9 +364,10 @@ namespace ImageSharp.Formats
///
/// The index of the Huffman encoder
/// The value to encode.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuff(HuffIndex index, int value)
{
- uint x = TheHuffmanLut[(int)index].Values[value];
+ uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value];
this.Emit(x & ((1 << 24) - 1), x >> 24);
}
@@ -456,6 +377,7 @@ namespace ImageSharp.Formats
/// The index of the Huffman encoder
/// The number of copies to encode.
/// The value to encode.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EmitHuffRLE(HuffIndex index, int runLength, int value)
{
int a = value;
@@ -469,11 +391,11 @@ namespace ImageSharp.Formats
uint bt;
if (a < 0x100)
{
- bt = this.bitCountLut[a];
+ bt = BitCountLut[a];
}
else
{
- bt = 8 + (uint)this.bitCountLut[a >> 8];
+ bt = 8 + BitCountLut[a >> 8];
}
this.EmitHuff(index, (int)((uint)(runLength << 4) | bt));
@@ -483,30 +405,149 @@ namespace ImageSharp.Formats
}
}
+ ///
+ /// Encodes the image with no subsampling.
+ ///
+ /// The pixel format.
+ /// The pixel accessor providing access to the image pixels.
+ private void Encode444(PixelAccessor pixels)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ // TODO: Need a JpegEncoderScanProcessor struct to encapsulate all this mess:
+ Block8x8F b = default(Block8x8F);
+ Block8x8F cb = default(Block8x8F);
+ Block8x8F cr = default(Block8x8F);
+
+ Block8x8F temp1 = default(Block8x8F);
+ Block8x8F temp2 = default(Block8x8F);
+
+ Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
+ Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
+
+ UnzigData unzig = UnzigData.Create();
+
+ // ReSharper disable once InconsistentNaming
+ float prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
+
+ using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true))
+ {
+ for (int y = 0; y < pixels.Height; y += 8)
+ {
+ for (int x = 0; x < pixels.Width; x += 8)
+ {
+ ToYCbCr(pixels, x, y, &b, &cb, &cr, rgbBytes);
+
+ prevDCY = this.WriteBlock(
+ QuantIndex.Luminance,
+ prevDCY,
+ &b,
+ &temp1,
+ &temp2,
+ &onStackLuminanceQuantTable,
+ unzig.Data);
+ prevDCCb = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCb,
+ &cb,
+ &temp1,
+ &temp2,
+ &onStackChrominanceQuantTable,
+ unzig.Data);
+ prevDCCr = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCr,
+ &cr,
+ &temp1,
+ &temp2,
+ &onStackChrominanceQuantTable,
+ unzig.Data);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Writes the application header containing the JFIF identifier plus extra data.
+ ///
+ /// The resolution of the image in the x- direction.
+ /// The resolution of the image in the y- direction.
+ private void WriteApplicationHeader(short horizontalResolution, short verticalResolution)
+ {
+ // Write the start of image marker. Markers are always prefixed with with 0xff.
+ this.buffer[0] = JpegConstants.Markers.XFF;
+ this.buffer[1] = JpegConstants.Markers.SOI;
+
+ // Write the JFIF headers
+ this.buffer[2] = JpegConstants.Markers.XFF;
+ this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker
+ this.buffer[4] = 0x00;
+ this.buffer[5] = 0x10;
+ this.buffer[6] = 0x4a; // J
+ this.buffer[7] = 0x46; // F
+ this.buffer[8] = 0x49; // I
+ this.buffer[9] = 0x46; // F
+ this.buffer[10] = 0x00; // = "JFIF",'\0'
+ this.buffer[11] = 0x01; // versionhi
+ this.buffer[12] = 0x01; // versionlo
+ this.buffer[13] = 0x01; // xyunits as dpi
+
+ // No thumbnail
+ this.buffer[14] = 0x00; // Thumbnail width
+ this.buffer[15] = 0x00; // Thumbnail height
+
+ this.outputStream.Write(this.buffer, 0, 16);
+
+ // Resolution. Big Endian
+ this.buffer[0] = (byte)(horizontalResolution >> 8);
+ this.buffer[1] = (byte)horizontalResolution;
+ this.buffer[2] = (byte)(verticalResolution >> 8);
+ this.buffer[3] = (byte)verticalResolution;
+
+ this.outputStream.Write(this.buffer, 0, 4);
+ }
+
///
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
/// The block is in natural (not zig-zag) order.
///
- /// The block to write.
/// The quantization table index.
/// The previous DC value.
- /// The
- private int WriteBlock(ref Block block, QuantIndex index, int prevDC)
+ /// Source block
+ /// Temporal block to be used as FDCT Destination
+ /// Temporal block 2
+ /// Quantization table
+ /// The 8x8 Unzig block ptr
+ ///
+ /// The
+ ///
+ private float WriteBlock(
+ QuantIndex index,
+ float prevDC,
+ Block8x8F* src,
+ Block8x8F* tempDest,
+ Block8x8F* temp2,
+ Block8x8F* quant,
+ int* unzigPtr)
{
- FDCT.Transform(ref block);
+ DCT.TransformFDCT(ref *src, ref *tempDest, ref *temp2);
+
+ Block8x8F.UnZigDivRound(tempDest, temp2, quant, unzigPtr);
+
+ float* d = (float*)temp2;
// Emit the DC delta.
- int dc = Round(block[0], 8 * this.quant[(int)index][0]);
- this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC);
+ float dc = d[0];
+
+ this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, (int)(dc - prevDC));
// Emit the AC components.
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++)
{
- int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]);
+ float ac = d[zig];
if (ac == 0)
{
@@ -520,7 +561,7 @@ namespace ImageSharp.Formats
runLength -= 16;
}
- this.EmitHuffRLE(h, runLength, ac);
+ this.EmitHuffRLE(h, runLength, (int)ac);
runLength = 0;
}
}
@@ -534,116 +575,74 @@ namespace ImageSharp.Formats
}
///
- /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values.
+ /// Writes the Define Huffman Table marker and tables.
///
- /// The pixel format.
- /// The pixel accessor.
- /// The x-position within the image.
- /// The y-position within the image.
- /// The luminance block.
- /// The red chroma block.
- /// The blue chroma block.
- private void ToYCbCr(PixelAccessor pixels, int x, int y, ref Block yBlock, ref Block cbBlock, ref Block crBlock)
- where TColor : struct, IPackedPixel, IEquatable
+ /// The number of components to write.
+ private void WriteDefineHuffmanTables(int componentCount)
{
- int xmax = pixels.Width - 1;
- int ymax = pixels.Height - 1;
- byte[] color = new byte[3];
- for (int j = 0; j < 8; j++)
- {
- for (int i = 0; i < 8; i++)
- {
- pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToBytes(color, 0, ComponentOrder.XYZ);
+ // Table identifiers.
+ byte[] headers = { 0x00, 0x10, 0x01, 0x11 };
+ int markerlen = 2;
+ HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs;
- byte r = color[0];
- byte g = color[1];
- byte b = color[2];
+ if (componentCount == 1)
+ {
+ // Drop the Chrominance tables.
+ specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] };
+ }
- // Convert returned bytes into the YCbCr color space. Assume RGBA
- byte yy = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b));
- byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)));
- byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)));
-
- int index = (8 * j) + i;
- yBlock[index] = yy;
- cbBlock[index] = cb;
- crBlock[index] = cr;
- }
+ foreach (HuffmanSpec s in specs)
+ {
+ markerlen += 1 + 16 + s.Values.Length;
}
- }
- ///
- /// Scales the 16x16 region represented by the 4 source blocks to the 8x8
- /// DST block.
- ///
- /// The destination block array
- /// The source block array.
- private void Scale16X16To8X8(ref Block destination, Block[] source)
- {
- for (int i = 0; i < 4; i++)
+ this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
+ for (int i = 0; i < specs.Length; i++)
{
- int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
- for (int y = 0; y < 4; y++)
+ HuffmanSpec spec = specs[i];
+ int len = 0;
+
+ fixed (byte* huffman = this.huffmanBuffer)
+ fixed (byte* count = spec.Count)
+ fixed (byte* values = spec.Values)
{
- for (int x = 0; x < 4; x++)
+ huffman[len++] = headers[i];
+
+ for (int c = 0; c < spec.Count.Length; c++)
{
- int j = (16 * y) + (2 * x);
- int sum = source[i][j] + source[i][j + 1] + source[i][j + 8] + source[i][j + 9];
- destination[(8 * y) + x + dstOff] = (sum + 2) / 4;
+ huffman[len++] = count[c];
+ }
+
+ for (int v = 0; v < spec.Values.Length; v++)
+ {
+ huffman[len++] = values[v];
}
}
+
+ this.outputStream.Write(this.huffmanBuffer, 0, len);
}
}
///
- /// Writes the application header containing the JFIF identifier plus extra data.
+ /// Writes the Define Quantization Marker and tables.
///
- /// The resolution of the image in the x- direction.
- /// The resolution of the image in the y- direction.
- private void WriteApplicationHeader(short horizontalResolution, short verticalResolution)
+ private void WriteDefineQuantizationTables()
{
- // Write the start of image marker. Markers are always prefixed with with 0xff.
- this.buffer[0] = JpegConstants.Markers.XFF;
- this.buffer[1] = JpegConstants.Markers.SOI;
-
- // Write the JFIF headers
- this.buffer[2] = JpegConstants.Markers.XFF;
- this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker
- this.buffer[4] = 0x00;
- this.buffer[5] = 0x10;
- this.buffer[6] = 0x4a; // J
- this.buffer[7] = 0x46; // F
- this.buffer[8] = 0x49; // I
- this.buffer[9] = 0x46; // F
- this.buffer[10] = 0x00; // = "JFIF",'\0'
- this.buffer[11] = 0x01; // versionhi
- this.buffer[12] = 0x01; // versionlo
- this.buffer[13] = 0x01; // xyunits as dpi
-
- // No thumbnail
- this.buffer[14] = 0x00; // Thumbnail width
- this.buffer[15] = 0x00; // Thumbnail height
+ // Marker + quantization table lengths
+ int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.ScalarCount));
+ this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
- this.outputStream.Write(this.buffer, 0, 16);
+ // Loop through and collect the tables as one array.
+ // This allows us to reduce the number of writes to the stream.
+ int dqtCount = (QuantizationTableCount * Block8x8F.ScalarCount) + QuantizationTableCount;
+ byte[] dqt = ArrayPool.Shared.Rent(dqtCount);
+ int offset = 0;
- // Resolution. Big Endian
- this.buffer[0] = (byte)(horizontalResolution >> 8);
- this.buffer[1] = (byte)horizontalResolution;
- this.buffer[2] = (byte)(verticalResolution >> 8);
- this.buffer[3] = (byte)verticalResolution;
+ WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable);
+ WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable);
- this.outputStream.Write(this.buffer, 0, 4);
- }
-
- ///
- /// Writes the metadata profiles to the image.
- ///
- /// The image.
- /// The pixel format.
- private void WriteProfiles(Image image)
- where TColor : struct, IPackedPixel, IEquatable
- {
- this.WriteProfile(image.ExifProfile);
+ this.outputStream.Write(dqt, 0, dqtCount);
+ ArrayPool.Shared.Return(dqt);
}
///
@@ -679,29 +678,14 @@ namespace ImageSharp.Formats
}
///
- /// Writes the Define Quantization Marker and tables.
+ /// Writes the metadata profiles to the image.
///
- private void WriteDefineQuantizationTables()
+ /// The image.
+ /// The pixel format.
+ private void WriteProfiles(Image image)
+ where TColor : struct, IPackedPixel, IEquatable
{
- // Marker + quantization table lengths
- int markerlen = 2 + (QuantizationTableCount * (1 + Block.BlockSize));
- 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];
- int offset = 0;
- for (int i = 0; i < QuantizationTableCount; i++)
- {
- dqt[offset++] = (byte)i;
- int len = this.quant[i].Length;
- for (int j = 0; j < len; j++)
- {
- dqt[offset++] = this.quant[i][j];
- }
- }
-
- this.outputStream.Write(dqt, 0, dqt.Length);
+ this.WriteProfile(image.ExifProfile);
}
///
@@ -734,7 +718,9 @@ namespace ImageSharp.Formats
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[3] = (byte)(width >> 8);
this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
- this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK)
+ this.buffer[5] = (byte)componentCount;
+
+ // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK)
if (componentCount == 1)
{
this.buffer[6] = 1;
@@ -758,67 +744,17 @@ namespace ImageSharp.Formats
this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9);
}
- ///
- /// Writes the Define Huffman Table marker and tables.
- ///
- /// The number of components to write.
- private void WriteDefineHuffmanTables(int componentCount)
- {
- // Table identifiers.
- byte[] headers = { 0x00, 0x10, 0x01, 0x11 };
- int markerlen = 2;
- HuffmanSpec[] specs = TheHuffmanSpecs;
-
- if (componentCount == 1)
- {
- // Drop the Chrominance tables.
- specs = new[] { TheHuffmanSpecs[0], TheHuffmanSpecs[1] };
- }
-
- foreach (HuffmanSpec s in specs)
- {
- markerlen += 1 + 16 + s.Values.Length;
- }
-
- this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
- for (int i = 0; i < specs.Length; i++)
- {
- HuffmanSpec spec = specs[i];
- int len = 0;
-
- fixed (byte* huffman = this.huffmanBuffer)
- fixed (byte* count = spec.Count)
- fixed (byte* values = spec.Values)
- {
- huffman[len++] = headers[i];
-
- for (int c = 0; c < spec.Count.Length; c++)
- {
- huffman[len++] = count[c];
- }
-
- for (int v = 0; v < spec.Values.Length; v++)
- {
- huffman[len++] = values[v];
- }
- }
-
- this.outputStream.Write(this.huffmanBuffer, 0, len);
- }
- }
-
///
/// Writes the StartOfScan marker.
///
/// The pixel format.
- ///
- /// The pixel accessor providing access to the image pixels.
- ///
+ /// The pixel accessor providing access to the image pixels.
private void WriteStartOfScan(PixelAccessor pixels)
where TColor : struct, IPackedPixel, IEquatable
{
+ // TODO: This method should be the entry point for a JpegEncoderScanProcessor struct
// TODO: We should allow grayscale writing.
- this.outputStream.Write(this.sosHeaderYCbCr, 0, this.sosHeaderYCbCr.Length);
+ this.outputStream.Write(SosHeaderYCbCr, 0, SosHeaderYCbCr.Length);
switch (this.subsample)
{
@@ -835,75 +771,78 @@ namespace ImageSharp.Formats
}
///
- /// Encodes the image with no subsampling.
+ /// Encodes the image with subsampling. The Cb and Cr components are each subsampled
+ /// at a factor of 2 both horizontally and vertically.
///
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
- private void Encode444(PixelAccessor pixels)
+ private void Encode420(PixelAccessor pixels)
where TColor : struct, IPackedPixel, IEquatable
{
- Block b = Block.Create();
- Block cb = Block.Create();
- Block cr = Block.Create();
+ // TODO: Need a JpegEncoderScanProcessor struct to encapsulate all this mess:
+ Block8x8F b = default(Block8x8F);
- // ReSharper disable once InconsistentNaming
- int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
+ BlockQuad cb = default(BlockQuad);
+ BlockQuad cr = default(BlockQuad);
+ Block8x8F* cbPtr = (Block8x8F*)cb.Data;
+ Block8x8F* crPtr = (Block8x8F*)cr.Data;
- for (int y = 0; y < pixels.Height; y += 8)
- {
- for (int x = 0; x < pixels.Width; x += 8)
- {
- 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);
- }
- }
+ Block8x8F temp1 = default(Block8x8F);
+ Block8x8F temp2 = default(Block8x8F);
- b.Dispose();
- cb.Dispose();
- cr.Dispose();
- }
+ Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
+ Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
- ///
- /// Encodes the image with subsampling. The Cb and Cr components are each subsampled
- /// at a factor of 2 both horizontally and vertically.
- ///
- /// The pixel format.
- /// The pixel accessor providing access to the image pixels.
- private void Encode420(PixelAccessor pixels)
- where TColor : struct, IPackedPixel, IEquatable
- {
- Block b = Block.Create();
- Block[] cb = Block.CreateArray(4);
- Block[] cr = Block.CreateArray(4);
+ UnzigData unzig = UnzigData.Create();
// ReSharper disable once InconsistentNaming
- int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
+ float prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
- for (int y = 0; y < pixels.Height; y += 16)
+ using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.XYZ, true))
{
- for (int x = 0; x < pixels.Width; x += 16)
+ for (int y = 0; y < pixels.Height; y += 16)
{
- for (int i = 0; i < 4; i++)
+ for (int x = 0; x < pixels.Width; x += 16)
{
- int xOff = (i & 1) * 8;
- int yOff = (i & 2) * 4;
-
- this.ToYCbCr(pixels, x + xOff, y + yOff, ref b, ref cb[i], ref cr[i]);
- prevDCY = this.WriteBlock(ref b, QuantIndex.Luminance, prevDCY);
+ for (int i = 0; i < 4; i++)
+ {
+ int xOff = (i & 1) * 8;
+ int yOff = (i & 2) * 4;
+
+ ToYCbCr(pixels, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i, rgbBytes);
+
+ prevDCY = this.WriteBlock(
+ QuantIndex.Luminance,
+ prevDCY,
+ &b,
+ &temp1,
+ &temp2,
+ &onStackLuminanceQuantTable,
+ unzig.Data);
+ }
+
+ Block8x8F.Scale16X16To8X8(&b, cbPtr);
+ prevDCCb = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCb,
+ &b,
+ &temp1,
+ &temp2,
+ &onStackChrominanceQuantTable,
+ unzig.Data);
+
+ Block8x8F.Scale16X16To8X8(&b, crPtr);
+ prevDCCr = this.WriteBlock(
+ QuantIndex.Chrominance,
+ prevDCCr,
+ &b,
+ &temp1,
+ &temp2,
+ &onStackChrominanceQuantTable,
+ unzig.Data);
}
-
- 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);
}
///
@@ -920,81 +859,5 @@ namespace ImageSharp.Formats
this.buffer[3] = (byte)(length & 0xff);
this.outputStream.Write(this.buffer, 0, 4);
}
-
- ///
- /// The Huffman encoding specifications.
- ///
- private struct HuffmanSpec
- {
- ///
- /// Gets count[i] - The number of codes of length i bits.
- ///
- public readonly byte[] Count;
-
- ///
- /// Gets value[i] - The decoded value of the codeword at the given index.
- ///
- public readonly byte[] Values;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The number of codes.
- /// The decoded values.
- public HuffmanSpec(byte[] count, byte[] values)
- {
- this.Count = count;
- this.Values = values;
- }
- }
-
- ///
- /// A compiled look-up table representation of a huffmanSpec.
- /// Each value maps to a uint32 of which the 8 most significant bits hold the
- /// codeword size in bits and the 24 least significant bits hold the codeword.
- /// The maximum codeword size is 16 bits.
- ///
- private class HuffmanLut
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The encoding specifications.
- public HuffmanLut(HuffmanSpec spec)
- {
- int maxValue = 0;
-
- foreach (byte v in spec.Values)
- {
- if (v > maxValue)
- {
- maxValue = v;
- }
- }
-
- this.Values = new uint[maxValue + 1];
-
- int code = 0;
- int k = 0;
-
- for (int i = 0; i < spec.Count.Length; i++)
- {
- int bits = (i + 1) << 24;
- for (int j = 0; j < spec.Count[i]; j++)
- {
- this.Values[spec.Values[k]] = (uint)(bits | code);
- code++;
- k++;
- }
-
- code <<= 1;
- }
- }
-
- ///
- /// Gets the collection of huffman values.
- ///
- public uint[] Values { get; }
- }
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/UnzigData.cs b/src/ImageSharp/Formats/Jpg/UnzigData.cs
new file mode 100644
index 000000000..e74dd5c73
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/UnzigData.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats
+{
+ using System;
+ using System.Runtime.InteropServices;
+
+ ///
+ /// Holds the Jpeg UnZig array in a value/stack type.
+ /// Unzig maps from the zigzag ordering to the natural ordering. For example,
+ /// unzig[3] is the column and row of the fourth element in zigzag order. The
+ /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
+ ///
+ internal unsafe struct UnzigData
+ {
+ ///
+ /// Copy of in a value type
+ ///
+ public fixed int Data[64];
+
+ ///
+ /// Unzig maps from the zigzag ordering to the natural ordering. For example,
+ /// unzig[3] is the column and row of the fourth element in zigzag order. The
+ /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
+ ///
+ private static readonly int[] Unzig =
+ {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33,
+ 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50,
+ 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+ };
+
+ ///
+ /// Creates and fills an instance of with Jpeg unzig indices
+ ///
+ /// The new instance
+ public static UnzigData Create()
+ {
+ UnzigData result = default(UnzigData);
+ int* unzigPtr = result.Data;
+ Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs
new file mode 100644
index 000000000..7608c70c6
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Utils/JpegUtils.cs
@@ -0,0 +1,123 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Formats.Jpg
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Jpeg specific utilities and extension methods
+ ///
+ internal static unsafe class JpegUtils
+ {
+ ///
+ /// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of the image.
+ ///
+ /// The pixel type
+ /// The input pixel acessor
+ /// The destination
+ /// Starting Y coord
+ /// Starting X coord
+ public static void CopyRGBBytesStretchedTo(
+ this PixelAccessor pixels,
+ PixelArea dest,
+ int sourceY,
+ int sourceX)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ pixels.UncheckedCopyTo(dest, sourceY, sourceX);
+ int stretchFromX = pixels.Width - sourceX;
+ int stretchFromY = pixels.Height - sourceY;
+ StretchPixels(dest, stretchFromX, stretchFromY);
+ }
+
+ ///
+ /// Copy a region of image into the image destination area. Does not throw when requesting a 0-size copy.
+ ///
+ /// The pixel type
+ /// The source
+ /// The destination area.
+ /// The source row index.
+ /// The source column index.
+ ///
+ /// Thrown when an unsupported component order value is passed.
+ ///
+ public static void UncheckedCopyTo(
+ this PixelAccessor sourcePixels,
+ PixelArea destinationArea,
+ int sourceY,
+ int sourceX)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ // TODO: Code smell! This is exactly the same code PixelArea.CopyTo() starts with!
+ int width = Math.Min(destinationArea.Width, sourcePixels.Width - sourceX);
+ int height = Math.Min(destinationArea.Height, sourcePixels.Height - sourceY);
+ if (width < 1 || height < 1)
+ {
+ return;
+ }
+
+ sourcePixels.CopyTo(destinationArea, sourceY, sourceX);
+ }
+
+ ///
+ /// Copy an RGB value
+ ///
+ /// Source pointer
+ /// Destination pointer
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyRgb(byte* source, byte* dest)
+ {
+ *dest++ = *source++; // R
+ *dest++ = *source++; // G
+ *dest = *source; // B
+ }
+
+ // Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsInvalidStretchStartingPosition(PixelArea area, int fromX, int fromY)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height;
+ }
+
+ private static void StretchPixels(PixelArea area, int fromX, int fromY)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ if (IsInvalidStretchStartingPosition(area, fromX, fromY))
+ {
+ return;
+ }
+
+ for (int y = 0; y < fromY; y++)
+ {
+ byte* ptrBase = (byte*)area.DataPointer + (y * area.RowByteCount);
+
+ for (int x = fromX; x < area.Width; x++)
+ {
+ byte* prevPtr = ptrBase + ((x - 1) * 3);
+ byte* currPtr = ptrBase + (x * 3);
+
+ CopyRgb(prevPtr, currPtr);
+ }
+ }
+
+ for (int y = fromY; y < area.Height; y++)
+ {
+ byte* currBase = (byte*)area.DataPointer + (y * area.RowByteCount);
+ byte* prevBase = (byte*)area.DataPointer + ((y - 1) * area.RowByteCount);
+
+ for (int x = 0; x < area.Width; x++)
+ {
+ int x3 = 3 * x;
+ byte* currPtr = currBase + x3;
+ byte* prevPtr = prevBase + x3;
+
+ CopyRgb(prevPtr, currPtr);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs b/src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs
similarity index 98%
rename from src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs
rename to src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs
index 77ee17fc2..99d1c3e04 100644
--- a/src/ImageSharp/Formats/Jpg/Components/MutableSpan.cs
+++ b/src/ImageSharp/Formats/Jpg/Utils/MutableSpan.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Formats
+namespace ImageSharp.Formats.Jpg
{
using System.Runtime.CompilerServices;
diff --git a/src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs b/src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs
new file mode 100644
index 000000000..45ecfc092
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpg/Utils/MutableSpanExtensions.cs
@@ -0,0 +1,164 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats.Jpg
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// MutableSpan Extensions
+ ///
+ internal static class MutableSpanExtensions
+ {
+ ///
+ /// Slice
+ ///
+ /// The type of the data in the span
+ /// The data array
+ /// The offset
+ /// The new
+ public static MutableSpan Slice(this T[] array, int offset) => new MutableSpan(array, offset);
+
+ ///
+ /// Save to a Vector4
+ ///
+ /// The data
+ /// The vector to save to
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SaveTo(this MutableSpan data, ref Vector4 v)
+ {
+ v.X = data[0];
+ v.Y = data[1];
+ v.Z = data[2];
+ v.W = data[3];
+ }
+
+ ///
+ /// Save to a Vector4
+ ///
+ /// The data
+ /// The vector to save to
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SaveTo(this MutableSpan data, ref Vector4 v)
+ {
+ v.X = data[0];
+ v.Y = data[1];
+ v.Z = data[2];
+ v.W = data[3];
+ }
+
+ ///
+ /// Load from Vector4
+ ///
+ /// The data
+ /// The vector to load from
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void LoadFrom(this MutableSpan data, ref Vector4 v)
+ {
+ data[0] = v.X;
+ data[1] = v.Y;
+ data[2] = v.Z;
+ data[3] = v.W;
+ }
+
+ ///
+ /// Load from Vector4
+ ///
+ /// The data
+ /// The vector to load from
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void LoadFrom(this MutableSpan data, ref Vector4 v)
+ {
+ data[0] = (int)v.X;
+ data[1] = (int)v.Y;
+ data[2] = (int)v.Z;
+ data[3] = (int)v.W;
+ }
+
+ ///
+ /// Converts all int values of src to float
+ ///
+ /// Source
+ /// A new with float values
+ public static MutableSpan ConvertToFloat32MutableSpan(this MutableSpan src)
+ {
+ MutableSpan result = new MutableSpan(src.TotalCount);
+ for (int i = 0; i < src.TotalCount; i++)
+ {
+ result[i] = (float)src[i];
+ }
+
+ return result;
+ }
+
+ ///
+ /// Converts all float values of src to int
+ ///
+ /// Source
+ /// A new with float values
+ public static MutableSpan ConvertToInt32MutableSpan(this MutableSpan src)
+ {
+ MutableSpan result = new MutableSpan(src.TotalCount);
+ for (int i = 0; i < src.TotalCount; i++)
+ {
+ result[i] = (int)src[i];
+ }
+
+ return result;
+ }
+
+ ///
+ /// Add a scalar to all values of src
+ ///
+ /// The source
+ /// The scalar value to add
+ /// A new instance of
+ public static MutableSpan AddScalarToAllValues(this MutableSpan src, float scalar)
+ {
+ MutableSpan result = new MutableSpan(src.TotalCount);
+ for (int i = 0; i < src.TotalCount; i++)
+ {
+ result[i] = src[i] + scalar;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Add a scalar to all values of src
+ ///
+ /// The source
+ /// The scalar value to add
+ /// A new instance of
+ public static MutableSpan AddScalarToAllValues(this MutableSpan src, int scalar)
+ {
+ MutableSpan result = new MutableSpan(src.TotalCount);
+ for (int i = 0; i < src.TotalCount; i++)
+ {
+ result[i] = src[i] + scalar;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Copy all values in src to a new instance
+ ///
+ /// Element type
+ /// The source
+ /// A new instance of
+ public static MutableSpan Copy(this MutableSpan src)
+ {
+ MutableSpan result = new MutableSpan(src.TotalCount);
+ for (int i = 0; i < src.TotalCount; i++)
+ {
+ result[i] = src[i];
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs
index 3642d3942..178ea5274 100644
--- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs
+++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs
@@ -187,7 +187,6 @@ namespace ImageSharp
int height = Math.Min(area.Height, this.Height - targetY);
this.CheckDimensions(width, height);
-
switch (area.ComponentOrder)
{
case ComponentOrder.ZYX:
@@ -222,7 +221,6 @@ namespace ImageSharp
int height = Math.Min(area.Height, this.Height - sourceY);
this.CheckDimensions(width, height);
-
switch (area.ComponentOrder)
{
case ComponentOrder.ZYX:
diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs
index 673fe5500..b1ae5f870 100644
--- a/src/ImageSharp/Image/PixelArea{TColor}.cs
+++ b/src/ImageSharp/Image/PixelArea{TColor}.cs
@@ -2,10 +2,10 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-
namespace ImageSharp
{
using System;
+ using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
@@ -18,6 +18,11 @@ namespace ImageSharp
public sealed unsafe class PixelArea : IDisposable
where TColor : struct, IPackedPixel, IEquatable
{
+ ///
+ /// True if was rented from by the constructor
+ ///
+ private readonly bool isBufferRented;
+
///
/// Provides a way to access the pixels from unmanaged memory.
///
@@ -85,8 +90,9 @@ namespace ImageSharp
/// The width.
/// The height.
/// The component order.
- public PixelArea(int width, int height, ComponentOrder componentOrder)
- : this(width, height, componentOrder, 0)
+ /// True if the buffer should be rented from ArrayPool
+ public PixelArea(int width, int height, ComponentOrder componentOrder, bool usePool = false)
+ : this(width, height, componentOrder, 0, usePool)
{
}
@@ -95,8 +101,9 @@ namespace ImageSharp
///
/// The width.
/// The component order.
- public PixelArea(int width, ComponentOrder componentOrder)
- : this(width, 1, componentOrder, 0)
+ /// True if the buffer should be rented from ArrayPool
+ public PixelArea(int width, ComponentOrder componentOrder, bool usePool = false)
+ : this(width, 1, componentOrder, 0, usePool)
{
}
@@ -106,8 +113,9 @@ namespace ImageSharp
/// The width.
/// The component order.
/// The number of bytes to pad each row.
- public PixelArea(int width, ComponentOrder componentOrder, int padding)
- : this(width, 1, componentOrder, padding)
+ /// True if the buffer should be rented from ArrayPool
+ public PixelArea(int width, ComponentOrder componentOrder, int padding, bool usePool = false)
+ : this(width, 1, componentOrder, padding, usePool)
{
}
@@ -118,13 +126,27 @@ namespace ImageSharp
/// The height.
/// The component order.
/// The number of bytes to pad each row.
- public PixelArea(int width, int height, ComponentOrder componentOrder, int padding)
+ /// True if the buffer should be rented from ArrayPool
+ public PixelArea(int width, int height, ComponentOrder componentOrder, int padding, bool usePool = false)
{
this.Width = width;
this.Height = height;
this.ComponentOrder = componentOrder;
this.RowByteCount = (width * GetComponentCount(componentOrder)) + padding;
- this.Bytes = new byte[this.RowByteCount * height];
+
+ int bufferSize = this.RowByteCount * height;
+
+ if (usePool)
+ {
+ this.Bytes = BytesPool.Rent(bufferSize);
+ this.isBufferRented = true;
+ Array.Clear(this.Bytes, 0, bufferSize);
+ }
+ else
+ {
+ this.Bytes = new byte[bufferSize];
+ }
+
this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned);
// TODO: Why is Resharper warning us about an impure method call?
@@ -137,7 +159,7 @@ namespace ImageSharp
///
~PixelArea()
{
- this.Dispose();
+ this.Dispose(false);
}
///
@@ -145,20 +167,30 @@ namespace ImageSharp
///
public byte[] Bytes { get; }
+ ///
+ /// Gets the component order.
+ ///
+ public ComponentOrder ComponentOrder { get; }
+
///
/// Gets the pointer to the pixel buffer.
///
public IntPtr DataPointer => this.dataPointer;
+ ///
+ /// Gets the height.
+ ///
+ public int Height { get; }
+
///
/// Gets the data pointer.
///
public byte* PixelBase { get; private set; }
///
- /// Gets the component order.
+ /// Gets number of bytes in a row.
///
- public ComponentOrder ComponentOrder { get; }
+ public int RowByteCount { get; }
///
/// Gets the width.
@@ -166,14 +198,18 @@ namespace ImageSharp
public int Width { get; }
///
- /// Gets the height.
+ /// Gets the pool used to rent , when it's not coming from an external source
///
- public int Height { get; }
+ // ReSharper disable once StaticMemberInGenericType
+ private static ArrayPool BytesPool => ArrayPool.Shared;
///
- /// Gets number of bytes in a row.
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
- public int RowByteCount { get; }
+ public void Dispose()
+ {
+ this.Dispose(true);
+ }
///
/// Reads the stream to the area.
@@ -193,45 +229,12 @@ namespace ImageSharp
stream.Write(this.Bytes, 0, this.Bytes.Length);
}
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- public void Dispose()
- {
- if (this.isDisposed)
- {
- return;
- }
-
- if (this.PixelBase == null)
- {
- return;
- }
-
- if (this.pixelsHandle.IsAllocated)
- {
- this.pixelsHandle.Free();
- }
-
- this.dataPointer = IntPtr.Zero;
- this.PixelBase = null;
-
- this.isDisposed = true;
-
- // This object will be cleaned up by the Dispose method.
- // Therefore, you should call GC.SuppressFinalize to
- // take this object off the finalization queue
- // and prevent finalization code for this object
- // from executing a second time.
- GC.SuppressFinalize(this);
- }
-
///
/// Resets the bytes of the array to it's initial value.
///
internal void Reset()
{
- Unsafe.InitBlock(this.PixelBase, 0, (uint)this.Bytes.Length);
+ Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowByteCount * this.Height));
}
///
@@ -265,8 +268,45 @@ namespace ImageSharp
int requiredLength = (width * GetComponentCount(componentOrder)) * height;
if (bytes.Length != requiredLength)
{
- throw new ArgumentOutOfRangeException(nameof(bytes), $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}.");
+ throw new ArgumentOutOfRangeException(
+ nameof(bytes),
+ $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}.");
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
}
+
+ if (this.PixelBase == null)
+ {
+ return;
+ }
+
+ if (this.pixelsHandle.IsAllocated)
+ {
+ this.pixelsHandle.Free();
+ }
+
+ if (disposing && this.isBufferRented)
+ {
+ BytesPool.Return(this.Bytes);
+ }
+
+ this.dataPointer = IntPtr.Zero;
+ this.PixelBase = null;
+
+ this.isDisposed = true;
+
+ // This object will be cleaned up by the Dispose method.
+ // Therefore, you should call GC.SuppressFinalize to
+ // take this object off the finalization queue
+ // and prevent finalization code for this object
+ // from executing a second time.
+ GC.SuppressFinalize(this);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ImageSharp.xproj b/src/ImageSharp/ImageSharp.xproj
index dcd5b9ed1..bf2f6a67b 100644
--- a/src/ImageSharp/ImageSharp.xproj
+++ b/src/ImageSharp/ImageSharp.xproj
@@ -18,5 +18,8 @@
True
+
+
+
\ No newline at end of file
diff --git a/src/ImageSharp/Numerics/RectangleF.cs b/src/ImageSharp/Numerics/RectangleF.cs
index c55608f4b..2ed57c841 100644
--- a/src/ImageSharp/Numerics/RectangleF.cs
+++ b/src/ImageSharp/Numerics/RectangleF.cs
@@ -249,7 +249,7 @@ namespace ImageSharp
///
public static RectangleF Outset(RectangleF region, float width)
{
- var dblWidth = width * 2;
+ float dblWidth = width * 2;
return new RectangleF(region.X - width, region.Y - width, region.Width + dblWidth, region.Height + dblWidth);
}
diff --git a/src/ImageSharp/PixelAccessor.cs b/src/ImageSharp/PixelAccessor.cs
index 378ec55dc..c91f10cbd 100644
--- a/src/ImageSharp/PixelAccessor.cs
+++ b/src/ImageSharp/PixelAccessor.cs
@@ -109,6 +109,26 @@ namespace ImageSharp
}
}
+ ///
+ protected override unsafe void CopyToXYZ(PixelArea area, int sourceY, int sourceX, int width, int height)
+ {
+ for (int y = 0; y < height; y++)
+ {
+ byte* source = this.GetRowPointer(sourceX, sourceY + y);
+ byte* destination = area.PixelBase + (y * area.RowByteCount);
+
+ for (int x = 0; x < width; x++)
+ {
+ *destination = *(source + 0);
+ *(destination + 1) = *(source + 1);
+ *(destination + 2) = *(source + 2);
+
+ source += 4;
+ destination += 3;
+ }
+ }
+ }
+
///
protected override void CopyToZYXW(PixelArea area, int sourceY, int sourceX, int width, int height)
{
diff --git a/src/ImageSharp/Properties/AssemblyInfo.cs b/src/ImageSharp/Properties/AssemblyInfo.cs
index ec405e1d0..8f3982ee4 100644
--- a/src/ImageSharp/Properties/AssemblyInfo.cs
+++ b/src/ImageSharp/Properties/AssemblyInfo.cs
@@ -37,3 +37,4 @@ using System.Runtime.CompilerServices;
// Ensure the internals can be tested.
[assembly: InternalsVisibleTo("ImageSharp.Benchmarks")]
[assembly: InternalsVisibleTo("ImageSharp.Tests")]
+[assembly: InternalsVisibleTo("ImageSharp.Tests46")]
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/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
index 524b84082..57b3c56f8 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
@@ -1,20 +1,19 @@
// Uncomment this to turn unit tests into benchmarks:
//#define BENCHMARKING
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Text;
-using ImageSharp.Formats;
-using Xunit;
-using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
-namespace ImageSharp.Tests.Formats.Jpg
+namespace ImageSharp.Tests
{
+ using System.Diagnostics;
+ using System.Numerics;
+
+ using ImageSharp.Formats;
+ using ImageSharp.Formats.Jpg;
+
+ using Xunit;
+ using Xunit.Abstractions;
+
public class Block8x8FTests : UtilityTestClassBase
{
#if BENCHMARKING
@@ -23,7 +22,8 @@ namespace ImageSharp.Tests.Formats.Jpg
public const int Times = 1;
#endif
- public Block8x8FTests(ITestOutputHelper output) : base(output)
+ public Block8x8FTests(ITestOutputHelper output)
+ : base(output)
{
}
@@ -31,20 +31,23 @@ namespace ImageSharp.Tests.Formats.Jpg
public void Indexer()
{
float sum = 0;
- this.Measure(Times, () =>
- {
- Block8x8F block = new Block8x8F();
-
- for (int i = 0; i < Block8x8F.ScalarCount; i++)
- {
- block[i] = i;
- }
- sum = 0;
- for (int i = 0; i < Block8x8F.ScalarCount; i++)
- {
- sum += block[i];
- }
- });
+ this.Measure(
+ Times,
+ () =>
+ {
+ Block8x8F block = new Block8x8F();
+
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ block[i] = i;
+ }
+
+ sum = 0;
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ sum += block[i];
+ }
+ });
Assert.Equal(sum, 64f * 63f * 0.5f);
}
@@ -52,21 +55,24 @@ namespace ImageSharp.Tests.Formats.Jpg
public unsafe void Indexer_GetScalarAt_SetScalarAt()
{
float sum = 0;
- Measure(Times, () =>
- {
- Block8x8F block = new Block8x8F();
-
- for (int i = 0; i < Block8x8F.ScalarCount; i++)
- {
- Block8x8F.SetScalarAt(&block, i, i);
- }
- sum = 0;
- for (int i = 0; i < Block8x8F.ScalarCount; i++)
- {
- sum += Block8x8F.GetScalarAt(&block, i);
- }
- });
- Assert.Equal(sum, 64f*63f*0.5f);
+ this.Measure(
+ Times,
+ () =>
+ {
+ Block8x8F block = new Block8x8F();
+
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ Block8x8F.SetScalarAt(&block, i, i);
+ }
+
+ sum = 0;
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ sum += Block8x8F.GetScalarAt(&block, i);
+ }
+ });
+ Assert.Equal(sum, 64f * 63f * 0.5f);
}
[Fact]
@@ -74,22 +80,24 @@ namespace ImageSharp.Tests.Formats.Jpg
{
float sum = 0;
-
- Measure(Times, () =>
- {
- //Block8x8F block = new Block8x8F();
- float[] block = new float[64];
- for (int i = 0; i < Block8x8F.ScalarCount; i++)
- {
- block[i] = i;
- }
- sum = 0;
- for (int i = 0; i < Block8x8F.ScalarCount; i++)
- {
- sum += block[i];
- }
- });
- Assert.Equal(sum, 64f*63f*0.5f);
+ this.Measure(
+ Times,
+ () =>
+ {
+ // Block8x8F block = new Block8x8F();
+ float[] block = new float[64];
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ block[i] = i;
+ }
+
+ sum = 0;
+ for (int i = 0; i < Block8x8F.ScalarCount; i++)
+ {
+ sum += block[i];
+ }
+ });
+ Assert.Equal(sum, 64f * 63f * 0.5f);
}
[Fact]
@@ -102,15 +110,19 @@ namespace ImageSharp.Tests.Formats.Jpg
{
data[i] = i;
}
- Measure(Times, () =>
- {
- Block8x8F b = new Block8x8F();
- b.LoadFrom(data);
- b.CopyTo(mirror);
- });
+
+ this.Measure(
+ Times,
+ () =>
+ {
+ Block8x8F b = new Block8x8F();
+ b.LoadFrom(data);
+ b.CopyTo(mirror);
+ });
Assert.Equal(data, mirror);
- //PrintLinearData((MutableSpan)mirror);
+
+ // PrintLinearData((MutableSpan)mirror);
}
[Fact]
@@ -123,15 +135,19 @@ namespace ImageSharp.Tests.Formats.Jpg
{
data[i] = i;
}
- Measure(Times, () =>
- {
- Block8x8F b = new Block8x8F();
- Block8x8F.LoadFrom(&b, data);
- Block8x8F.CopyTo(&b, mirror);
- });
+
+ this.Measure(
+ Times,
+ () =>
+ {
+ Block8x8F b = new Block8x8F();
+ Block8x8F.LoadFrom(&b, data);
+ Block8x8F.CopyTo(&b, mirror);
+ });
Assert.Equal(data, mirror);
- //PrintLinearData((MutableSpan)mirror);
+
+ // PrintLinearData((MutableSpan)mirror);
}
[Fact]
@@ -144,19 +160,21 @@ namespace ImageSharp.Tests.Formats.Jpg
{
data[i] = i;
}
- Measure(Times, () =>
- {
- Block8x8F v = new Block8x8F();
- v.LoadFrom(data);
- v.CopyTo(mirror);
- });
-
+ this.Measure(
+ Times,
+ () =>
+ {
+ Block8x8F v = new Block8x8F();
+ v.LoadFrom(data);
+ v.CopyTo(mirror);
+ });
+
Assert.Equal(data, mirror);
- //PrintLinearData((MutableSpan)mirror);
+
+ // PrintLinearData((MutableSpan)mirror);
}
-
-
+
[Fact]
public void TransposeInto()
{
@@ -174,8 +192,6 @@ namespace ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual);
}
-
-
private class BufferHolder
{
@@ -189,7 +205,7 @@ namespace ImageSharp.Tests.Formats.Jpg
source.Buffer.LoadFrom(Create8x8FloatData());
BufferHolder dest = new BufferHolder();
- Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ...");
+ this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark X {Times} ...");
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < Times; i++)
@@ -198,10 +214,9 @@ namespace ImageSharp.Tests.Formats.Jpg
}
sw.Stop();
- Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
-
+ this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
}
-
+
[Fact]
public void iDCT2D8x4_LeftPart()
{
@@ -215,14 +230,14 @@ namespace ImageSharp.Tests.Formats.Jpg
Block8x8F dest = new Block8x8F();
- source.IDCT8x4_LeftPart(ref dest);
+ DCT.IDCT8x4_LeftPart(ref source, ref dest);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
- Print8x8Data(expectedDestArray);
- Output.WriteLine("**************");
- Print8x8Data(actualDestArray);
+ this.Print8x8Data(expectedDestArray);
+ this.Output.WriteLine("**************");
+ this.Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray, actualDestArray);
}
@@ -240,65 +255,49 @@ namespace ImageSharp.Tests.Formats.Jpg
Block8x8F dest = new Block8x8F();
- source.IDCT8x4_RightPart(ref dest);
+ DCT.IDCT8x4_RightPart(ref source, ref dest);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
- Print8x8Data(expectedDestArray);
- Output.WriteLine("**************");
- Print8x8Data(actualDestArray);
+ this.Print8x8Data(expectedDestArray);
+ this.Output.WriteLine("**************");
+ this.Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray.Data, actualDestArray);
}
- private struct ApproximateFloatComparer : IEqualityComparer
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ public void TransformIDCT(int seed)
{
- private const float Eps = 0.0001f;
-
- public bool Equals(float x, float y)
- {
- float d = x - y;
-
- return d > -Eps && d < Eps;
- }
-
- public int GetHashCode(float obj)
- {
- throw new InvalidOperationException();
- }
- }
-
- [Fact]
- public void IDCTInto()
- {
- float[] sourceArray = Create8x8FloatData();
+ var sourceArray = Create8x8RandomFloatData(-200, 200, seed);
float[] expectedDestArray = new float[64];
float[] tempArray = new float[64];
ReferenceImplementations.iDCT2D_llm(sourceArray, expectedDestArray, tempArray);
- //ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray);
-
+ // ReferenceImplementations.iDCT8x8_llm_sse(sourceArray, expectedDestArray, tempArray);
Block8x8F source = new Block8x8F();
source.LoadFrom(sourceArray);
Block8x8F dest = new Block8x8F();
Block8x8F tempBuffer = new Block8x8F();
- source.TransformIDCTInto(ref dest, ref tempBuffer);
+ DCT.TransformIDCT(ref source, ref dest, ref tempBuffer);
float[] actualDestArray = new float[64];
dest.CopyTo(actualDestArray);
- Print8x8Data(expectedDestArray);
- Output.WriteLine("**************");
- Print8x8Data(actualDestArray);
- Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer());
- Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer());
+ this.Print8x8Data(expectedDestArray);
+ this.Output.WriteLine("**************");
+ this.Print8x8Data(actualDestArray);
+ Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f));
+ Assert.Equal(expectedDestArray, actualDestArray, new ApproximateFloatComparer(1f));
}
-
[Fact]
public unsafe void CopyColorsTo()
{
@@ -309,24 +308,23 @@ namespace ImageSharp.Tests.Formats.Jpg
int stride = 256;
int height = 42;
- int offset = height*10 + 20;
+ int offset = height * 10 + 20;
- byte[] colorsExpected = new byte[stride*height];
- byte[] colorsActual = new byte[stride*height];
+ byte[] colorsExpected = new byte[stride * height];
+ byte[] colorsActual = new byte[stride * height];
Block8x8F temp = new Block8x8F();
-
+
ReferenceImplementations.CopyColorsTo(ref block, new MutableSpan(colorsExpected, offset), stride);
block.CopyColorsTo(new MutableSpan(colorsActual, offset), stride, &temp);
- //Output.WriteLine("******* EXPECTED: *********");
- //PrintLinearData(colorsExpected);
- //Output.WriteLine("******** ACTUAL: **********");
-
+ // Output.WriteLine("******* EXPECTED: *********");
+ // PrintLinearData(colorsExpected);
+ // Output.WriteLine("******** ACTUAL: **********");
Assert.Equal(colorsExpected, colorsActual);
}
-
+
private static float[] Create8x8ColorCropTestData()
{
float[] result = new float[64];
@@ -337,6 +335,7 @@ namespace ImageSharp.Tests.Formats.Jpg
result[i * 8 + j] = -300 + i * 100 + j * 10;
}
}
+
return result;
}
@@ -346,22 +345,88 @@ namespace ImageSharp.Tests.Formats.Jpg
Block8x8F block = new Block8x8F();
var input = Create8x8ColorCropTestData();
block.LoadFrom(input);
- Output.WriteLine("Input:");
- PrintLinearData(input);
-
+ this.Output.WriteLine("Input:");
+ this.PrintLinearData(input);
Block8x8F dest = new Block8x8F();
block.TransformByteConvetibleColorValuesInto(ref dest);
float[] array = new float[64];
dest.CopyTo(array);
- Output.WriteLine("Result:");
- PrintLinearData(array);
+ this.Output.WriteLine("Result:");
+ this.PrintLinearData(array);
foreach (float val in array)
{
Assert.InRange(val, 0, 255);
}
}
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ public void FDCT8x4_LeftPart(int seed)
+ {
+ var src = Create8x8RandomFloatData(-200, 200, seed);
+ var srcBlock = new Block8x8F();
+ srcBlock.LoadFrom(src);
+
+ var destBlock = new Block8x8F();
+
+ var expectedDest = new MutableSpan(64);
+
+ ReferenceImplementations.fDCT2D8x4_32f(src, expectedDest);
+ DCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock);
+
+ var actualDest = new MutableSpan(64);
+ destBlock.CopyTo(actualDest);
+
+ Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f));
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ public void FDCT8x4_RightPart(int seed)
+ {
+ var src = Create8x8RandomFloatData(-200, 200, seed);
+ var srcBlock = new Block8x8F();
+ srcBlock.LoadFrom(src);
+
+ var destBlock = new Block8x8F();
+
+ var expectedDest = new MutableSpan(64);
+
+ ReferenceImplementations.fDCT2D8x4_32f(src.Slice(4), expectedDest.Slice(4));
+ DCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock);
+
+ var actualDest = new MutableSpan(64);
+ destBlock.CopyTo(actualDest);
+
+ Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f));
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ public void TransformFDCT(int seed)
+ {
+ var src = Create8x8RandomFloatData(-200, 200, seed);
+ var srcBlock = new Block8x8F();
+ srcBlock.LoadFrom(src);
+
+ var destBlock = new Block8x8F();
+
+ var expectedDest = new MutableSpan(64);
+ var temp1 = new MutableSpan(64);
+ var temp2 = new Block8x8F();
+
+ ReferenceImplementations.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true);
+ DCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false);
+
+ var actualDest = new MutableSpan(64);
+ destBlock.CopyTo(actualDest);
+
+ Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f));
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
index 11d535fb8..5168d1d67 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegTests.cs
@@ -5,20 +5,21 @@ using System.Linq;
using ImageSharp.Formats;
using Xunit;
using Xunit.Abstractions;
+// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests
{
- public class JpegTests
- {
-
- public const string TestOutputDirectory = "TestOutput/Jpeg";
+ using System.Numerics;
- private ITestOutputHelper Output { get; }
+ using ImageSharp.Formats.Jpg;
+ public class JpegTests : MeasureFixture
+ {
public JpegTests(ITestOutputHelper output)
+ : base(output)
{
- this.Output = output;
}
+
public static IEnumerable AllJpegFiles => TestImages.Jpeg.All;
[Theory]
@@ -56,5 +57,158 @@ namespace ImageSharp.Tests
image.Save(outputStream, encoder);
}
}
+
+ private const int BenchmarkExecTimes = 30;
+
+ public static readonly string[] EncoderBenchmarkFiles =
+ {
+ TestImages.Bmp.Car, TestImages.Bmp.NegHeight,
+ TestImages.Bmp.F, TestImages.Png.Splash,
+ TestImages.Jpeg.Jpeg420, TestImages.Jpeg.Calliphora,
+ TestImages.Jpeg.Cmyk
+ };
+
+ private const PixelTypes BenchmarkPixels = PixelTypes.StandardImageClass; //PixelTypes.Color | PixelTypes.Argb;
+
+ //[Theory] // Benchmark, enable manually
+ [InlineData(TestImages.Jpeg.Cmyk)]
+ [InlineData(TestImages.Jpeg.Ycck)]
+ [InlineData(TestImages.Jpeg.Calliphora)]
+ [InlineData(TestImages.Jpeg.Jpeg400)]
+ [InlineData(TestImages.Jpeg.Jpeg420)]
+ [InlineData(TestImages.Jpeg.Jpeg444)]
+ public void Benchmark_JpegDecoder(string fileName)
+ {
+ string path = TestFile.GetPath(fileName);
+ byte[] bytes = File.ReadAllBytes(path);
+
+ this.Measure(
+ 40,
+ () =>
+ {
+ Image img = new Image(bytes);
+ },
+ $"Decode {fileName}");
+
+ }
+
+ //[Theory] // Benchmark, enable manually
+ [WithFileCollection(nameof(EncoderBenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio420, 75)]
+ [WithFileCollection(nameof(EncoderBenchmarkFiles), BenchmarkPixels, JpegSubsample.Ratio444, 75)]
+ public void Benchmark_JpegEncoder(TestImageProvider provider, JpegSubsample subSample, int quality)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ var image = provider.GetImage();
+
+ using (var outputStream = new MemoryStream())
+ {
+ var encoder = new JpegEncoder()
+ {
+ Subsample = subSample,
+ Quality = quality
+ };
+
+ for (int i = 0; i < BenchmarkExecTimes; i++)
+ {
+ image.Save(outputStream, encoder);
+ outputStream.Seek(0, SeekOrigin.Begin);
+ }
+ }
+ }
+
+ public static Image CreateTestImage(GenericFactory factory)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ Image image = factory.CreateImage(10, 10);
+
+ using (var pixels = image.Lock())
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ for (int j = 0; j < 10; j++)
+ {
+ Vector4 v = new Vector4(i/10f, j/10f, 0, 1);
+
+ TColor color = default(TColor);
+ color.PackFromVector4(v);
+
+ pixels[i, j] = color;
+ }
+ }
+ }
+ return image;
+ }
+
+ [Theory]
+ [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)]
+ public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ var src = provider.GetImage();
+
+ PixelArea area = new PixelArea(8, 8, ComponentOrder.XYZ);
+ var dest = provider.Factory.CreateImage(8, 8);
+
+ using (var s = src.Lock())
+ {
+ using (var d = dest.Lock())
+ {
+ s.CopyRGBBytesStretchedTo(area, 0, 0);
+ d.CopyFrom(area, 0, 0);
+
+ Assert.Equal(s[0, 0], d[0, 0]);
+ Assert.Equal(s[7, 0], d[7, 0]);
+ Assert.Equal(s[0, 7], d[0, 7]);
+ Assert.Equal(s[7, 7], d[7, 7]);
+ }
+ }
+ }
+
+ [Theory]
+ [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Color | PixelTypes.StandardImageClass | PixelTypes.Argb)]
+ public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ var src = provider.GetImage();
+
+ PixelArea area = new PixelArea(8, 8, ComponentOrder.XYZ);
+ var dest = provider.Factory.CreateImage(8, 8);
+
+ using (var s = src.Lock())
+ {
+ using (var d = dest.Lock())
+ {
+ s.CopyRGBBytesStretchedTo(area, 7, 6);
+ d.CopyFrom(area, 0, 0);
+
+ Assert.Equal(s[6, 7], d[0, 0]);
+ Assert.Equal(s[6, 8], d[0, 1]);
+ Assert.Equal(s[7, 8], d[1, 1]);
+
+ Assert.Equal(s[6, 9], d[0, 2]);
+ Assert.Equal(s[6, 9], d[0, 3]);
+ Assert.Equal(s[6, 9], d[0, 7]);
+
+ Assert.Equal(s[7, 9], d[1, 2]);
+ Assert.Equal(s[7, 9], d[1, 3]);
+ Assert.Equal(s[7, 9], d[1, 7]);
+
+ Assert.Equal(s[9, 9], d[3, 2]);
+ Assert.Equal(s[9, 9], d[3, 3]);
+ Assert.Equal(s[9, 9], d[3, 7]);
+
+ Assert.Equal(s[9, 7], d[3, 0]);
+ Assert.Equal(s[9, 7], d[4, 0]);
+ Assert.Equal(s[9, 7], d[7, 0]);
+
+ Assert.Equal(s[9, 9], d[3, 2]);
+ Assert.Equal(s[9, 9], d[4, 2]);
+ Assert.Equal(s[9, 9], d[7, 2]);
+
+ Assert.Equal(s[9, 9], d[4, 3]);
+ Assert.Equal(s[9, 9], d[7, 7]);
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs
index cee236adf..c045d8937 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs
@@ -1,15 +1,17 @@
// ReSharper disable InconsistentNaming
-namespace ImageSharp.Tests.Formats.Jpg
+namespace ImageSharp.Tests
{
+ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp.Formats;
+ using ImageSharp.Formats.Jpg;
///
/// This class contains simplified (unefficient) reference implementations to produce verification data for unit tests
- /// DCT code Ported from https://github.com/norishigefukushima/dct_simd
+ /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd
///
internal static class ReferenceImplementations
{
@@ -46,6 +48,310 @@ namespace ImageSharp.Tests.Formats.Jpg
}
}
+ public static class IntegerReferenceDCT
+ {
+ 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;
+
+ ///
+ /// 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.
+ /// Leave results scaled up by an overall factor of 8.
+ ///
+ /// The block of coefficients.
+ public static void TransformFDCTInplace(MutableSpan 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);
+ }
+
+ }
+ 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 TransformIDCTInplace(MutableSpan 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;
+ }
+ }
+ }
+
///
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200
///
@@ -56,9 +362,11 @@ namespace ImageSharp.Tests.Formats.Jpg
float a0, a1, a2, a3, b0, b1, b2, b3;
float z0, z1, z2, z3, z4;
+ float r0 = 1.414214f;
float r1 = 1.387040f;
float r2 = 1.306563f;
float r3 = 1.175876f;
+ float r4 = 1.000000f;
float r5 = 0.785695f;
float r6 = 0.541196f;
float r7 = 0.275899f;
@@ -130,6 +438,145 @@ namespace ImageSharp.Tests.Formats.Jpg
}
}
+ ///
+ /// Original:
+ ///
+ /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15
+ ///
+ ///
+ /// Source
+ /// Destination
+ public static void fDCT2D8x4_32f(MutableSpan s, MutableSpan d)
+ {
+ Vector4 c0 = _mm_load_ps(s, 0);
+ Vector4 c1 = _mm_load_ps(s, 56);
+ Vector4 t0 = (c0 + c1);
+ Vector4 t7 = (c0 - c1);
+
+ c1 = _mm_load_ps(s, 48);
+ c0 = _mm_load_ps(s, 8);
+ Vector4 t1 = (c0 + c1);
+ Vector4 t6 = (c0 - c1);
+
+ c1 = _mm_load_ps(s, 40);
+ c0 = _mm_load_ps(s, 16);
+ Vector4 t2 = (c0 + c1);
+ Vector4 t5 = (c0 - c1);
+
+ c0 = _mm_load_ps(s, 24);
+ c1 = _mm_load_ps(s, 32);
+ Vector4 t3 = (c0 + c1);
+ Vector4 t4 = (c0 - c1);
+
+ /*
+ c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2;
+ c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2;
+ c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2;
+ c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2;
+ */
+
+ c0 = (t0 + t3);
+ Vector4 c3 = (t0 - t3);
+ c1 = (t1 + t2);
+ Vector4 c2 = (t1 - t2);
+
+ /*
+ c0 = t0 + t3; c3 = t0 - t3;
+ c1 = t1 + t2; c2 = t1 - t2;
+ */
+
+ _mm_store_ps(d, 0, (c0 + c1));
+
+ _mm_store_ps(d, 32, (c0 - c1));
+
+ /*y[0] = c0 + c1;
+ y[4] = c0 - c1;*/
+
+ Vector4 w0 = new Vector4(0.541196f);
+ Vector4 w1 = new Vector4(1.306563f);
+
+ _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3)));
+
+ _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2)));
+ /*
+ y[2] = c2 * r[6] + c3 * r[2];
+ y[6] = c3 * r[6] - c2 * r[2];
+ */
+
+ w0 = new Vector4(1.175876f);
+ w1 = new Vector4(0.785695f);
+ c3 = ((w0 * t4) + (w1 * t7));
+ c0 = ((w0 * t7) - (w1 * t4));
+ /*
+ c3 = t4 * r[3] + t7 * r[5];
+ c0 = t7 * r[3] - t4 * r[5];
+ */
+
+ w0 = new Vector4(1.387040f);
+ w1 = new Vector4(0.275899f);
+ c2 = ((w0 * t5) + (w1 * t6));
+ c1 = ((w0 * t6) - (w1 * t5));
+ /*
+ c2 = t5 * r[1] + t6 * r[7];
+ c1 = t6 * r[1] - t5 * r[7];
+ */
+
+ _mm_store_ps(d, 24, (c0 - c2));
+
+ _mm_store_ps(d, 40, (c3 - c1));
+ //y[5] = c3 - c1; y[3] = c0 - c2;
+
+ Vector4 invsqrt2 = new Vector4(0.707107f);
+ c0 = ((c0 + c2) * invsqrt2);
+ c3 = ((c3 + c1) * invsqrt2);
+ //c0 = (c0 + c2) * invsqrt2;
+ //c3 = (c3 + c1) * invsqrt2;
+
+ _mm_store_ps(d, 8, (c0 + c3));
+
+ _mm_store_ps(d, 56, (c0 - c3));
+ //y[1] = c0 + c3; y[7] = c0 - c3;
+
+ /*for(i = 0;i < 8;i++)
+ {
+ y[i] *= invsqrt2h;
+ }*/
+ }
+
+ public static void fDCT8x8_llm_sse(MutableSpan s, MutableSpan d, MutableSpan temp)
+ {
+ Transpose8x8(s, temp);
+
+ fDCT2D8x4_32f(temp, d);
+
+ fDCT2D8x4_32f(temp.Slice(4), d.Slice(4));
+
+ Transpose8x8(d, temp);
+
+ fDCT2D8x4_32f(temp, d);
+
+ fDCT2D8x4_32f(temp.Slice(4), d.Slice(4));
+
+ Vector4 c = new Vector4(0.1250f);
+
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//0
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//1
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//2
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//3
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//4
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//5
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//6
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//7
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//8
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//9
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//10
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//11
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//12
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//13
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//14
+ _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d.AddOffset(4);//15
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 _mm_load_ps(MutableSpan src, int offset)
{
@@ -329,5 +776,96 @@ namespace ImageSharp.Tests.Formats.Jpg
}
}
}
+
+ internal static void fDCT1Dllm_32f(MutableSpan x, MutableSpan y)
+ {
+ float t0, t1, t2, t3, t4, t5, t6, t7;
+ float c0, c1, c2, c3;
+ float[] r = new float[8];
+
+ //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); }
+ r[0] = 1.414214f;
+ r[1] = 1.387040f;
+ r[2] = 1.306563f;
+ r[3] = 1.175876f;
+ r[4] = 1.000000f;
+ r[5] = 0.785695f;
+ r[6] = 0.541196f;
+ r[7] = 0.275899f;
+
+ const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2);
+ const float invsqrt2h = 0.353554f; //invsqrt2*0.5f;
+
+ c1 = x[0];
+ c2 = x[7];
+ t0 = c1 + c2;
+ t7 = c1 - c2;
+ c1 = x[1];
+ c2 = x[6];
+ t1 = c1 + c2;
+ t6 = c1 - c2;
+ c1 = x[2];
+ c2 = x[5];
+ t2 = c1 + c2;
+ t5 = c1 - c2;
+ c1 = x[3];
+ c2 = x[4];
+ t3 = c1 + c2;
+ t4 = c1 - c2;
+
+ c0 = t0 + t3;
+ c3 = t0 - t3;
+ c1 = t1 + t2;
+ c2 = t1 - t2;
+
+ y[0] = c0 + c1;
+ y[4] = c0 - c1;
+ y[2] = c2 * r[6] + c3 * r[2];
+ y[6] = c3 * r[6] - c2 * r[2];
+
+ c3 = t4 * r[3] + t7 * r[5];
+ c0 = t7 * r[3] - t4 * r[5];
+ c2 = t5 * r[1] + t6 * r[7];
+ c1 = t6 * r[1] - t5 * r[7];
+
+ y[5] = c3 - c1;
+ y[3] = c0 - c2;
+ c0 = (c0 + c2) * invsqrt2;
+ c3 = (c3 + c1) * invsqrt2;
+ y[1] = c0 + c3;
+ y[7] = c0 - c3;
+ }
+
+ internal static void fDCT2D_llm(
+ MutableSpan s,
+ MutableSpan d,
+ MutableSpan temp,
+ bool downscaleBy8 = false,
+ bool offsetSourceByNeg128 = false)
+ {
+ MutableSpan sWorker = offsetSourceByNeg128 ? s.AddScalarToAllValues(-128f) : s;
+
+ for (int j = 0; j < 8; j++)
+ {
+ fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8));
+ }
+
+ Transpose8x8(temp, d);
+
+ for (int j = 0; j < 8; j++)
+ {
+ fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8));
+ }
+
+ Transpose8x8(temp, d);
+
+ if (downscaleBy8)
+ {
+ for (int j = 0; j < 64; j++)
+ {
+ d[j] *= 0.125f;
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs
new file mode 100644
index 000000000..dcccc58be
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs
@@ -0,0 +1,127 @@
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.Tests.Formats.Jpg
+{
+ using System.Numerics;
+ using ImageSharp.Formats;
+ using ImageSharp.Formats.Jpg;
+
+ using Xunit;
+ using Xunit.Abstractions;
+
+ public class ReferenceImplementationsTests : UtilityTestClassBase
+ {
+ public ReferenceImplementationsTests(ITestOutputHelper output)
+ : base(output)
+ {
+ }
+
+
+ [Theory]
+ [InlineData(42)]
+ [InlineData(1)]
+ [InlineData(2)]
+ public void Idct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed)
+ {
+ MutableSpan intData = Create8x8RandomIntData(-200, 200, seed);
+ MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan();
+
+ ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(intData);
+
+ MutableSpan dest = new MutableSpan(64);
+ MutableSpan temp = new MutableSpan(64);
+
+ ReferenceImplementations.iDCT2D_llm(floatSrc, dest, temp);
+
+ for (int i = 0; i < 64; i++)
+ {
+ float expected = intData[i];
+ float actual = dest[i];
+
+ Assert.Equal(expected, actual, new ApproximateFloatComparer(1f));
+ }
+ }
+
+ [Theory]
+ [InlineData(42, 0)]
+ [InlineData(1, 0)]
+ [InlineData(2, 0)]
+ public void IntegerDCT_ForwardThenInverse(int seed, int startAt)
+ {
+ MutableSpan original = Create8x8RandomIntData(-200, 200, seed);
+
+ var block = original.AddScalarToAllValues(128);
+
+ ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block);
+
+ for (int i = 0; i < 64; i++)
+ {
+ block[i] /= 8;
+ }
+
+ ReferenceImplementations.IntegerReferenceDCT.TransformIDCTInplace(block);
+
+ for (int i = startAt; i < 64; i++)
+ {
+ float expected = original[i];
+ float actual = (float)block[i];
+
+ Assert.Equal(expected, actual, new ApproximateFloatComparer(3f));
+ }
+
+ }
+
+ [Theory]
+ [InlineData(42, 0)]
+ [InlineData(1, 0)]
+ [InlineData(2, 0)]
+ public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt)
+ {
+ var data = Create8x8RandomIntData(-200, 200, seed);
+ MutableSpan src = new MutableSpan(data).ConvertToFloat32MutableSpan();
+ MutableSpan dest = new MutableSpan(64);
+ MutableSpan temp = new MutableSpan(64);
+
+ ReferenceImplementations.fDCT2D_llm(src, dest, temp, true);
+ ReferenceImplementations.iDCT2D_llm(dest, src, temp);
+
+ for (int i = startAt; i < 64; i++)
+ {
+ float expected = data[i];
+ float actual = (float)src[i];
+
+ Assert.Equal(expected, actual, new ApproximateFloatComparer(2f));
+ }
+ }
+
+ [Fact]
+ public void HowMuchIsTheFish()
+ {
+ Output.WriteLine(Vector.Count.ToString());
+ }
+
+ [Theory]
+ [InlineData(42)]
+ [InlineData(1)]
+ [InlineData(2)]
+ public void Fdct_FloatingPointReferenceImplementation_IsEquivalentToIntegerImplementation(int seed)
+ {
+ MutableSpan intData = Create8x8RandomIntData(-200, 200, seed);
+ MutableSpan floatSrc = intData.ConvertToFloat32MutableSpan();
+
+ ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(intData);
+
+ MutableSpan dest = new MutableSpan(64);
+ MutableSpan temp = new MutableSpan(64);
+
+ ReferenceImplementations.fDCT2D_llm(floatSrc, dest, temp, offsetSourceByNeg128: true);
+
+ for (int i = 0; i < 64; i++)
+ {
+ float expected = intData[i];
+ float actual = dest[i];
+
+ Assert.Equal(expected, actual, new ApproximateFloatComparer(1f));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs
index 55e609a52..74c6772b7 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/UtilityTestClassBase.cs
@@ -1,21 +1,53 @@
-using System;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Text;
+using System.Text;
using ImageSharp.Formats;
using Xunit.Abstractions;
+// ReSharper disable InconsistentNaming
-namespace ImageSharp.Tests.Formats.Jpg
+namespace ImageSharp.Tests
{
- public class UtilityTestClassBase
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.Formats.Jpg;
+
+ ///
+ /// Utility class to measure the execution of an operation.
+ ///
+ public class MeasureFixture
{
- public UtilityTestClassBase(ITestOutputHelper output)
+ protected bool EnablePrinting = true;
+
+ protected void Measure(int times, Action action, [CallerMemberName] string operationName = null)
{
- Output = output;
+ if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} X {times} ...");
+ Stopwatch sw = Stopwatch.StartNew();
+
+ for (int i = 0; i < times; i++)
+ {
+ action();
+ }
+
+ sw.Stop();
+ if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms");
+ }
+
+ public MeasureFixture(ITestOutputHelper output)
+ {
+ this.Output = output;
}
protected ITestOutputHelper Output { get; }
+ }
+
+ public class UtilityTestClassBase : MeasureFixture
+ {
+ public UtilityTestClassBase(ITestOutputHelper output) : base(output)
+ {
+ }
+
// ReSharper disable once InconsistentNaming
public static float[] Create8x8FloatData()
{
@@ -29,10 +61,7 @@ namespace ImageSharp.Tests.Formats.Jpg
}
return result;
}
-
-
-
-
+
// ReSharper disable once InconsistentNaming
public static int[] Create8x8IntData()
{
@@ -47,7 +76,25 @@ namespace ImageSharp.Tests.Formats.Jpg
return result;
}
- internal void Print8x8Data(MutableSpan data) => Print8x8Data(data.Data);
+ // ReSharper disable once InconsistentNaming
+ public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42)
+ {
+ Random rnd = new Random(seed);
+ int[] result = new int[64];
+ for (int i = 0; i < 8; i++)
+ {
+ for (int j = 0; j < 8; j++)
+ {
+ result[i * 8 + j] = rnd.Next(minValue, maxValue);
+ }
+ }
+ return result;
+ }
+
+ internal static MutableSpan Create8x8RandomFloatData(int minValue, int maxValue, int seed = 42)
+ => new MutableSpan(Create8x8RandomIntData(minValue, maxValue, seed)).ConvertToFloat32MutableSpan();
+
+ internal void Print8x8Data(MutableSpan data) => this.Print8x8Data(data.Data);
internal void Print8x8Data(T[] data)
{
@@ -61,10 +108,10 @@ namespace ImageSharp.Tests.Formats.Jpg
bld.AppendLine();
}
- Output.WriteLine(bld.ToString());
+ this.Output.WriteLine(bld.ToString());
}
- internal void PrintLinearData(T[] data) => PrintLinearData(new MutableSpan(data), data.Length);
+ internal void PrintLinearData(T[] data) => this.PrintLinearData(new MutableSpan(data), data.Length);
internal void PrintLinearData(MutableSpan data, int count = -1)
{
@@ -75,21 +122,35 @@ namespace ImageSharp.Tests.Formats.Jpg
{
bld.Append($"{data[i],3} ");
}
- Output.WriteLine(bld.ToString());
+ this.Output.WriteLine(bld.ToString());
}
- protected void Measure(int times, Action action, [CallerMemberName] string operationName = null)
+ internal struct ApproximateFloatComparer : IEqualityComparer
{
- Output.WriteLine($"{operationName} X {times} ...");
- Stopwatch sw = Stopwatch.StartNew();
+ private readonly float Eps;
- for (int i = 0; i < times; i++)
+ public ApproximateFloatComparer(float eps = 1f)
{
- action();
+ this.Eps = eps;
}
- sw.Stop();
- Output.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms");
+ public bool Equals(float x, float y)
+ {
+ float d = x - y;
+
+ return d > -this.Eps && d < this.Eps;
+ }
+
+ public int GetHashCode(float obj)
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ protected void Print(string msg)
+ {
+ Debug.WriteLine(msg);
+ this.Output.WriteLine(msg);
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs
index aab7c2620..892e344d6 100644
--- a/tests/ImageSharp.Tests/TestFile.cs
+++ b/tests/ImageSharp.Tests/TestFile.cs
@@ -34,12 +34,17 @@ namespace ImageSharp.Tests
this.Bytes = File.ReadAllBytes(file);
this.image = new Image(this.Bytes);
}
+
+ public static string GetPath(string file)
+ {
+ return Path.Combine(FormatsDirectory, file);
+ }
public static TestFile Create(string file)
{
return cache.GetOrAdd(file, (string fileName) =>
{
- return new TestFile(FormatsDirectory + fileName);
+ return new TestFile(GetPath(file));
});
}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index e5b46b0e1..7265e45a4 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -56,7 +56,7 @@ namespace ImageSharp.Tests
public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg";
public static readonly string[] All = {
- Cmyk, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray,
+ Cmyk, Ycck, Exif, Floorplan, Calliphora, Turtle, Fb, Progress, GammaDalaiLamaGray,
Festzug, Hiyamugi,
Jpeg400, Jpeg420, Jpeg444,
};
diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs
index 8e164cb3c..c2f0aed84 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs
@@ -23,5 +23,10 @@ namespace ImageSharp.Tests
{
return new Image(bytes);
}
+
+ public virtual Image CreateImage(Image other)
+ {
+ return new Image(other);
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs
index 288dbdd8c..a8d398c1e 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs
@@ -10,5 +10,11 @@ namespace ImageSharp.Tests
public override Image CreateImage(byte[] bytes) => new Image(bytes);
public override Image CreateImage(int width, int height) => new Image(width, height);
+
+ public override Image CreateImage(Image other)
+ {
+ Image img = (Image)other;
+ return new Image(img);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
index edd6d03a1..1b8c1498e 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
@@ -13,8 +13,18 @@ namespace ImageSharp.Tests
{
private class FileProvider : TestImageProvider
{
- private static ConcurrentDictionary> cache =
- new ConcurrentDictionary>();
+ // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider
+ // are shared between PixelTypes.Color & PixelTypes.StandardImageClass
+ private class Key : Tuple
+ {
+ public Key(PixelTypes item1, string item2)
+ : base(item1, item2)
+ {
+ }
+ }
+
+ private static ConcurrentDictionary> cache =
+ new ConcurrentDictionary>();
private string filePath;
@@ -27,15 +37,17 @@ namespace ImageSharp.Tests
public override Image GetImage()
{
+ var key = new Key(this.PixelType, this.filePath);
+
var cachedImage = cache.GetOrAdd(
- this.filePath,
+ key,
fn =>
{
var testFile = TestFile.Create(this.filePath);
return this.Factory.CreateImage(testFile.Bytes);
});
- return new Image(cachedImage);
+ return this.Factory.CreateImage(cachedImage);
}
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
index 8a8db3ee8..911719afa 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
@@ -80,7 +80,7 @@ namespace ImageSharp.Tests
this.Utility = new ImagingTestCaseUtility()
{
SourceFileOrDescription = this.SourceFileOrDescription,
- PixelTypeName = typeof(TColor).Name
+ PixelTypeName = this.PixelType.ToString()
};
if (testMethod != null)
@@ -90,5 +90,11 @@ namespace ImageSharp.Tests
return this;
}
+
+ public override string ToString()
+ {
+ string provName = this.GetType().Name.Replace("Provider", "");
+ return $"{this.SourceFileOrDescription}[{this.PixelType}]";
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
index 28e5ad2c2..31014f89d 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs
@@ -57,6 +57,7 @@ namespace ImageSharp.Tests
[Theory]
[WithBlankImages(1, 1, PixelTypes.StandardImageClass)]
+ [WithFile(TestImages.Bmp.F, PixelTypes.StandardImageClass)]
public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass(
TestImageProvider provider)
where TColor : struct, IPackedPixel, IEquatable