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