diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs new file mode 100644 index 000000000..a9ad4e9dd --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -0,0 +1,97 @@ +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + using System.Diagnostics; + + /// + /// Represents a Jpeg block with coefficiens. + /// + // ReSharper disable once InconsistentNaming + internal unsafe struct Block8x8 + { + /// + /// A number of scalar coefficients in a + /// + public const int Size = 64; + + private fixed short data[Size]; + + public Block8x8(Span coefficients) + { + ref byte selfRef = ref Unsafe.As(ref this); + ref byte sourceRef = ref coefficients.NonPortableCast().DangerousGetPinnableReference(); + Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); + } + + /// + /// Pointer-based "Indexer" (getter part) + /// + /// Block pointer + /// Index + /// The scaleVec value at the specified index + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe short GetScalarAt(Block8x8* blockPtr, int idx) + { + GuardBlockIndex(idx); + + short* fp = (short*)blockPtr; + return fp[idx]; + } + + /// + /// Pointer-based "Indexer" (setter part) + /// + /// Block pointer + /// Index + /// Value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void SetScalarAt(Block8x8* blockPtr, int idx, short value) + { + GuardBlockIndex(idx); + + short* fp = (short*)blockPtr; + fp[idx] = value; + } + + + public short this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + GuardBlockIndex(idx); + ref short selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, idx); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + GuardBlockIndex(idx); + ref short selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, idx) = value; + } + } + + [Conditional("DEBUG")] + private static void GuardBlockIndex(int idx) + { + DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); + DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); + } + + public Block8x8F AsFloatBlock() + { + // TODO: Optimize this + var result = default(Block8x8F); + for (int i = 0; i < Size; i++) + { + result[i] = this[i]; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 9ee5cfc70..491908f92 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,10 +10,8 @@ using System.Runtime.InteropServices; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Common { - using SixLabors.ImageSharp.Memory; - /// - /// DCT code Ported from https://github.com/norishigefukushima/dct_simd + /// Represents a Jpeg block with coefficients. /// internal partial struct Block8x8F { @@ -27,9 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public const int VectorCount = 16; /// - /// Scalar count + /// A number of scalar coefficients in a /// - public const int ScalarCount = VectorCount * 4; + public const int Size = 64; #pragma warning disable SA1600 // ElementsMustBeDocumented public Vector4 V0L; @@ -65,29 +64,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// The index /// The float value at the specified index - public unsafe float this[int idx] + public float this[int idx] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - fixed (Block8x8F* p = &this) - { - float* fp = (float*)p; - return fp[idx]; - } + GuardBlockIndex(idx); + ref float selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, idx); } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - fixed (Block8x8F* p = &this) - { - float* fp = (float*)p; - fp[idx] = value; - } + GuardBlockIndex(idx); + ref float selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, idx) = value; } } - + /// /// Pointer-based "Indexer" (getter part) /// @@ -97,6 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx) { + GuardBlockIndex(idx); + float* fp = (float*)blockPtr; return fp[idx]; } @@ -110,10 +107,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value) { + GuardBlockIndex(idx); + float* fp = (float*)blockPtr; fp[idx] = value; } + public Block8x8 AsInt16Block() + { + // TODO: Optimize this + var result = default(Block8x8); + for (int i = 0; i < Size; i++) + { + result[i] = (short)this[i]; + } + + return result; + } + /// /// Fill the block with defaults (zeroes) /// @@ -134,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common ref byte s = ref Unsafe.As(ref source.DangerousGetPinnableReference()); ref byte d = ref Unsafe.As(ref this); - Unsafe.CopyBlock(ref d, ref s, ScalarCount * sizeof(float)); + Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } /// @@ -157,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common fixed (Vector4* ptr = &this.V0L) { float* fp = (float*)ptr; - for (int i = 0; i < ScalarCount; i++) + for (int i = 0; i < Size; i++) { fp[i] = source[i]; } @@ -174,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common ref byte d = ref Unsafe.As(ref dest.DangerousGetPinnableReference()); ref byte s = ref Unsafe.As(ref this); - Unsafe.CopyBlock(ref d, ref s, ScalarCount * sizeof(float)); + Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } /// @@ -186,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) { float* fPtr = (float*)blockPtr; - for (int i = 0; i < ScalarCount; i++) + for (int i = 0; i < Size; i++) { dest[i] = (byte)*fPtr; fPtr++; @@ -213,7 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common { fixed (void* ptr = &this.V0L) { - Marshal.Copy((IntPtr)ptr, dest, 0, ScalarCount); + Marshal.Copy((IntPtr)ptr, dest, 0, Size); } } @@ -226,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common fixed (Vector4* ptr = &this.V0L) { float* fp = (float*)ptr; - for (int i = 0; i < ScalarCount; i++) + for (int i = 0; i < Size; i++) { dest[i] = (int)fp[i]; } @@ -294,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common { float* b = (float*)blockPtr; float* qtp = (float*)qtPtr; - for (int zig = 0; zig < ScalarCount; zig++) + for (int zig = 0; zig < Size; zig++) { float* unzigPos = b + unzigPtr[zig]; float val = *unzigPos; @@ -347,7 +358,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common float* s = (float*)block; float* d = (float*)dest; - for (int zig = 0; zig < ScalarCount; zig++) + for (int zig = 0; zig < Size; zig++) { d[zig] = s[unzigPtr[zig]]; } @@ -411,5 +422,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common // AlmostRound(dividend/divisor) = dividend/divisior + 0.5*sign(dividend) return (dividend / divisor) + (sign * Offset); } + + [Conditional("DEBUG")] + private static void GuardBlockIndex(int idx) + { + DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); + DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs index aaefbb3af..e243938e3 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.Common { /// + /// TODO: This should be simply just a ! /// 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 diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs index 4f1b8783a..6b16ea824 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/BlockQuad.cs @@ -14,6 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components /// /// The value-type buffer sized for 4 instances. /// - public fixed float Data[4 * Block8x8F.ScalarCount]; + public fixed float Data[4 * Block8x8F.Size]; } } \ 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 cff46391e..a3a8d1218 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -43,13 +43,13 @@ namespace SixLabors.ImageSharp.Tests { Block8x8F block = new Block8x8F(); - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { block[i] = i; } sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += block[i]; } @@ -67,13 +67,13 @@ namespace SixLabors.ImageSharp.Tests { Block8x8F block = new Block8x8F(); - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { Block8x8F.SetScalarAt(&block, i, i); } sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += Block8x8F.GetScalarAt(&block, i); } @@ -92,13 +92,13 @@ namespace SixLabors.ImageSharp.Tests { // Block8x8F block = new Block8x8F(); float[] block = new float[64]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { block[i] = i; } sum = 0; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { sum += block[i]; } @@ -109,10 +109,10 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Load_Store_FloatArray() { - float[] data = new float[Block8x8F.ScalarCount]; - float[] mirror = new float[Block8x8F.ScalarCount]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { data[i] = i; } @@ -134,10 +134,10 @@ namespace SixLabors.ImageSharp.Tests [Fact] public unsafe void Load_Store_FloatArray_Ptr() { - float[] data = new float[Block8x8F.ScalarCount]; - float[] mirror = new float[Block8x8F.ScalarCount]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { data[i] = i; } @@ -159,10 +159,10 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Load_Store_IntArray() { - int[] data = new int[Block8x8F.ScalarCount]; - int[] mirror = new int[Block8x8F.ScalarCount]; + int[] data = new int[Block8x8F.Size]; + int[] mirror = new int[Block8x8F.Size]; - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { data[i] = i; } @@ -448,14 +448,14 @@ namespace SixLabors.ImageSharp.Tests UnzigData unzig = UnzigData.Create(); - int* expectedResults = stackalloc int[Block8x8F.ScalarCount]; + int* expectedResults = stackalloc int[Block8x8F.Size]; ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data); Block8x8F actualResults = default(Block8x8F); Block8x8F.UnzigDivRound(&block, &actualResults, &qt, unzig.Data); - for (int i = 0; i < Block8x8F.ScalarCount; i++) + for (int i = 0; i < Block8x8F.Size; i++) { int expected = expectedResults[i]; int actual = (int)actualResults[i]; @@ -463,5 +463,21 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected, actual); } } + + [Fact] + public void AsInt16Block() + { + float[] data = Create8x8FloatData(); + + var source = default(Block8x8F); + source.LoadFrom(data); + + Block8x8 dest = source.AsInt16Block(); + + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal((short)data[i], dest[i]); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs new file mode 100644 index 000000000..46b12b80f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -0,0 +1,80 @@ +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests +{ + using Moq; + + using SixLabors.ImageSharp.Formats.Jpeg.Common; + + using Xunit; + using Xunit.Abstractions; + + public class Block8x8Tests : JpegUtilityTestFixture + { + public Block8x8Tests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void Construct_And_Indexer_Get() + { + short[] data = Create8x8ShortData(); + + var block = new Block8x8(data); + + for (int i = 0; i < Block8x8.Size; i++) + { + Assert.Equal(data[i], block[i]); + } + } + + [Fact] + public void Indexer_Set() + { + var block = default(Block8x8); + + block[17] = 17; + block[42] = 42; + + Assert.Equal(0, block[0]); + Assert.Equal(17, block[17]); + Assert.Equal(42, block[42]); + } + + + [Fact] + public unsafe void Indexer_GetScalarAt_SetScalarAt() + { + int sum = 0; + var block = default(Block8x8); + + for (int i = 0; i < Block8x8.Size; i++) + { + Block8x8.SetScalarAt(&block, i, i); + } + + sum = 0; + for (int i = 0; i < Block8x8.Size; i++) + { + sum += Block8x8.GetScalarAt(&block, i); + } + Assert.Equal(sum, 64 * 63 / 2); + } + + + [Fact] + public void AsFloatBlock() + { + short[] data = Create8x8ShortData(); + + var source = new Block8x8(data); + + Block8x8F dest = source.AsFloatBlock(); + + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal((float)data[i], dest[i]); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs index ffb3c1af8..c3ccba8f6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs @@ -45,6 +45,20 @@ namespace SixLabors.ImageSharp.Tests return result; } + // ReSharper disable once InconsistentNaming + public static short[] Create8x8ShortData() + { + short[] result = new short[64]; + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 8; j++) + { + result[i * 8 + j] = (short)(i * 10 + j); + } + } + return result; + } + // ReSharper disable once InconsistentNaming public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs index f085c858c..e86f96572 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementations.cs @@ -888,7 +888,7 @@ namespace SixLabors.ImageSharp.Tests float* s = (float*)src; float* q = (float*)qt; - for (int zig = 0; zig < Block8x8F.ScalarCount; zig++) + for (int zig = 0; zig < Block8x8F.Size; zig++) { int a = (int)s[unzigPtr[zig]]; int b = (int)q[zig];