diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.cs b/src/ImageSharp/Common/Extensions/SimdUtils.cs
index d6b2fad09..0188bc03c 100644
--- a/src/ImageSharp/Common/Extensions/SimdUtils.cs
+++ b/src/ImageSharp/Common/Extensions/SimdUtils.cs
@@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp
internal static class SimdUtils
{
///
- /// Indicates AVX2 architecture where both float and integer registers are of size 256 byte.
+ /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte.
///
- public static readonly bool IsAvx2CompatibleArchitecture = Vector.Count == 8 && Vector.Count == 8;
+ public static bool IsAvx2CompatibleArchitecture => Vector.Count == 8 && Vector.Count == 8;
internal static void GuardAvx2(string operation)
{
diff --git a/src/ImageSharp/Common/Tuples/Tuple8.cs b/src/ImageSharp/Common/Tuples/Tuple8.cs
new file mode 100644
index 000000000..3335e6e37
--- /dev/null
+++ b/src/ImageSharp/Common/Tuples/Tuple8.cs
@@ -0,0 +1,98 @@
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Common.Tuples
+{
+ ///
+ /// Contains value type tuples of 8 elements.
+ /// TODO: We should T4 this stuff to be DRY
+ ///
+ internal static class Tuple8
+ {
+ ///
+ /// Value type tuple of 8 -s
+ ///
+ [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))]
+ public struct OfUInt32
+ {
+ [FieldOffset(0 * sizeof(uint))]
+ public uint V0;
+
+ [FieldOffset(1 * sizeof(uint))]
+ public uint V1;
+
+ [FieldOffset(2 * sizeof(uint))]
+ public uint V2;
+
+ [FieldOffset(3 * sizeof(uint))]
+ public uint V3;
+
+ [FieldOffset(4 * sizeof(uint))]
+ public uint V4;
+
+ [FieldOffset(5 * sizeof(uint))]
+ public uint V5;
+
+ [FieldOffset(6 * sizeof(uint))]
+ public uint V6;
+
+ [FieldOffset(7 * sizeof(uint))]
+ public uint V7;
+
+ public override string ToString()
+ {
+ return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]";
+ }
+ }
+
+ ///
+ /// Value type tuple of 8 -s
+ ///
+ [StructLayout(LayoutKind.Explicit, Size = 8)]
+ public struct OfByte
+ {
+ [FieldOffset(0)]
+ public byte V0;
+
+ [FieldOffset(1)]
+ public byte V1;
+
+ [FieldOffset(2)]
+ public byte V2;
+
+ [FieldOffset(3)]
+ public byte V3;
+
+ [FieldOffset(4)]
+ public byte V4;
+
+ [FieldOffset(5)]
+ public byte V5;
+
+ [FieldOffset(6)]
+ public byte V6;
+
+ [FieldOffset(7)]
+ public byte V7;
+
+ public override string ToString()
+ {
+ return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]";
+ }
+
+ ///
+ /// Sets the values of this tuple by casting all elements of the given tuple to .
+ ///
+ public void LoadFrom(ref OfUInt32 i)
+ {
+ this.V0 = (byte)i.V0;
+ this.V1 = (byte)i.V1;
+ this.V2 = (byte)i.V2;
+ this.V3 = (byte)i.V3;
+ this.V4 = (byte)i.V4;
+ this.V5 = (byte)i.V5;
+ this.V6 = (byte)i.V6;
+ this.V7 = (byte)i.V7;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs
new file mode 100644
index 000000000..4f43c9811
--- /dev/null
+++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs
@@ -0,0 +1,75 @@
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Common.Tuples
+{
+ ///
+ /// Its faster to process multiple Vector4-s together, so let's pair them!
+ /// On AVX2 this pair should be convertible to of !
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct Vector4Pair
+ {
+ public Vector4 A;
+
+ public Vector4 B;
+
+ private static readonly Vector4 Scale = new Vector4(1 / 255f);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void MultiplyInplace(float value)
+ {
+ this.A *= value;
+ this.B *= value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddInplace(Vector4 value)
+ {
+ this.A += value;
+ this.B += value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddInplace(ref Vector4Pair other)
+ {
+ this.A += other.A;
+ this.B += other.B;
+ }
+
+ ///
+ /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4!
+ /// TODO: Move it somewhere else.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void RoundAndDownscalePreAvx2()
+ {
+ ref Vector a = ref Unsafe.As>(ref this.A);
+ a = a.FastRound();
+
+ ref Vector b = ref Unsafe.As>(ref this.B);
+ b = b.FastRound();
+
+ // Downscale by 1/255
+ this.A *= Scale;
+ this.B *= Scale;
+ }
+
+ ///
+ /// AVX2-only Downscale method, specific to Jpeg color conversion.
+ /// TODO: Move it somewhere else.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void RoundAndDownscaleAvx2()
+ {
+ ref Vector self = ref Unsafe.As>(ref this);
+ Vector v = self;
+ v = v.FastRound();
+
+ // Downscale by 1/255
+ v *= new Vector(1 / 255f);
+ self = v;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs
index 3f4c69c3e..96ed0a8c9 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs
@@ -176,17 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
///
- /// Convert into
+ /// Convert to
///
public Block8x8F AsFloatBlock()
{
- // TODO: Optimize this
var result = default(Block8x8F);
- for (int i = 0; i < Size; i++)
- {
- result[i] = this[i];
- }
-
+ result.LoadFrom(ref this);
return result;
}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs
new file mode 100644
index 000000000..c1c5cfcde
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs
@@ -0,0 +1,146 @@
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Formats.Jpeg.Common
+{
+ internal partial struct Block8x8F
+ {
+ ///
+ /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical.
+ ///
+ public void CopyTo(BufferArea area, int horizontalScale, int verticalScale)
+ {
+ if (horizontalScale == 1 && verticalScale == 1)
+ {
+ this.CopyTo(area);
+ return;
+ }
+ else if (horizontalScale == 2 && verticalScale == 2)
+ {
+ this.CopyTo2x2(area);
+ return;
+ }
+
+ // TODO: Optimize: implement all the cases with loopless special code! (T4?)
+ for (int y = 0; y < 8; y++)
+ {
+ int yy = y * verticalScale;
+ int y8 = y * 8;
+
+ for (int x = 0; x < 8; x++)
+ {
+ int xx = x * horizontalScale;
+
+ float value = this[y8 + x];
+
+ for (int i = 0; i < verticalScale; i++)
+ {
+ for (int j = 0; j < horizontalScale; j++)
+ {
+ area[xx + j, yy + i] = value;
+ }
+ }
+ }
+ }
+ }
+
+ // [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void CopyTo(BufferArea area)
+ {
+ ref byte selfBase = ref Unsafe.As(ref this);
+ ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo());
+ int destStride = area.Stride * sizeof(float);
+
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 2);
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 3);
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 4);
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 5);
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 6);
+ CopyRowImpl(ref selfBase, ref destBase, destStride, 7);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
+ {
+ ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
+ ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
+ Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
+ }
+
+ private void CopyTo2x2(BufferArea area)
+ {
+ ref float destBase = ref area.GetReferenceToOrigo();
+ int destStride = area.Stride;
+
+ this.WidenCopyImpl2x2(ref destBase, 0, destStride);
+ this.WidenCopyImpl2x2(ref destBase, 1, destStride);
+ this.WidenCopyImpl2x2(ref destBase, 2, destStride);
+ this.WidenCopyImpl2x2(ref destBase, 3, destStride);
+ this.WidenCopyImpl2x2(ref destBase, 4, destStride);
+ this.WidenCopyImpl2x2(ref destBase, 5, destStride);
+ this.WidenCopyImpl2x2(ref destBase, 6, destStride);
+ this.WidenCopyImpl2x2(ref destBase, 7, destStride);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void WidenCopyImpl2x2(ref float destBase, int row, int destStride)
+ {
+ ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row);
+ ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1);
+ ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride);
+
+ Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X;
+ Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X;
+ Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y;
+ Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y;
+ Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z;
+ Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z;
+ Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W;
+ Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W;
+
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W;
+
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 0) = selfLeft.X;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 1) = selfLeft.X;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 2) = selfLeft.Y;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 3) = selfLeft.Y;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 4) = selfLeft.Z;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 5) = selfLeft.Z;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 6) = selfLeft.W;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 7) = selfLeft.W;
+
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 0) = selfRight.X;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 1) = selfRight.X;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 2) = selfRight.Y;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 3) = selfRight.Y;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 4) = selfRight.Z;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 5) = selfRight.Z;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 6) = selfRight.W;
+ Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 7) = selfRight.W;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WidenCopyImpl(ref Vector4 s, ref float destBase)
+ {
+ Unsafe.Add(ref destBase, 0) = s.X;
+ Unsafe.Add(ref destBase, 1) = s.X;
+ Unsafe.Add(ref destBase, 2) = s.Y;
+ Unsafe.Add(ref destBase, 3) = s.Y;
+ Unsafe.Add(ref destBase, 4) = s.Z;
+ Unsafe.Add(ref destBase, 5) = s.Z;
+ Unsafe.Add(ref destBase, 6) = s.W;
+ Unsafe.Add(ref destBase, 7) = s.W;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs
index 4c1b4f4d1..93e9e0388 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs
@@ -96,51 +96,139 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
///
/// Level shift by +128, clip to [0, 255]
///
- /// The destination block
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void NormalizeColorsInto(ref Block8x8F d)
+ public void NormalizeColorsInplace()
{
- d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4);
- d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4);
- d.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4);
- d.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4);
- d.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4);
- d.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4);
- d.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4);
- d.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4);
- d.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4);
- d.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4);
- d.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4);
- d.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4);
- d.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4);
- d.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4);
- d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4);
- d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4);
+ this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4);
+ this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4);
+ this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4);
+ this.V1R = Vector4.Clamp(this.V1R + COff4, CMin4, CMax4);
+ this.V2L = Vector4.Clamp(this.V2L + COff4, CMin4, CMax4);
+ this.V2R = Vector4.Clamp(this.V2R + COff4, CMin4, CMax4);
+ this.V3L = Vector4.Clamp(this.V3L + COff4, CMin4, CMax4);
+ this.V3R = Vector4.Clamp(this.V3R + COff4, CMin4, CMax4);
+ this.V4L = Vector4.Clamp(this.V4L + COff4, CMin4, CMax4);
+ this.V4R = Vector4.Clamp(this.V4R + COff4, CMin4, CMax4);
+ this.V5L = Vector4.Clamp(this.V5L + COff4, CMin4, CMax4);
+ this.V5R = Vector4.Clamp(this.V5R + COff4, CMin4, CMax4);
+ this.V6L = Vector4.Clamp(this.V6L + COff4, CMin4, CMax4);
+ this.V6R = Vector4.Clamp(this.V6R + COff4, CMin4, CMax4);
+ this.V7L = Vector4.Clamp(this.V7L + COff4, CMin4, CMax4);
+ this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4);
}
-
///
- /// Level shift by +128, clip to [0, 255]
+ /// AVX2-only variant for executing and in one step.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void NormalizeColorsInplace()
+ public void NormalizeColorsAndRoundInplaceAvx2()
+ {
+ Vector off = new Vector(128f);
+ Vector max = new Vector(255F);
+
+ ref Vector row0 = ref Unsafe.As>(ref this.V0L);
+ row0 = NormalizeAndRound(row0, off, max);
+
+ ref Vector row1 = ref Unsafe.As>(ref this.V1L);
+ row1 = NormalizeAndRound(row1, off, max);
+
+ ref Vector row2 = ref Unsafe.As>(ref this.V2L);
+ row2 = NormalizeAndRound(row2, off, max);
+
+ ref Vector row3 = ref Unsafe.As>(ref this.V3L);
+ row3 = NormalizeAndRound(row3, off, max);
+
+ ref Vector row4 = ref Unsafe.As>(ref this.V4L);
+ row4 = NormalizeAndRound(row4, off, max);
+
+ ref Vector row5 = ref Unsafe.As>(ref this.V5L);
+ row5 = NormalizeAndRound(row5, off, max);
+
+ ref Vector row6 = ref Unsafe.As>(ref this.V6L);
+ row6 = NormalizeAndRound(row6, off, max);
+
+ ref Vector row7 = ref Unsafe.As>(ref this.V7L);
+ row7 = NormalizeAndRound(row7, off, max);
+
+ }
+
+ ///
+ /// Fill the block from 'source' doing short -> float conversion.
+ ///
+ public void LoadFrom(ref Block8x8 source)
{
- this.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4);
- this.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4);
- this.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4);
- this.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4);
- this.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4);
- this.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4);
- this.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4);
- this.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4);
- this.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4);
- this.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4);
- this.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4);
- this.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4);
- this.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4);
- this.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4);
- this.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4);
- this.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4);
+ ref short selfRef = ref Unsafe.As(ref source);
+
+ this.V0L.X = Unsafe.Add(ref selfRef, 0);
+ this.V0L.Y = Unsafe.Add(ref selfRef, 1);
+ this.V0L.Z = Unsafe.Add(ref selfRef, 2);
+ this.V0L.W = Unsafe.Add(ref selfRef, 3);
+ this.V0R.X = Unsafe.Add(ref selfRef, 4);
+ this.V0R.Y = Unsafe.Add(ref selfRef, 5);
+ this.V0R.Z = Unsafe.Add(ref selfRef, 6);
+ this.V0R.W = Unsafe.Add(ref selfRef, 7);
+
+ this.V1L.X = Unsafe.Add(ref selfRef, 8);
+ this.V1L.Y = Unsafe.Add(ref selfRef, 9);
+ this.V1L.Z = Unsafe.Add(ref selfRef, 10);
+ this.V1L.W = Unsafe.Add(ref selfRef, 11);
+ this.V1R.X = Unsafe.Add(ref selfRef, 12);
+ this.V1R.Y = Unsafe.Add(ref selfRef, 13);
+ this.V1R.Z = Unsafe.Add(ref selfRef, 14);
+ this.V1R.W = Unsafe.Add(ref selfRef, 15);
+
+ this.V2L.X = Unsafe.Add(ref selfRef, 16);
+ this.V2L.Y = Unsafe.Add(ref selfRef, 17);
+ this.V2L.Z = Unsafe.Add(ref selfRef, 18);
+ this.V2L.W = Unsafe.Add(ref selfRef, 19);
+ this.V2R.X = Unsafe.Add(ref selfRef, 20);
+ this.V2R.Y = Unsafe.Add(ref selfRef, 21);
+ this.V2R.Z = Unsafe.Add(ref selfRef, 22);
+ this.V2R.W = Unsafe.Add(ref selfRef, 23);
+
+ this.V3L.X = Unsafe.Add(ref selfRef, 24);
+ this.V3L.Y = Unsafe.Add(ref selfRef, 25);
+ this.V3L.Z = Unsafe.Add(ref selfRef, 26);
+ this.V3L.W = Unsafe.Add(ref selfRef, 27);
+ this.V3R.X = Unsafe.Add(ref selfRef, 28);
+ this.V3R.Y = Unsafe.Add(ref selfRef, 29);
+ this.V3R.Z = Unsafe.Add(ref selfRef, 30);
+ this.V3R.W = Unsafe.Add(ref selfRef, 31);
+
+ this.V4L.X = Unsafe.Add(ref selfRef, 32);
+ this.V4L.Y = Unsafe.Add(ref selfRef, 33);
+ this.V4L.Z = Unsafe.Add(ref selfRef, 34);
+ this.V4L.W = Unsafe.Add(ref selfRef, 35);
+ this.V4R.X = Unsafe.Add(ref selfRef, 36);
+ this.V4R.Y = Unsafe.Add(ref selfRef, 37);
+ this.V4R.Z = Unsafe.Add(ref selfRef, 38);
+ this.V4R.W = Unsafe.Add(ref selfRef, 39);
+
+ this.V5L.X = Unsafe.Add(ref selfRef, 40);
+ this.V5L.Y = Unsafe.Add(ref selfRef, 41);
+ this.V5L.Z = Unsafe.Add(ref selfRef, 42);
+ this.V5L.W = Unsafe.Add(ref selfRef, 43);
+ this.V5R.X = Unsafe.Add(ref selfRef, 44);
+ this.V5R.Y = Unsafe.Add(ref selfRef, 45);
+ this.V5R.Z = Unsafe.Add(ref selfRef, 46);
+ this.V5R.W = Unsafe.Add(ref selfRef, 47);
+
+ this.V6L.X = Unsafe.Add(ref selfRef, 48);
+ this.V6L.Y = Unsafe.Add(ref selfRef, 49);
+ this.V6L.Z = Unsafe.Add(ref selfRef, 50);
+ this.V6L.W = Unsafe.Add(ref selfRef, 51);
+ this.V6R.X = Unsafe.Add(ref selfRef, 52);
+ this.V6R.Y = Unsafe.Add(ref selfRef, 53);
+ this.V6R.Z = Unsafe.Add(ref selfRef, 54);
+ this.V6R.W = Unsafe.Add(ref selfRef, 55);
+
+ this.V7L.X = Unsafe.Add(ref selfRef, 56);
+ this.V7L.Y = Unsafe.Add(ref selfRef, 57);
+ this.V7L.Z = Unsafe.Add(ref selfRef, 58);
+ this.V7L.W = Unsafe.Add(ref selfRef, 59);
+ this.V7R.X = Unsafe.Add(ref selfRef, 60);
+ this.V7R.Y = Unsafe.Add(ref selfRef, 61);
+ this.V7R.Z = Unsafe.Add(ref selfRef, 62);
+ this.V7R.W = Unsafe.Add(ref selfRef, 63);
}
- }
+ }
}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt
index bef3e4914..dc0996b65 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt
+++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt
@@ -61,9 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
///
/// Level shift by +128, clip to [0, 255]
///
- /// The destination block
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
+ public void NormalizeColorsInplace()
{
<#
@@ -74,7 +72,61 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
for (int j = 0; j < 2; j++)
{
char side = j == 0 ? 'L' : 'R';
- Write($"d.V{i}{side} = Vector4.Clamp(V{i}{side} + COff4, CMin4, CMax4);\r\n");
+ Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n");
+ }
+ }
+ PopIndent();
+ #>
+ }
+
+ ///
+ /// AVX2-only variant for executing and in one step.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void NormalizeColorsAndRoundInplaceAvx2()
+ {
+ Vector off = new Vector(128f);
+ Vector max = new Vector(255F);
+ <#
+
+ for (int i = 0; i < 8; i++)
+ {
+ #>
+
+ ref Vector row<#=i#> = ref Unsafe.As>(ref this.V<#=i#>L);
+ row<#=i#> = NormalizeAndRound(row<#=i#>, off, max);
+ <#
+ }
+ #>
+
+ }
+
+ ///
+ /// Fill the block from 'source' doing short -> float conversion.
+ ///
+ public void LoadFrom(ref Block8x8 source)
+ {
+ ref short selfRef = ref Unsafe.As(ref source);
+
+ <#
+ PushIndent(" ");
+ for (int j = 0; j < 8; j++)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ char destCoord = coordz[i % 4];
+ char destSide = (i / 4) % 2 == 0 ? 'L' : 'R';
+
+ if(j > 0 && i == 0){
+ WriteLine("");
+ }
+
+ char srcCoord = coordz[j % 4];
+ char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R';
+
+ string expression = $"this.V{j}{destSide}.{destCoord} = Unsafe.Add(ref selfRef, {j*8+i});\r\n";
+ Write(expression);
+
}
}
PopIndent();
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
index 191fa50ca..2dd42288c 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
@@ -7,7 +7,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
-using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
@@ -17,16 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
///
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
- ///
- public const int VectorCount = 16;
-
///
/// A number of scalar coefficients in a
///
@@ -157,36 +146,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return result;
}
- ///
- /// Pointer-based "Indexer" (getter part)
- ///
- /// Block pointer
- /// Index
- /// The scaleVec value at the specified index
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx)
- {
- GuardBlockIndex(idx);
-
- float* fp = (float*)blockPtr;
- return fp[idx];
- }
-
- ///
- /// Pointer-based "Indexer" (setter part)
- ///
- /// Block pointer
- /// Index
- /// Value
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value)
- {
- GuardBlockIndex(idx);
-
- float* fp = (float*)blockPtr;
- fp[idx] = value;
- }
-
///
/// Fill the block with defaults (zeroes)
///
@@ -242,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
///
/// Destination
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe void CopyTo(Span dest)
+ public void CopyTo(Span dest)
{
ref byte d = ref Unsafe.As(ref dest.DangerousGetPinnableReference());
ref byte s = ref Unsafe.As(ref this);
@@ -306,109 +265,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
}
}
- // [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void CopyTo(BufferArea area)
- {
- ref byte selfBase = ref Unsafe.As(ref this);
- ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo());
- int destStride = area.Stride * sizeof(float);
-
- CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
- CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
- CopyRowImpl(ref selfBase, ref destBase, destStride, 2);
- CopyRowImpl(ref selfBase, ref destBase, destStride, 3);
- CopyRowImpl(ref selfBase, ref destBase, destStride, 4);
- CopyRowImpl(ref selfBase, ref destBase, destStride, 5);
- CopyRowImpl(ref selfBase, ref destBase, destStride, 6);
- CopyRowImpl(ref selfBase, ref destBase, destStride, 7);
- }
-
- public void CopyTo(BufferArea area, int horizontalScale, int verticalScale)
- {
- if (horizontalScale == 1 && verticalScale == 1)
- {
- this.CopyTo(area);
- return;
- }
- else if (horizontalScale == 2 && verticalScale == 2)
- {
- this.CopyTo2x2(area);
- return;
- }
-
- // TODO: Optimize: implement all the cases with loopless special code! (T4?)
- for (int y = 0; y < 8; y++)
- {
- int yy = y * verticalScale;
- int y8 = y * 8;
-
- for (int x = 0; x < 8; x++)
- {
- int xx = x * horizontalScale;
-
- float value = this[y8 + x];
-
- for (int i = 0; i < verticalScale; i++)
- {
- for (int j = 0; j < horizontalScale; j++)
- {
- area[xx + j, yy + i] = value;
- }
- }
- }
- }
- }
-
- private void CopyTo2x2(BufferArea area)
- {
- ref float destBase = ref area.GetReferenceToOrigo();
- int destStride = area.Stride;
-
- this.CopyRow2x2Impl(ref destBase, 0, destStride);
- this.CopyRow2x2Impl(ref destBase, 1, destStride);
- this.CopyRow2x2Impl(ref destBase, 2, destStride);
- this.CopyRow2x2Impl(ref destBase, 3, destStride);
- this.CopyRow2x2Impl(ref destBase, 4, destStride);
- this.CopyRow2x2Impl(ref destBase, 5, destStride);
- this.CopyRow2x2Impl(ref destBase, 6, destStride);
- this.CopyRow2x2Impl(ref destBase, 7, destStride);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row)
- {
- ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float));
- ref byte d = ref Unsafe.Add(ref destBase, row * destStride);
- Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void CopyRow2x2Impl(ref float destBase, int row, int destStride)
- {
- ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row);
- ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1);
- ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride);
-
- Stride2VectorCopyImpl(ref selfLeft, ref destLocalOrigo);
- Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, 8));
-
- Stride2VectorCopyImpl(ref selfLeft, ref Unsafe.Add(ref destLocalOrigo, destStride));
- Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, destStride + 8));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void Stride2VectorCopyImpl(ref Vector4 s, ref float destBase)
- {
- Unsafe.Add(ref destBase, 0) = s.X;
- Unsafe.Add(ref destBase, 1) = s.X;
- Unsafe.Add(ref destBase, 2) = s.Y;
- Unsafe.Add(ref destBase, 3) = s.Y;
- Unsafe.Add(ref destBase, 4) = s.Z;
- Unsafe.Add(ref destBase, 5) = s.Z;
- Unsafe.Add(ref destBase, 6) = s.W;
- Unsafe.Add(ref destBase, 7) = s.W;
- }
-
public float[] ToArray()
{
float[] result = new float[Size];
@@ -419,26 +275,50 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
///
/// Multiply all elements of the block.
///
- /// Vector to multiply by
+ /// The value to multiply by
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void MultiplyAllInplace(float scaleVec)
- {
- 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;
+ public void MultiplyInplace(float value)
+ {
+ this.V0L *= value;
+ this.V0R *= value;
+ this.V1L *= value;
+ this.V1R *= value;
+ this.V2L *= value;
+ this.V2R *= value;
+ this.V3L *= value;
+ this.V3R *= value;
+ this.V4L *= value;
+ this.V4R *= value;
+ this.V5L *= value;
+ this.V5R *= value;
+ this.V6L *= value;
+ this.V6R *= value;
+ this.V7L *= value;
+ this.V7R *= value;
+ }
+
+ ///
+ /// Multiply all elements of the block by the corresponding elements of 'other'
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void MultiplyInplace(ref Block8x8F other)
+ {
+ this.V0L *= other.V0L;
+ this.V0R *= other.V0R;
+ this.V1L *= other.V1L;
+ this.V1R *= other.V1R;
+ this.V2L *= other.V2L;
+ this.V2R *= other.V2R;
+ this.V3L *= other.V3L;
+ this.V3R *= other.V3R;
+ this.V4L *= other.V4L;
+ this.V4R *= other.V4R;
+ this.V5L *= other.V5L;
+ this.V5R *= other.V5R;
+ this.V6L *= other.V6L;
+ this.V6R *= other.V6R;
+ this.V7L *= other.V7L;
+ this.V7R *= other.V7R;
}
///
@@ -472,56 +352,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// Block pointer
/// Qt pointer
/// Unzig pointer
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ // [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
- for (int zig = 0; zig < Size; zig++)
+ for (int qtIndex = 0; qtIndex < Size; qtIndex++)
{
- float* unzigPos = b + unzigPtr[zig];
+ int blockIndex = unzigPtr[qtIndex];
+ float* unzigPos = b + blockIndex;
+
float val = *unzigPos;
- val *= qtp[zig];
+ val *= qtp[qtIndex];
*unzigPos = val;
}
}
///
- /// Level shift by +128, clip to [0, 255], and write to buffer.
- ///
- /// Color buffer
- /// Stride offset
- /// Temp Block pointer
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe void CopyColorsTo(Span destinationBuffer, int stride, Block8x8F* tempBlockPtr)
- {
- this.NormalizeColorsInto(ref *tempBlockPtr);
- ref byte d = ref destinationBuffer.DangerousGetPinnableReference();
- float* src = (float*)tempBlockPtr;
- for (int i = 0; i < 8; i++)
- {
- ref byte dRow = ref Unsafe.Add(ref d, i * stride);
- Unsafe.Add(ref dRow, 0) = (byte)src[0];
- Unsafe.Add(ref dRow, 1) = (byte)src[1];
- Unsafe.Add(ref dRow, 2) = (byte)src[2];
- Unsafe.Add(ref dRow, 3) = (byte)src[3];
- Unsafe.Add(ref dRow, 4) = (byte)src[4];
- Unsafe.Add(ref dRow, 5) = (byte)src[5];
- Unsafe.Add(ref dRow, 6) = (byte)src[6];
- Unsafe.Add(ref dRow, 7) = (byte)src[7];
- src += 8;
- }
- }
-
- ///
+ /// Quantize 'block' into 'dest' using the 'qt' quantization table:
/// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values.
/// To finish the rounding it's enough to (int)-cast these values.
///
/// Source block
/// Destination block
/// The quantization table
- /// Pointer to elements of
- public static unsafe void UnzigDivRound(
+ /// Pointer to elements of
+ public static unsafe void Quantize(
Block8x8F* block,
Block8x8F* dest,
Block8x8F* qt,
@@ -610,34 +466,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return result;
}
- public void RoundInplace()
+ ///
+ /// Level shift by +128, clip to [0..255], and round all the values in the block.
+ ///
+ public void NormalizeColorsAndRoundInplace()
{
- if (Vector.Count == 8 && Vector.Count == 8)
+ if (SimdUtils.IsAvx2CompatibleArchitecture)
{
- ref Vector row0 = ref Unsafe.As>(ref this.V0L);
- row0 = row0.FastRound();
- ref Vector row1 = ref Unsafe.As>(ref this.V1L);
- row1 = row1.FastRound();
- ref Vector row2 = ref Unsafe.As>(ref this.V2L);
- row2 = row2.FastRound();
- ref Vector row3 = ref Unsafe.As>(ref this.V3L);
- row3 = row3.FastRound();
- ref Vector row4 = ref Unsafe.As>(ref this.V4L);
- row4 = row4.FastRound();
- ref Vector row5 = ref Unsafe.As>(ref this.V5L);
- row5 = row5.FastRound();
- ref Vector row6 = ref Unsafe.As>(ref this.V6L);
- row6 = row6.FastRound();
- ref Vector row7 = ref Unsafe.As>(ref this.V7L);
- row7 = row7.FastRound();
+ this.NormalizeColorsAndRoundInplaceAvx2();
}
else
{
- this.RoundInplaceSlow();
+ this.NormalizeColorsInplace();
+ this.RoundInplace();
}
}
- private void RoundInplaceSlow()
+ ///
+ /// Rounds all values in the block.
+ ///
+ public void RoundInplace()
{
for (int i = 0; i < Size; i++)
{
@@ -663,6 +511,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return bld.ToString();
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector NormalizeAndRound(Vector row, Vector off, Vector max)
+ {
+ row += off;
+ row = Vector.Max(row, Vector.Zero);
+ row = Vector.Min(row, max);
+ return row.FastRound();
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
@@ -679,10 +536,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
DebugGuard.MustBeLessThan(idx, Size, nameof(idx));
DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx));
}
-
- [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))]
- private struct Row
- {
- }
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
index 4c4701753..574967b6b 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
@@ -11,145 +11,72 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// Encapsulates the implementation of processing "raw" -s into Jpeg image channels.
///
[StructLayout(LayoutKind.Sequential)]
- internal unsafe struct JpegBlockPostProcessor
+ internal struct JpegBlockPostProcessor
{
///
- /// The
+ /// Source block
///
- private ComputationData data;
+ public Block8x8F SourceBlock;
///
- /// Pointers to elements of
+ /// Temporal block 1 to store intermediate and/or final computation results
///
- private DataPointers pointers;
+ public Block8x8F WorkspaceBlock1;
///
- /// Initialize the instance on the stack.
+ /// Temporal block 2 to store intermediate and/or final computation results
///
- /// The instance
- public static void Init(JpegBlockPostProcessor* postProcessor)
- {
- postProcessor->data = ComputationData.Create();
- postProcessor->pointers = new DataPointers(&postProcessor->data);
- }
-
- public void QuantizeAndTransform(IRawJpegData decoder, IJpegComponent component, ref Block8x8 sourceBlock)
- {
- this.data.SourceBlock = sourceBlock.AsFloatBlock();
- int qtIndex = component.QuantizationTableIndex;
- this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex];
-
- Block8x8F* b = this.pointers.SourceBlock;
-
- Block8x8F.DequantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig);
-
- FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2);
- }
+ public Block8x8F WorkspaceBlock2;
- public void ProcessBlockColorsInto(
- IRawJpegData decoder,
- IJpegComponent component,
- ref Block8x8 sourceBlock,
- BufferArea destArea)
- {
- this.QuantizeAndTransform(decoder, component, ref sourceBlock);
-
- this.data.WorkspaceBlock1.NormalizeColorsInplace();
-
- // To conform better to libjpeg we actually NEED TO loose precision here.
- // This is because they store blocks as Int16 between all the operations.
- // Unfortunately, we need to emulate this to be "more accurate" :(
- this.data.WorkspaceBlock1.RoundInplace();
+ ///
+ /// The quantization table as
+ ///
+ public Block8x8F DequantiazationTable;
- Size divs = component.SubSamplingDivisors;
- this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height);
- }
+ ///
+ /// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block.
+ ///
+ private Size subSamplingDivisors;
///
- /// Holds the "large" data blocks needed for computations.
+ /// Initializes a new instance of the struct.
///
- [StructLayout(LayoutKind.Sequential)]
- public struct ComputationData
+ public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{
- ///
- /// Source block
- ///
- public Block8x8F SourceBlock;
-
- ///
- /// Temporal block 1 to store intermediate and/or final computation results
- ///
- public Block8x8F WorkspaceBlock1;
-
- ///
- /// Temporal block 2 to store intermediate and/or final computation results
- ///
- public Block8x8F WorkspaceBlock2;
-
- ///
- /// The quantization table as
- ///
- public Block8x8F QuantiazationTable;
-
- ///
- /// The jpeg unzig data
- ///
- public UnzigData Unzig;
+ int qtIndex = component.QuantizationTableIndex;
+ this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]);
+ this.subSamplingDivisors = component.SubSamplingDivisors;
- ///
- /// Creates and initializes a new instance
- ///
- /// The
- public static ComputationData Create()
- {
- var data = default(ComputationData);
- data.Unzig = UnzigData.Create();
- return data;
- }
+ this.SourceBlock = default(Block8x8F);
+ this.WorkspaceBlock1 = default(Block8x8F);
+ this.WorkspaceBlock2 = default(Block8x8F);
}
///
- /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of
+ /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values:
+ /// - Dequantize
+ /// - Applying IDCT
+ /// - Level shift by +128, clip to [0, 255]
+ /// - Copy the resultin color values into 'destArea' scaling up the block by amount defined in
///
- public struct DataPointers
+ public void ProcessBlockColorsInto(
+ ref Block8x8 sourceBlock,
+ BufferArea destArea)
{
- ///
- /// Pointer to
- ///
- public Block8x8F* SourceBlock;
-
- ///
- /// Pointer to
- ///
- public Block8x8F* WorkspaceBlock1;
+ ref Block8x8F b = ref this.SourceBlock;
+ b.LoadFrom(ref sourceBlock);
- ///
- /// Pointer to
- ///
- public Block8x8F* WorkspaceBlock2;
+ // Dequantize:
+ b.MultiplyInplace(ref this.DequantiazationTable);
- ///
- /// Pointer to
- ///
- public Block8x8F* QuantiazationTable;
+ FastFloatingPointDCT.TransformIDCT(ref b, ref this.WorkspaceBlock1, ref this.WorkspaceBlock2);
- ///
- /// Pointer to as int*
- ///
- public int* Unzig;
+ // To conform better to libjpeg we actually NEED TO loose precision here.
+ // This is because they store blocks as Int16 between all the operations.
+ // To be "more accurate", we need to emulate this by rounding!
+ this.WorkspaceBlock1.NormalizeColorsAndRoundInplace();
- ///
- /// Initializes a new instance of the struct.
- ///
- /// Pointer to
- internal DataPointers(ComputationData* dataPtr)
- {
- this.SourceBlock = &dataPtr->SourceBlock;
- this.WorkspaceBlock1 = &dataPtr->WorkspaceBlock1;
- this.WorkspaceBlock2 = &dataPtr->WorkspaceBlock2;
- this.QuantiazationTable = &dataPtr->QuantiazationTable;
- this.Unzig = dataPtr->Unzig.Data;
- }
+ this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
index 059b2e89a..b988695e1 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs
@@ -1,6 +1,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Common.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
@@ -121,9 +122,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
tmp.MultiplyInplace(1.772F);
b.AddInplace(ref tmp);
- r.RoundAndDownscale();
- g.RoundAndDownscale();
- b.RoundAndDownscale();
+ if (Vector.Count == 4)
+ {
+ // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?)
+ r.RoundAndDownscalePreAvx2();
+ g.RoundAndDownscalePreAvx2();
+ b.RoundAndDownscalePreAvx2();
+ }
+ else if (SimdUtils.IsAvx2CompatibleArchitecture)
+ {
+ r.RoundAndDownscaleAvx2();
+ g.RoundAndDownscaleAvx2();
+ b.RoundAndDownscaleAvx2();
+ }
+ else
+ {
+ // TODO: Run fallback scalar code here
+ // However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007
+ throw new NotImplementedException("Your CPU architecture is too modern!");
+ }
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
@@ -131,53 +148,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
}
}
- ///
- /// Its faster to process multiple Vector4-s together
- ///
- private struct Vector4Pair
- {
- public Vector4 A;
-
- public Vector4 B;
-
- private static readonly Vector4 Scale = new Vector4(1 / 255f);
-
- private static readonly Vector4 Half = new Vector4(0.5f);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void RoundAndDownscale()
- {
- // Emulate rounding:
- this.A += Half;
- this.B += Half;
-
- // Downscale by 1/255
- this.A *= Scale;
- this.B *= Scale;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void MultiplyInplace(float value)
- {
- this.A *= value;
- this.B *= value;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddInplace(Vector4 value)
- {
- this.A += value;
- this.B += value;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddInplace(ref Vector4Pair other)
- {
- this.A += other.A;
- this.B += other.B;
- }
- }
-
private struct Vector4Octet
{
#pragma warning disable SA1132 // Do not combine fields
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs
index feb5164d7..87c1431e0 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs
@@ -66,10 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
///
/// Invoke for block rows, copy the result into .
///
- public unsafe void CopyBlocksToColorBuffer()
+ public void CopyBlocksToColorBuffer()
{
- var blockPp = default(JpegBlockPostProcessor);
- JpegBlockPostProcessor.Init(&blockPp);
+ var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component);
for (int y = 0; y < this.BlockRowsPerStep; y++)
{
@@ -95,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
this.blockAreaSize.Width,
this.blockAreaSize.Height);
- blockPp.ProcessBlockColorsInto(this.ImagePostProcessor.RawJpeg, this.Component, ref block, destArea);
+ blockPp.ProcessBlockColorsInto(ref block, destArea);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs
index 8a4f56e3d..3ee6e72c5 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs
@@ -50,7 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
/// Temporary block provided by the caller
public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp)
{
+ // TODO: Transpose is a bottleneck now. We need full AVX support to optimize it:
+ // https://github.com/dotnet/corefx/issues/22940
src.TransposeInto(ref temp);
+
IDCT8x4_LeftPart(ref temp, ref dest);
IDCT8x4_RightPart(ref temp, ref dest);
@@ -60,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
IDCT8x4_RightPart(ref temp, ref dest);
// TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing?
- dest.MultiplyAllInplace(C_0_125);
+ dest.MultiplyInplace(C_0_125);
}
///
@@ -334,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
- dest.MultiplyAllInplace(C_0_125);
+ dest.MultiplyInplace(C_0_125);
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs b/src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs
similarity index 70%
rename from src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs
rename to src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs
index e243938e3..18270f5ba 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs
@@ -6,13 +6,12 @@ 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
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
///
- internal unsafe struct UnzigData
+ internal unsafe struct ZigZag
{
///
/// Copy of in a value type
@@ -33,15 +32,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
};
///
- /// Creates and fills an instance of with Jpeg unzig indices
+ /// Creates and fills an instance of with Jpeg unzig indices
///
/// The new instance
- public static UnzigData Create()
+ public static ZigZag CreateUnzigTable()
{
- UnzigData result = default(UnzigData);
+ ZigZag result = default(ZigZag);
int* unzigPtr = result.Data;
Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
return result;
}
+
+ ///
+ /// Apply Zigging to the given quantization table, so it will be sufficient to multiply blocks for dequantizing them.
+ ///
+ public static Block8x8F CreateDequantizationTable(ref Block8x8F qt)
+ {
+ Block8x8F result = default(Block8x8F);
+
+ for (int i = 0; i < 64; i++)
+ {
+ result[Unzig[i]] = qt[i];
+ }
+
+ return result;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs
index 6252d8209..d426ed4f1 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs
@@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
///
/// The jpeg unzig data
///
- public UnzigData Unzig;
+ public ZigZag Unzig;
///
/// The buffer storing the -s for each component
@@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public static ComputationData Create()
{
ComputationData data = default(ComputationData);
- data.Unzig = UnzigData.Create();
+ data.Unzig = ZigZag.CreateUnzigTable();
return data;
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
index 984fb828c..2deb3f62d 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs
@@ -455,7 +455,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
- UnzigData unzig = UnzigData.Create();
+ ZigZag unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@@ -564,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2);
- Block8x8F.UnzigDivRound(tempDest1, tempDest2, quant, unzigPtr);
+ Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr);
float* unziggedDestPtr = (float*)tempDest2;
int dc = (int)unziggedDestPtr[0];
@@ -912,7 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
- UnzigData unzig = UnzigData.Create();
+ ZigZag unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
index e5f2fd5e7..15a794144 100644
--- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
+++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
@@ -8,6 +8,8 @@ namespace SixLabors.ImageSharp.Tests.Common
using System.Linq;
using System.Runtime.CompilerServices;
+ using SixLabors.ImageSharp.Common.Tuples;
+
using Xunit.Abstractions;
using Xunit.Sdk;
@@ -243,15 +245,15 @@ namespace SixLabors.ImageSharp.Tests.Common
x = (x * scale) + magick;
- SimdUtils.Octet.OfUInt32 ii = default(SimdUtils.Octet.OfUInt32);
+ Tuple8.OfUInt32 ii = default(Tuple8.OfUInt32);
- ref Vector iiRef = ref Unsafe.As>(ref ii);
+ ref Vector iiRef = ref Unsafe.As>(ref ii);
iiRef = x;
- //SimdUtils.Octet.OfUInt32 ii = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x);
+ //Tuple8.OfUInt32 ii = Unsafe.As, Tuple8.OfUInt32>(ref x);
- ref SimdUtils.Octet.OfByte d = ref dest.NonPortableCast()[0];
+ ref Tuple8.OfByte d = ref dest.NonPortableCast()[0];
d.LoadFrom(ref ii);
this.Output.WriteLine(ii.ToString());
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
index 52c38bee8..bf5507676 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
@@ -32,32 +32,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
}
- [Fact]
- public void Indexer()
+ private bool SkipOnNonAvx2Runner()
{
- float sum = 0;
- this.Measure(
- Times,
- () =>
- {
- var block = new Block8x8F();
-
- for (int i = 0; i < Block8x8F.Size; i++)
- {
- block[i] = i;
- }
-
- sum = 0;
- for (int i = 0; i < Block8x8F.Size; i++)
- {
- sum += block[i];
- }
- });
- Assert.Equal(sum, 64f * 63f * 0.5f);
+ if (!SimdUtils.IsAvx2CompatibleArchitecture)
+ {
+ this.Output.WriteLine("AVX2 not supported, skipping!");
+ return true;
+ }
+ return false;
}
[Fact]
- public unsafe void Indexer_GetScalarAt_SetScalarAt()
+ public void Indexer()
{
float sum = 0;
this.Measure(
@@ -68,18 +54,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
for (int i = 0; i < Block8x8F.Size; i++)
{
- Block8x8F.SetScalarAt(&block, i, i);
+ block[i] = i;
}
sum = 0;
for (int i = 0; i < Block8x8F.Size; i++)
{
- sum += Block8x8F.GetScalarAt(&block, i);
+ sum += block[i];
}
});
Assert.Equal(sum, 64f * 63f * 0.5f);
}
-
+
[Fact]
public void Indexer_ReferenceBenchmarkWithArray()
{
@@ -222,33 +208,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms");
}
- [Fact]
- public unsafe void CopyColorsTo()
- {
- float[] data = Create8x8FloatData();
- var block = new Block8x8F();
- block.LoadFrom(data);
- block.MultiplyAllInplace(5);
-
- int stride = 256;
- int height = 42;
- int offset = height * 10 + 20;
-
- byte[] colorsExpected = new byte[stride * height];
- byte[] colorsActual = new byte[stride * height];
-
- var temp = new Block8x8F();
-
- ReferenceImplementations.CopyColorsTo(ref block, new Span(colorsExpected, offset), stride);
-
- block.CopyColorsTo(new Span(colorsActual, offset), stride, &temp);
-
- // Output.WriteLine("******* EXPECTED: *********");
- // PrintLinearData(colorsExpected);
- // Output.WriteLine("******** ACTUAL: **********");
- Assert.Equal(colorsExpected, colorsActual);
- }
-
private static float[] Create8x8ColorCropTestData()
{
float[] result = new float[64];
@@ -263,30 +222,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return result;
}
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public void NormalizeColors(bool inplace)
+ [Fact]
+ public void NormalizeColors()
{
var block = default(Block8x8F);
float[] input = Create8x8ColorCropTestData();
block.LoadFrom(input);
this.Output.WriteLine("Input:");
this.PrintLinearData(input);
-
- var dest = default(Block8x8F);
- if (inplace)
- {
- dest = block;
- dest.NormalizeColorsInplace();
- }
- else
- {
- block.NormalizeColorsInto(ref dest);
- }
+ Block8x8F dest = block;
+ dest.NormalizeColorsInplace();
-
float[] array = new float[64];
dest.CopyTo(array);
this.Output.WriteLine("Result:");
@@ -297,11 +244,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ public void NormalizeColorsAndRoundAvx2(int seed)
+ {
+ if (this.SkipOnNonAvx2Runner())
+ {
+ return;
+ }
+
+ Block8x8F source = CreateRandomFloatBlock(-200, 200, seed);
+
+ Block8x8F expected = source;
+ expected.NormalizeColorsInplace();
+ expected.RoundInplace();
+
+ Block8x8F actual = source;
+ actual.NormalizeColorsAndRoundInplaceAvx2();
+
+ this.Output.WriteLine(expected.ToString());
+ this.Output.WriteLine(actual.ToString());
+ this.CompareBlocks(expected, actual, 0);
+ }
+
[Theory]
[InlineData(1)]
[InlineData(2)]
- public unsafe void UnzigDivRound(int seed)
+ public unsafe void Quantize(int seed)
{
var block = new Block8x8F();
block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed));
@@ -309,14 +280,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var qt = new Block8x8F();
qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed));
- var unzig = UnzigData.Create();
+ var unzig = ZigZag.CreateUnzigTable();
int* expectedResults = stackalloc int[Block8x8F.Size];
- ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data);
+ ReferenceImplementations.QuantizeRational(&block, expectedResults, &qt, unzig.Data);
var actualResults = default(Block8x8F);
- Block8x8F.UnzigDivRound(&block, &actualResults, &qt, unzig.Data);
+ Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data);
for (int i = 0; i < Block8x8F.Size; i++)
{
@@ -352,7 +323,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
- public void RoundInplace(int seed)
+ public void RoundInplaceSlow(int seed)
{
Block8x8F s = CreateRandomFloatBlock(-500, 500, seed);
@@ -370,5 +341,77 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expected, actual);
}
}
+
+ [Fact]
+ public void MultiplyInplace_ByOtherBlock()
+ {
+ Block8x8F original = CreateRandomFloatBlock(-500, 500, 42);
+ Block8x8F m = CreateRandomFloatBlock(-500, 500, 42);
+
+ Block8x8F actual = original;
+
+ actual.MultiplyInplace(ref m);
+
+ for (int i = 0; i < Block8x8F.Size; i++)
+ {
+ Assert.Equal(original[i]*m[i], actual[i]);
+ }
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ public unsafe void DequantizeBlock(int seed)
+ {
+ Block8x8F original = CreateRandomFloatBlock(-500, 500, seed);
+ Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42);
+
+ var unzig = ZigZag.CreateUnzigTable();
+
+ Block8x8F expected = original;
+ Block8x8F actual = original;
+
+ ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data);
+ Block8x8F.DequantizeBlock(&actual, &qt, unzig.Data);
+
+ this.CompareBlocks(expected, actual, 0);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(3)]
+ public unsafe void ZigZag_CreateDequantizationTable_MultiplicationShouldQuantize(int seed)
+ {
+ Block8x8F original = CreateRandomFloatBlock(-500, 500, seed);
+ Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42);
+
+ var unzig = ZigZag.CreateUnzigTable();
+ Block8x8F zigQt = ZigZag.CreateDequantizationTable(ref qt);
+
+ Block8x8F expected = original;
+ Block8x8F actual = original;
+
+ ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data);
+
+ actual.MultiplyInplace(ref zigQt);
+
+ this.CompareBlocks(expected, actual, 0);
+ }
+
+ [Fact]
+ public void MultiplyInplace_ByScalar()
+ {
+ Block8x8F original = CreateRandomFloatBlock(-500, 500);
+
+ Block8x8F actual = original;
+ actual.MultiplyInplace(42f);
+
+ for (int i = 0; i < 64; i++)
+ {
+ Assert.Equal(original[i]*42f, actual[i]);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
index 706fa1e20..0197dd917 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
@@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public class JpegColorConverterTests
{
- private const float Precision = 1/255f;
+ private const float Precision = 0.1f / 255;
public static readonly TheoryData CommonConversionData =
new TheoryData
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index d8d24224b..e26ab2cc9 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
@@ -9,6 +9,7 @@ using System;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
+ using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -24,6 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using Xunit;
using Xunit.Abstractions;
+ // TODO: Scatter test cases into multiple test classes
public class JpegDecoderTests
{
public static string[] BaselineTestJpegs =
@@ -50,16 +52,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159,
};
+ private static readonly Dictionary CustomToleranceValues = new Dictionary
+ {
+ // Baseline:
+ [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100,
+ [TestImages.Jpeg.Baseline.Bad.ExifUndefType] = 0.011f / 100,
+ [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100,
+ [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100,
+
+ // Progressive:
+ [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100,
+ [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100,
+ [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100,
+ [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100,
+ [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100,
+ [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100,
+ };
+
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector;
- // TODO: We should make this comparer less tolerant ...
- private static readonly ImageComparer VeryTolerantJpegComparer =
- ImageComparer.Tolerant(0.005f, perPixelManhattanThreshold: 4);
+ private const float BaselineTolerance_Orig = 0.001f / 100;
+ private const float BaselineTolerance_PdfJs = 0.005f;
- // BUG: PDF.js output is wrong on spectral level!
- private static readonly ImageComparer PdfJsProgressiveComparer =
- ImageComparer.Tolerant(0.015f, perPixelManhattanThreshold: 4);
+ private const float ProgressiveTolerance_Orig = 0.2f / 100;
+ private const float ProgressiveTolerance_PdfJs = 1.5f / 100; // PDF.js Progressive output is wrong on spectral level!
+ private ImageComparer GetImageComparerForOrigDecoder(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ string file = provider.SourceFileOrDescription;
+
+ if (!CustomToleranceValues.TryGetValue(file, out float tolerance))
+ {
+ tolerance = file.ToLower().Contains("baseline") ? BaselineTolerance_Orig : ProgressiveTolerance_Orig;
+ }
+
+ return ImageComparer.Tolerant(tolerance);
+ }
public JpegDecoderTests(ITestOutputHelper output)
{
@@ -71,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static IImageDecoder OrigJpegDecoder => new OrigJpegDecoder();
private static IImageDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder();
-
+
[Fact]
public void ParseStream_BasicPropertiesAreCorrect1_PdfJs()
{
@@ -84,50 +113,56 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31);
}
}
-
+
public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg";
+
[Theory]
- [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
- public void DecodeBaselineJpeg_PdfJs(TestImageProvider provider)
+ [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)]
+ [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)]
+ public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider, bool useOldDecoder)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage(PdfJsJpegDecoder))
+ IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder;
+ using (Image image = provider.GetImage(decoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
- image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(provider, ImageComparer.Tolerant(BaselineTolerance_PdfJs), appendPixelTypeToFileName: false);
}
}
[Theory]
- [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)]
- [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)]
- public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider, bool useOldDecoder)
+ [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
+ public void DecodeBaselineJpeg_Orig(TestImageProvider provider)
where TPixel : struct, IPixel
{
- IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder;
- using (Image image = provider.GetImage(decoder))
+ using (Image image = provider.GetImage(OrigJpegDecoder))
{
image.DebugSave(provider);
-
provider.Utility.TestName = DecodeBaselineJpegOutputName;
- image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(
+ provider,
+ this.GetImageComparerForOrigDecoder(provider),
+ appendPixelTypeToFileName: false);
}
}
-
+
[Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
- public void DecodeBaselineJpeg_Orig(TestImageProvider provider)
+ public void DecodeBaselineJpeg_PdfJs(TestImageProvider provider)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage(OrigJpegDecoder))
+ using (Image image = provider.GetImage(PdfJsJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
- image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(
+ provider,
+ ImageComparer.Tolerant(BaselineTolerance_PdfJs),
+ appendPixelTypeToFileName: false);
}
}
@@ -144,37 +179,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)]
- public void DecodeProgressiveJpeg_PdfJs(TestImageProvider provider)
+ public void DecodeProgressiveJpeg_Orig(TestImageProvider provider)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage(PdfJsJpegDecoder))
+ using (Image image = provider.GetImage(OrigJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeProgressiveJpegOutputName;
- image.CompareToReferenceOutput(provider, PdfJsProgressiveComparer, appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(
+ provider,
+ this.GetImageComparerForOrigDecoder(provider),
+ appendPixelTypeToFileName: false);
}
}
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)]
- public void DecodeProgressiveJpeg_Orig(TestImageProvider provider)
+ public void DecodeProgressiveJpeg_PdfJs(TestImageProvider provider)
where TPixel : struct, IPixel
{
- using (Image image = provider.GetImage(OrigJpegDecoder))
+ using (Image image = provider.GetImage(PdfJsJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeProgressiveJpegOutputName;
- image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false);
+ image.CompareToReferenceOutput(
+ provider,
+ ImageComparer.Tolerant(ProgressiveTolerance_PdfJs),
+ appendPixelTypeToFileName: false);
}
}
-
- private float GetDifferenceInPercents(Image image, TestImageProvider provider)
+
+ private string GetDifferenceInPercentageString(Image image, TestImageProvider provider)
where TPixel : struct, IPixel
{
var reportingComparer = ImageComparer.Tolerant(0, 0);
-
+
ImageSimilarityReport report = image.GetReferenceOutputSimilarityReports(
provider,
reportingComparer,
@@ -183,10 +224,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (report != null && report.TotalNormalizedDifference.HasValue)
{
- return report.TotalNormalizedDifference.Value * 100;
+ return report.DifferencePercentageString;
}
- return 0;
+ return "0%";
}
private void CompareJpegDecodersImpl(TestImageProvider provider, string testName)
@@ -202,14 +243,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image image = provider.GetImage(OrigJpegDecoder))
{
- double d = this.GetDifferenceInPercents(image, provider);
- this.Output.WriteLine($"Difference using ORIGINAL decoder: {d:0.0000}%");
+ string d = this.GetDifferenceInPercentageString(image, provider);
+
+ this.Output.WriteLine($"Difference using ORIGINAL decoder: {d}");
}
using (Image image = provider.GetImage(PdfJsJpegDecoder))
{
- double d = this.GetDifferenceInPercents(image, provider);
- this.Output.WriteLine($"Difference using PDFJS decoder: {d:0.0000}%");
+ string d = this.GetDifferenceInPercentageString(image, provider);
+ this.Output.WriteLine($"Difference using PDFJS decoder: {d}");
}
}
@@ -228,7 +270,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
this.CompareJpegDecodersImpl(provider, DecodeProgressiveJpegOutputName);
}
-
+
[Theory]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 75)]
[WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 100)]
@@ -256,7 +298,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var mirror = Image.Load(data, OrigJpegDecoder);
mirror.DebugSave(provider, $"_{subsample}_Q{quality}");
}
-
+
[Fact]
public void Decoder_Reads_Correct_Resolution_From_Jfif()
{
@@ -314,7 +356,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// into "\tests\Images\ActualOutput\JpegDecoderTests\"
//[Theory]
//[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")]
- public void ValidateProgressivePdfJsOutput(TestImageProvider provider,
+ public void ValidateProgressivePdfJsOutput(TestImageProvider provider,
string pdfJsOriginalResultImage)
where TPixel : struct, IPixel
{
@@ -335,7 +377,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult);
ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult);
-
+
this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}");
this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}");
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
index a228bc236..baa31f674 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
@@ -34,8 +34,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Jpeg444,
};
- // [Theory] // Benchmark, enable manually
- // [MemberData(nameof(DecodeJpegData))]
+ //[Theory] // Benchmark, enable manually
+ //[MemberData(nameof(DecodeJpegData))]
public void DecodeJpeg_Original(string fileName)
{
this.DecodeJpegBenchmarkImpl(fileName, new OrigJpegDecoder());
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs
index c8240bf08..92ead8164 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs
@@ -19,6 +19,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
///
internal static partial class ReferenceImplementations
{
+ public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr)
+ {
+ float* b = (float*)blockPtr;
+ float* qtp = (float*)qtPtr;
+ for (int qtIndex = 0; qtIndex < Block8x8F.Size; qtIndex++)
+ {
+ int i = unzigPtr[qtIndex];
+ float* unzigPos = b + i;
+
+ float val = *unzigPos;
+ val *= qtp[qtIndex];
+ *unzigPos = val;
+ }
+ }
+
///
/// Transpose 8x8 block stored linearly in a (inplace)
///
@@ -93,14 +108,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
}
///
- /// Reference implementation to test .
+ /// Reference implementation to test .
/// Rounding is done used an integer-based algorithm defined in .
///
/// The input block
/// The destination block of integers
/// The quantization table
- /// Pointer to
- public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr)
+ /// Pointer to
+ public static unsafe void QuantizeRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr)
{
float* s = (float*)src;
float* q = (float*)qt;
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
index 820ca8356..ed643c820 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
@@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
SizeF newSize = image.Size() * ratio;
image.Mutate(x => x.Resize((Size)newSize, sampler, false));
- string details = $"{name}-{ratio}";
+ string details = $"{name}-{ratio.ToString(System.Globalization.CultureInfo.InvariantCulture)}";
image.DebugSave(provider, details);
image.CompareToReferenceOutput(provider, details);
diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
index 6b3f6ccd2..70d4df273 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
@@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests
{
float d = x - y;
- return d > -this.Eps && d < this.Eps;
+ return d >= -this.Eps && d <= this.Eps;
}
public int GetHashCode(float obj)
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs
index 78e390bbd..9501a6c88 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs
@@ -26,9 +26,24 @@
// TODO: This should not be a nullable value!
public float? TotalNormalizedDifference { get; }
- public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue
- ? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"
- : "?";
+ public string DifferencePercentageString
+ {
+ get
+ {
+ if (!this.TotalNormalizedDifference.HasValue)
+ {
+ return "?";
+ }
+ else if (this.TotalNormalizedDifference == 0)
+ {
+ return "0%";
+ }
+ else
+ {
+ return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%";
+ }
+ }
+ }
public PixelDifference[] Differences { get; }
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
index 8ff532b2f..92332eba0 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
@@ -105,15 +105,20 @@ namespace SixLabors.ImageSharp.Tests
private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>();
- public FileProvider(string filePath)
+ // Needed for deserialization!
+ // ReSharper disable once UnusedMember.Local
+ public FileProvider()
{
- this.FilePath = filePath;
}
- public FileProvider()
+ public FileProvider(string filePath)
{
+ this.FilePath = filePath;
}
-
+
+ ///
+ /// Gets the file path relative to the "~/tests/images" folder
+ ///
public string FilePath { get; private set; }
public override string SourceFileOrDescription => this.FilePath;
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
index f4faa9456..7fd7a59a9 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
@@ -89,6 +89,8 @@ namespace SixLabors.ImageSharp.Tests
return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}";
}
+ private static string Inv(FormattableString formattable) => System.FormattableString.Invariant(formattable);
+
///
/// Gets the recommended file name for the output of the test
///
@@ -111,13 +113,17 @@ namespace SixLabors.ImageSharp.Tests
TypeInfo info = type.GetTypeInfo();
if (info.IsPrimitive || info.IsEnum || type == typeof(decimal))
{
- detailsString = testOutputDetails.ToString();
+ detailsString = Inv($"{testOutputDetails}");
}
else
{
IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties();
- detailsString = String.Join("_", properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)).Select(x => $"{x.Key}-{x.Value}"));
+ detailsString = string.Join(
+ "_",
+ properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails))
+ .Select(x => Inv($"{x.Key}-{x.Value}"))
+ );
}
}
return this.GetTestOutputFileNameImpl(extension, detailsString, appendPixelTypeToFileName);