diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs
index 0455fd26a..bf7869e53 100644
--- a/src/ImageSharp/Color/Color.Conversions.cs
+++ b/src/ImageSharp/Color/Color.Conversions.cs
@@ -17,56 +17,118 @@ namespace SixLabors.ImageSharp
///
/// The containing the color information.
[MethodImpl(InliningOptions.ShortMethod)]
- public Color(Rgba64 pixel) => this.data = pixel;
+ public Color(Rgba64 pixel)
+ {
+ this.data = pixel;
+ this.boxedHighPrecisionPixel = null;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The containing the color information.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Color(Rgb48 pixel)
+ {
+ this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue);
+ this.boxedHighPrecisionPixel = null;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The containing the color information.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Color(La32 pixel)
+ {
+ this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A);
+ this.boxedHighPrecisionPixel = null;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The containing the color information.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public Color(L16 pixel)
+ {
+ this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue);
+ this.boxedHighPrecisionPixel = null;
+ }
///
/// Initializes a new instance of the struct.
///
/// The containing the color information.
[MethodImpl(InliningOptions.ShortMethod)]
- public Color(Rgba32 pixel) => this.data = new Rgba64(pixel);
+ public Color(Rgba32 pixel)
+ {
+ this.data = new Rgba64(pixel);
+ this.boxedHighPrecisionPixel = null;
+ }
///
/// Initializes a new instance of the struct.
///
/// The containing the color information.
[MethodImpl(InliningOptions.ShortMethod)]
- public Color(Argb32 pixel) => this.data = new Rgba64(pixel);
+ public Color(Argb32 pixel)
+ {
+ this.data = new Rgba64(pixel);
+ this.boxedHighPrecisionPixel = null;
+ }
///
/// Initializes a new instance of the struct.
///
/// The containing the color information.
[MethodImpl(InliningOptions.ShortMethod)]
- public Color(Bgra32 pixel) => this.data = new Rgba64(pixel);
+ public Color(Bgra32 pixel)
+ {
+ this.data = new Rgba64(pixel);
+ this.boxedHighPrecisionPixel = null;
+ }
///
/// Initializes a new instance of the struct.
///
/// The containing the color information.
[MethodImpl(InliningOptions.ShortMethod)]
- public Color(Rgb24 pixel) => this.data = new Rgba64(pixel);
+ public Color(Rgb24 pixel)
+ {
+ this.data = new Rgba64(pixel);
+ this.boxedHighPrecisionPixel = null;
+ }
///
/// Initializes a new instance of the struct.
///
/// The containing the color information.
[MethodImpl(InliningOptions.ShortMethod)]
- public Color(Bgr24 pixel) => this.data = new Rgba64(pixel);
+ public Color(Bgr24 pixel)
+ {
+ this.data = new Rgba64(pixel);
+ this.boxedHighPrecisionPixel = null;
+ }
///
/// Initializes a new instance of the struct.
///
/// The containing the color information.
[MethodImpl(InliningOptions.ShortMethod)]
- public Color(Vector4 vector) => this.data = new Rgba64(vector);
+ public Color(Vector4 vector)
+ {
+ vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
+ this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W);
+ this.data = default;
+ }
///
/// Converts a to .
///
/// The .
/// The .
- public static explicit operator Vector4(Color color) => color.data.ToVector4();
+ public static explicit operator Vector4(Color color) => color.ToVector4();
///
/// Converts an to .
@@ -74,24 +136,82 @@ namespace SixLabors.ImageSharp
/// The .
/// The .
[MethodImpl(InliningOptions.ShortMethod)]
- public static explicit operator Color(Vector4 source) => new Color(source);
+ public static explicit operator Color(Vector4 source) => new(source);
[MethodImpl(InliningOptions.ShortMethod)]
- internal Rgba32 ToRgba32() => this.data.ToRgba32();
+ internal Rgba32 ToRgba32()
+ {
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data.ToRgba32();
+ }
+
+ Rgba32 value = default;
+ this.boxedHighPrecisionPixel.ToRgba32(ref value);
+ return value;
+ }
[MethodImpl(InliningOptions.ShortMethod)]
- internal Bgra32 ToBgra32() => this.data.ToBgra32();
+ internal Bgra32 ToBgra32()
+ {
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data.ToBgra32();
+ }
+
+ Bgra32 value = default;
+ value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
+ return value;
+ }
[MethodImpl(InliningOptions.ShortMethod)]
- internal Argb32 ToArgb32() => this.data.ToArgb32();
+ internal Argb32 ToArgb32()
+ {
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data.ToArgb32();
+ }
+
+ Argb32 value = default;
+ value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
+ return value;
+ }
[MethodImpl(InliningOptions.ShortMethod)]
- internal Rgb24 ToRgb24() => this.data.ToRgb24();
+ internal Rgb24 ToRgb24()
+ {
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data.ToRgb24();
+ }
+
+ Rgb24 value = default;
+ value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
+ return value;
+ }
[MethodImpl(InliningOptions.ShortMethod)]
- internal Bgr24 ToBgr24() => this.data.ToBgr24();
+ internal Bgr24 ToBgr24()
+ {
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data.ToBgr24();
+ }
+
+ Bgr24 value = default;
+ value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
+ return value;
+ }
[MethodImpl(InliningOptions.ShortMethod)]
- internal Vector4 ToVector4() => this.data.ToVector4();
+ internal Vector4 ToVector4()
+ {
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data.ToScaledVector4();
+ }
+
+ return this.boxedHighPrecisionPixel.ToScaledVector4();
+ }
}
}
diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index d5eedc160..7c21d62dd 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -4,7 +4,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@@ -21,6 +20,7 @@ namespace SixLabors.ImageSharp
public readonly partial struct Color : IEquatable
{
private readonly Rgba64 data;
+ private readonly IPixel boxedHighPrecisionPixel;
[MethodImpl(InliningOptions.ShortMethod)]
private Color(byte r, byte g, byte b, byte a)
@@ -30,6 +30,8 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ColorNumerics.UpscaleFrom8BitTo16Bit(a));
+
+ this.boxedHighPrecisionPixel = null;
}
[MethodImpl(InliningOptions.ShortMethod)]
@@ -40,6 +42,15 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ushort.MaxValue);
+
+ this.boxedHighPrecisionPixel = null;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private Color(IPixel pixel)
+ {
+ this.boxedHighPrecisionPixel = pixel;
+ this.data = default;
}
///
@@ -52,13 +63,10 @@ namespace SixLabors.ImageSharp
/// otherwise, false.
///
[MethodImpl(InliningOptions.ShortMethod)]
- public static bool operator ==(Color left, Color right)
- {
- return left.Equals(right);
- }
+ public static bool operator ==(Color left, Color right) => left.Equals(right);
///
- /// Checks whether two structures are equal.
+ /// Checks whether two structures are not equal.
///
/// The left hand operand.
/// The right hand operand.
@@ -67,10 +75,7 @@ namespace SixLabors.ImageSharp
/// otherwise, false.
///
[MethodImpl(InliningOptions.ShortMethod)]
- public static bool operator !=(Color left, Color right)
- {
- return !left.Equals(right);
- }
+ public static bool operator !=(Color left, Color right) => !left.Equals(right);
///
/// Creates a from RGBA bytes.
@@ -81,7 +86,7 @@ namespace SixLabors.ImageSharp
/// The alpha component (0-255).
/// The .
[MethodImpl(InliningOptions.ShortMethod)]
- public static Color FromRgba(byte r, byte g, byte b, byte a) => new Color(r, g, b, a);
+ public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a);
///
/// Creates a from RGB bytes.
@@ -91,7 +96,46 @@ namespace SixLabors.ImageSharp
/// The blue component (0-255).
/// The .
[MethodImpl(InliningOptions.ShortMethod)]
- public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b);
+ public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b);
+
+ ///
+ /// Creates a from the given .
+ ///
+ /// The pixel to convert from.
+ /// The pixel format.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static Color FromPixel(TPixel pixel)
+ where TPixel : unmanaged, IPixel
+ {
+ // Avoid boxing in case we can convert to Rgba64 safely and efficently
+ if (typeof(TPixel) == typeof(Rgba64))
+ {
+ return new((Rgba64)(object)pixel);
+ }
+ else if (typeof(TPixel) == typeof(Rgb48))
+ {
+ return new((Rgb48)(object)pixel);
+ }
+ else if (typeof(TPixel) == typeof(La32))
+ {
+ return new((La32)(object)pixel);
+ }
+ else if (typeof(TPixel) == typeof(L16))
+ {
+ return new((L16)(object)pixel);
+ }
+ else if (Unsafe.SizeOf() <= Unsafe.SizeOf())
+ {
+ Rgba32 p = default;
+ pixel.ToRgba32(ref p);
+ return new(p);
+ }
+ else
+ {
+ return new(pixel);
+ }
+ }
///
/// Creates a new instance of the struct
@@ -213,7 +257,7 @@ namespace SixLabors.ImageSharp
public override string ToString() => this.ToHex();
///
- /// Converts the color instance to a specified type.
+ /// Converts the color instance to a specified type.
///
/// The pixel type to convert to.
/// The pixel value.
@@ -221,13 +265,18 @@ namespace SixLabors.ImageSharp
public TPixel ToPixel()
where TPixel : unmanaged, IPixel
{
- TPixel pixel = default;
+ if (this.boxedHighPrecisionPixel is TPixel pixel)
+ {
+ return pixel;
+ }
+
+ pixel = default;
pixel.FromRgba64(this.data);
return pixel;
}
///
- /// Bulk converts a span of to a span of a specified type.
+ /// Bulk converts a span of to a span of a specified type.
///
/// The pixel type to convert to.
/// The configuration.
@@ -240,28 +289,38 @@ namespace SixLabors.ImageSharp
Span destination)
where TPixel : unmanaged, IPixel
{
- ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source);
- PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination);
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToPixel();
+ }
}
///
[MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(Color other)
{
- return this.data.PackedValue == other.data.PackedValue;
+ if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
+ {
+ return this.data.PackedValue == other.data.PackedValue;
+ }
+
+ return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
}
///
- public override bool Equals(object obj)
- {
- return obj is Color other && this.Equals(other);
- }
+ public override bool Equals(object obj) => obj is Color other && this.Equals(other);
///
[MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode()
{
- return this.data.PackedValue.GetHashCode();
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data.PackedValue.GetHashCode();
+ }
+
+ return this.boxedHighPrecisionPixel.GetHashCode();
}
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 987dc150c..cf3cd7eb1 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -1071,7 +1071,7 @@ namespace SixLabors.ImageSharp.Formats.Png
int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
while (bytesRead != 0)
{
- uncompressedBytes.AddRange(this.buffer.AsSpan().Slice(0, bytesRead).ToArray());
+ uncompressedBytes.AddRange(this.buffer.AsSpan(0, bytesRead).ToArray());
bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
}
diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
index 7dbf49d45..000de4f88 100644
--- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs
@@ -35,6 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
///
/// Gets the number of entropy-analysis passes (in [1..10]).
+ /// Defaults to 1.
///
int EntropyPasses { get; }
diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs
index 8596d8555..02bbc38fc 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System.Runtime.CompilerServices;
+
namespace SixLabors.ImageSharp.Formats.Webp.Lossless
{
///
@@ -41,6 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
/// Inserts a new color into the cache.
///
/// The color to insert.
+ [MethodImpl(InliningOptions.ShortMethod)]
public void Insert(uint bgra)
{
int key = HashPix(bgra, this.HashShift);
@@ -52,6 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
///
/// The key to lookup.
/// The color for the key.
+ [MethodImpl(InliningOptions.ShortMethod)]
public uint Lookup(int key) => this.Colors[key];
///
@@ -59,6 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
///
/// The color to check.
/// The index of the color in the cache or -1 if its not present.
+ [MethodImpl(InliningOptions.ShortMethod)]
public int Contains(uint bgra)
{
int key = HashPix(bgra, this.HashShift);
@@ -70,6 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
///
/// The color.
/// The index for the color.
+ [MethodImpl(InliningOptions.ShortMethod)]
public int GetIndex(uint bgra) => HashPix(bgra, this.HashShift);
///
@@ -77,8 +83,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
///
/// The key.
/// The color to add.
+ [MethodImpl(InliningOptions.ShortMethod)]
public void Set(uint key, uint bgra) => this.Colors[key] = bgra;
+ [MethodImpl(InliningOptions.ShortMethod)]
public static int HashPix(uint argb, int shift) => (int)((argb * HashMul) >> shift);
}
}
diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
index 823146407..f9b97c6c4 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs
@@ -765,6 +765,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
///
/// Fast calculation of log2(v) for integer input.
///
+ [MethodImpl(InliningOptions.ShortMethod)]
public static float FastLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v);
///
@@ -793,7 +794,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private static float FastSLog2Slow(uint v)
{
- Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v));
+ DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v));
if (v < ApproxLogWithCorrectionMax)
{
int logCnt = 0;
@@ -1214,30 +1215,65 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2)
{
- int a = AddSubtractComponentFull(
- (int)(c0 >> 24),
- (int)(c1 >> 24),
- (int)(c2 >> 24));
- int r = AddSubtractComponentFull(
- (int)((c0 >> 16) & 0xff),
- (int)((c1 >> 16) & 0xff),
- (int)((c2 >> 16) & 0xff));
- int g = AddSubtractComponentFull(
- (int)((c0 >> 8) & 0xff),
- (int)((c1 >> 8) & 0xff),
- (int)((c2 >> 8) & 0xff));
- int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff));
- return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b;
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Sse2.IsSupported)
+ {
+ Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero);
+ Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero);
+ Vector128 c2Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero);
+ Vector128 v1 = Sse2.Add(c0Vec.AsInt16(), c1Vec.AsInt16());
+ Vector128 v2 = Sse2.Subtract(v1, c2Vec.AsInt16());
+ Vector128 b = Sse2.PackUnsignedSaturate(v2, v2);
+ uint output = Sse2.ConvertToUInt32(b.AsUInt32());
+ return output;
+ }
+#endif
+ {
+ int a = AddSubtractComponentFull(
+ (int)(c0 >> 24),
+ (int)(c1 >> 24),
+ (int)(c2 >> 24));
+ int r = AddSubtractComponentFull(
+ (int)((c0 >> 16) & 0xff),
+ (int)((c1 >> 16) & 0xff),
+ (int)((c2 >> 16) & 0xff));
+ int g = AddSubtractComponentFull(
+ (int)((c0 >> 8) & 0xff),
+ (int)((c1 >> 8) & 0xff),
+ (int)((c2 >> 8) & 0xff));
+ int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff));
+ return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b;
+ }
}
private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2)
{
- uint ave = Average2(c0, c1);
- int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24));
- int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff));
- int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff));
- int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff));
- return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b;
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Sse2.IsSupported)
+ {
+ Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero);
+ Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero);
+ Vector128 b0 = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero);
+ Vector128 avg = Sse2.Add(c1Vec.AsInt16(), c0Vec.AsInt16());
+ Vector128 a0 = Sse2.ShiftRightLogical(avg, 1);
+ Vector128 a1 = Sse2.Subtract(a0, b0.AsInt16());
+ Vector128 bgta = Sse2.CompareGreaterThan(b0.AsInt16(), a0.AsInt16());
+ Vector128 a2 = Sse2.Subtract(a1, bgta);
+ Vector128 a3 = Sse2.ShiftRightArithmetic(a2, 1);
+ Vector128 a4 = Sse2.Add(a0, a3).AsInt16();
+ Vector128 a5 = Sse2.PackUnsignedSaturate(a4, a4);
+ uint output = Sse2.ConvertToUInt32(a5.AsUInt32());
+ return output;
+ }
+#endif
+ {
+ uint ave = Average2(c0, c1);
+ int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24));
+ int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff));
+ int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff));
+ int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff));
+ return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b;
+ }
}
[MethodImpl(InliningOptions.ShortMethod)]
@@ -1275,11 +1311,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless
Vector128 pb = Sse2.UnpackLow(bc, Vector128.Zero); // |b - c|
Vector128 diff = Sse2.Subtract(pb.AsUInt16(), pa.AsUInt16());
Sse2.Store((ushort*)p, diff);
+ int paMinusPb = output[3] + output[2] + output[1] + output[0];
+ return (paMinusPb <= 0) ? a : b;
}
-
- int paMinusPb = output[0] + output[1] + output[2] + output[3];
-
- return (paMinusPb <= 0) ? a : b;
}
else
#endif
diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
index aa35f9673..c1af2a453 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
@@ -6,7 +6,6 @@ using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
-using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
@@ -112,11 +111,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
public static int Vp8Disto16X16(Span a, Span b, Span w, Span scratch)
{
int d = 0;
+ int dataSize = (4 * WebpConstants.Bps) - 16;
for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps)
{
for (int x = 0; x < 16; x += 4)
{
- d += Vp8Disto4X4(a.Slice(x + y), b.Slice(x + y), w, scratch);
+ d += Vp8Disto4X4(a.Slice(x + y, dataSize), b.Slice(x + y, dataSize), w, scratch);
}
}
@@ -126,9 +126,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
[MethodImpl(InliningOptions.ShortMethod)]
public static int Vp8Disto4X4(Span a, Span b, Span w, Span scratch)
{
- int sum1 = TTransform(a, w, scratch);
- int sum2 = TTransform(b, w, scratch);
- return Math.Abs(sum2 - sum1) >> 5;
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Sse41.IsSupported)
+ {
+ int diffSum = TTransformSse41(a, b, w, scratch);
+ return Math.Abs(diffSum) >> 5;
+ }
+ else
+#endif
+ {
+ int sum1 = TTransform(a, w, scratch);
+ int sum2 = TTransform(b, w, scratch);
+ return Math.Abs(sum2 - sum1) >> 5;
+ }
}
public static void DC16(Span dst, Span yuv, int offset)
@@ -640,6 +650,127 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return sum;
}
+#if SUPPORTS_RUNTIME_INTRINSICS
+ ///
+ /// Hadamard transform
+ /// Returns the weighted sum of the absolute value of transformed coefficients.
+ /// w[] contains a row-major 4 by 4 symmetric matrix.
+ ///
+ public static int TTransformSse41(Span inputA, Span inputB, Span w, Span scratch)
+ {
+ Span sum = scratch.Slice(0, 4);
+ sum.Clear();
+
+ // Load and combine inputs.
+ Vector128 ina0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA));
+ Vector128 ina1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps, 16)));
+ Vector128 ina2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 2, 16)));
+ Vector128 ina3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 3, 16))).AsInt64();
+ Vector128 inb0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB));
+ Vector128 inb1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps, 16)));
+ Vector128 inb2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 2, 16)));
+ Vector128 inb3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 3, 16))).AsInt64();
+
+ // Combine inA and inB (we'll do two transforms in parallel).
+ Vector128 inab0 = Sse2.UnpackLow(ina0.AsInt32(), inb0.AsInt32());
+ Vector128 inab1 = Sse2.UnpackLow(ina1.AsInt32(), inb1.AsInt32());
+ Vector128 inab2 = Sse2.UnpackLow(ina2.AsInt32(), inb2.AsInt32());
+ Vector128 inab3 = Sse2.UnpackLow(ina3.AsInt32(), inb3.AsInt32());
+ Vector128 tmp0 = Sse41.ConvertToVector128Int16(inab0.AsByte());
+ Vector128 tmp1 = Sse41.ConvertToVector128Int16(inab1.AsByte());
+ Vector128 tmp2 = Sse41.ConvertToVector128Int16(inab2.AsByte());
+ Vector128 tmp3 = Sse41.ConvertToVector128Int16(inab3.AsByte());
+
+ // a00 a01 a02 a03 b00 b01 b02 b03
+ // a10 a11 a12 a13 b10 b11 b12 b13
+ // a20 a21 a22 a23 b20 b21 b22 b23
+ // a30 a31 a32 a33 b30 b31 b32 b33
+ // Vertical pass first to avoid a transpose (vertical and horizontal passes
+ // are commutative because w/kWeightY is symmetric) and subsequent transpose.
+ // Calculate a and b (two 4x4 at once).
+ Vector128 a0 = Sse2.Add(tmp0, tmp2);
+ Vector128 a1 = Sse2.Add(tmp1, tmp3);
+ Vector128 a2 = Sse2.Subtract(tmp1, tmp3);
+ Vector128 a3 = Sse2.Subtract(tmp0, tmp2);
+ Vector128 b0 = Sse2.Add(a0, a1);
+ Vector128 b1 = Sse2.Add(a3, a2);
+ Vector128 b2 = Sse2.Subtract(a3, a2);
+ Vector128 b3 = Sse2.Subtract(a0, a1);
+
+ // a00 a01 a02 a03 b00 b01 b02 b03
+ // a10 a11 a12 a13 b10 b11 b12 b13
+ // a20 a21 a22 a23 b20 b21 b22 b23
+ // a30 a31 a32 a33 b30 b31 b32 b33
+ // Transpose the two 4x4.
+ Vector128 transpose00 = Sse2.UnpackLow(b0, b1);
+ Vector128 transpose01 = Sse2.UnpackLow(b2, b3);
+ Vector128 transpose02 = Sse2.UnpackHigh(b0, b1);
+ Vector128 transpose03 = Sse2.UnpackHigh(b2, b3);
+
+ // a00 a10 a01 a11 a02 a12 a03 a13
+ // a20 a30 a21 a31 a22 a32 a23 a33
+ // b00 b10 b01 b11 b02 b12 b03 b13
+ // b20 b30 b21 b31 b22 b32 b23 b33
+ Vector128 transpose10 = Sse2.UnpackLow(transpose00.AsInt32(), transpose01.AsInt32());
+ Vector128 transpose11 = Sse2.UnpackLow(transpose02.AsInt32(), transpose03.AsInt32());
+ Vector128 transpose12 = Sse2.UnpackHigh(transpose00.AsInt32(), transpose01.AsInt32());
+ Vector128 transpose13 = Sse2.UnpackHigh(transpose02.AsInt32(), transpose03.AsInt32());
+
+ // a00 a10 a20 a30 a01 a11 a21 a31
+ // b00 b10 b20 b30 b01 b11 b21 b31
+ // a02 a12 a22 a32 a03 a13 a23 a33
+ // b02 b12 a22 b32 b03 b13 b23 b33
+ Vector128 output0 = Sse2.UnpackLow(transpose10.AsInt64(), transpose11.AsInt64());
+ Vector128 output1 = Sse2.UnpackHigh(transpose10.AsInt64(), transpose11.AsInt64());
+ Vector128 output2 = Sse2.UnpackLow(transpose12.AsInt64(), transpose13.AsInt64());
+ Vector128 output3 = Sse2.UnpackHigh(transpose12.AsInt64(), transpose13.AsInt64());
+
+ // a00 a10 a20 a30 b00 b10 b20 b30
+ // a01 a11 a21 a31 b01 b11 b21 b31
+ // a02 a12 a22 a32 b02 b12 b22 b32
+ // a03 a13 a23 a33 b03 b13 b23 b33
+ // Horizontal pass and difference of weighted sums.
+ Vector128 w0 = Unsafe.As>(ref MemoryMarshal.GetReference(w));
+ Vector128 w8 = Unsafe.As>(ref MemoryMarshal.GetReference(w.Slice(8, 8)));
+
+ // Calculate a and b (two 4x4 at once).
+ a0 = Sse2.Add(output0.AsInt16(), output2.AsInt16());
+ a1 = Sse2.Add(output1.AsInt16(), output3.AsInt16());
+ a2 = Sse2.Subtract(output1.AsInt16(), output3.AsInt16());
+ a3 = Sse2.Subtract(output0.AsInt16(), output2.AsInt16());
+ b0 = Sse2.Add(a0, a1);
+ b1 = Sse2.Add(a3, a2);
+ b2 = Sse2.Subtract(a3, a2);
+ b3 = Sse2.Subtract(a0, a1);
+
+ // Separate the transforms of inA and inB.
+ Vector128 ab0 = Sse2.UnpackLow(b0.AsInt64(), b1.AsInt64());
+ Vector128 ab2 = Sse2.UnpackLow(b2.AsInt64(), b3.AsInt64());
+ Vector128 bb0 = Sse2.UnpackHigh(b0.AsInt64(), b1.AsInt64());
+ Vector128 bb2 = Sse2.UnpackHigh(b2.AsInt64(), b3.AsInt64());
+
+ Vector128 ab0Abs = Ssse3.Abs(ab0.AsInt16());
+ Vector128 ab2Abs = Ssse3.Abs(ab2.AsInt16());
+ Vector128 b0Abs = Ssse3.Abs(bb0.AsInt16());
+ Vector128 bb2Abs = Ssse3.Abs(bb2.AsInt16());
+
+ // weighted sums.
+ Vector128 ab0mulw0 = Sse2.MultiplyAddAdjacent(ab0Abs.AsInt16(), w0.AsInt16());
+ Vector128 ab2mulw8 = Sse2.MultiplyAddAdjacent(ab2Abs.AsInt16(), w8.AsInt16());
+ Vector128 b0mulw0 = Sse2.MultiplyAddAdjacent(b0Abs.AsInt16(), w0.AsInt16());
+ Vector128 bb2mulw8 = Sse2.MultiplyAddAdjacent(bb2Abs.AsInt16(), w8.AsInt16());
+ Vector128 ab0ab2Sum = Sse2.Add(ab0mulw0, ab2mulw8);
+ Vector128 b0w0bb2w8Sum = Sse2.Add(b0mulw0, bb2mulw8);
+
+ // difference of weighted sums.
+ Vector128 result = Sse2.Subtract(ab0ab2Sum.AsInt32(), b0w0bb2w8Sum.AsInt32());
+
+ ref int outputRef = ref MemoryMarshal.GetReference(sum);
+ Unsafe.As>(ref outputRef) = result.AsInt32();
+ return sum[3] + sum[2] + sum[1] + sum[0];
+ }
+#endif
+
public static void TransformTwo(Span src, Span dst, Span scratch)
{
TransformOne(src, dst, scratch);
diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
index 18d7494f0..97ef27d25 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs
@@ -3,13 +3,18 @@
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+#if SUPPORTS_RUNTIME_INTRINSICS
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
///
/// Quantization methods.
///
- internal static class QuantEnc
+ internal static unsafe class QuantEnc
{
private static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 };
@@ -17,6 +22,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int MaxLevel = 2047;
+#if SUPPORTS_RUNTIME_INTRINSICS
+ private static readonly Vector128 MaxCoeff2047 = Vector128.Create((short)MaxLevel);
+
+ private static readonly Vector128 CstLo = Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13);
+
+ private static readonly Vector128 Cst7 = Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255);
+
+ private static readonly Vector128 CstHi = Vector128.Create(2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15);
+
+ private static readonly Vector128 Cst8 = Vector128.Create(254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255);
+#endif
+
// Diffusion weights. We under-correct a bit (15/16th of the error is actually
// diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0.
private const int C1 = 7; // fraction of error sent to the 4x4 block below
@@ -298,14 +315,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
Vp8Encoding.FTransformWht(tmp, dcTmp, scratch);
- nz |= QuantizeBlock(dcTmp, rd.YDcLevels, dqm.Y2) << 24;
+ nz |= QuantizeBlock(dcTmp, rd.YDcLevels, ref dqm.Y2) << 24;
for (n = 0; n < 16; n += 2)
{
// Zero-out the first coeff, so that: a) nz is correct below, and
// b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified.
tmp[n * 16] = tmp[(n + 1) * 16] = 0;
- nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), dqm.Y1) << n;
+ nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), ref dqm.Y1) << n;
}
// Transform back.
@@ -326,7 +343,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
tmp.Clear();
scratch.Clear();
Vp8Encoding.FTransform(src, reference, tmp, scratch);
- int nz = QuantizeBlock(tmp, levels, dqm.Y1);
+ int nz = QuantizeBlock(tmp, levels, ref dqm.Y1);
Vp8Encoding.ITransform(reference, tmp, yuvOut, false, scratch);
return nz;
@@ -353,11 +370,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
scratch);
}
- CorrectDcValues(it, dqm.Uv, tmp, rd);
+ CorrectDcValues(it, ref dqm.Uv, tmp, rd);
for (n = 0; n < 8; n += 2)
{
- nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), dqm.Uv) << n;
+ nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), ref dqm.Uv) << n;
}
for (n = 0; n < 8; n += 2)
@@ -508,58 +525,155 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
}
[MethodImpl(InliningOptions.ShortMethod)]
- public static int Quantize2Blocks(Span input, Span output, Vp8Matrix mtx)
+ public static int Quantize2Blocks(Span input, Span output, ref Vp8Matrix mtx)
{
- int nz = QuantizeBlock(input, output, mtx) << 0;
- nz |= QuantizeBlock(input.Slice(1 * 16), output.Slice(1 * 16), mtx) << 1;
+ int nz = QuantizeBlock(input.Slice(0, 16), output.Slice(0, 16), ref mtx) << 0;
+ nz |= QuantizeBlock(input.Slice(1 * 16, 16), output.Slice(1 * 16, 16), ref mtx) << 1;
return nz;
}
- public static int QuantizeBlock(Span input, Span output, Vp8Matrix mtx)
+ public static int QuantizeBlock(Span input, Span output, ref Vp8Matrix mtx)
{
- int last = -1;
- int n;
- for (n = 0; n < 16; ++n)
+#if SUPPORTS_RUNTIME_INTRINSICS
+ if (Sse41.IsSupported)
{
- int j = Zigzag[n];
- bool sign = input[j] < 0;
- uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]);
- if (coeff > mtx.ZThresh[j])
+ // Load all inputs.
+ Vector128 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input));
+ Vector128 input8 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(8, 8)));
+ Vector128 iq0 = Unsafe.As>(ref mtx.IQ[0]);
+ Vector128 iq8 = Unsafe.As>(ref mtx.IQ[8]);
+ Vector128 q0 = Unsafe.As>(ref mtx.Q[0]);
+ Vector128 q8 = Unsafe.As>(ref mtx.Q[8]);
+
+ // coeff = abs(in)
+ Vector128 coeff0 = Ssse3.Abs(input0);
+ Vector128 coeff8 = Ssse3.Abs(input8);
+
+ // coeff = abs(in) + sharpen
+ Vector128 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]);
+ Vector128 sharpen8 = Unsafe.As>(ref mtx.Sharpen[8]);
+ Sse2.Add(coeff0.AsInt16(), sharpen0);
+ Sse2.Add(coeff8.AsInt16(), sharpen8);
+
+ // out = (coeff * iQ + B) >> QFIX
+ // doing calculations with 32b precision (QFIX=17)
+ // out = (coeff * iQ)
+ Vector128 coeffiQ0H = Sse2.MultiplyHigh(coeff0, iq0);
+ Vector128 coeffiQ0L = Sse2.MultiplyLow(coeff0, iq0);
+ Vector128 coeffiQ8H = Sse2.MultiplyHigh(coeff8, iq8);
+ Vector128 coeffiQ8L = Sse2.MultiplyLow(coeff8, iq8);
+ Vector128 out00 = Sse2.UnpackLow(coeffiQ0L, coeffiQ0H);
+ Vector128 out04 = Sse2.UnpackHigh(coeffiQ0L, coeffiQ0H);
+ Vector128 out08 = Sse2.UnpackLow(coeffiQ8L, coeffiQ8H);
+ Vector128 out12 = Sse2.UnpackHigh(coeffiQ8L, coeffiQ8H);
+
+ // out = (coeff * iQ + B)
+ Vector128 bias00 = Unsafe.As>(ref mtx.Bias[0]);
+ Vector128 bias04 = Unsafe.As>(ref mtx.Bias[4]);
+ Vector128 bias08 = Unsafe.As>(ref mtx.Bias[8]);
+ Vector128 bias12 = Unsafe.As>(ref mtx.Bias[12]);
+ out00 = Sse2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16();
+ out04 = Sse2.Add(out04.AsInt32(), bias04.AsInt32()).AsUInt16();
+ out08 = Sse2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16();
+ out12 = Sse2.Add(out12.AsInt32(), bias12.AsInt32()).AsUInt16();
+
+ // out = QUANTDIV(coeff, iQ, B, QFIX)
+ out00 = Sse2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16();
+ out04 = Sse2.ShiftRightArithmetic(out04.AsInt32(), WebpConstants.QFix).AsUInt16();
+ out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16();
+ out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16();
+
+ // pack result as 16b
+ Vector128 out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32());
+ Vector128 out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32());
+
+ // if (coeff > 2047) coeff = 2047
+ out0 = Sse2.Min(out0, MaxCoeff2047);
+ out8 = Sse2.Min(out8, MaxCoeff2047);
+
+ // put sign back
+ out0 = Ssse3.Sign(out0, input0);
+ out8 = Ssse3.Sign(out8, input8);
+
+ // in = out * Q
+ input0 = Sse2.MultiplyLow(out0, q0.AsInt16());
+ input8 = Sse2.MultiplyLow(out8, q8.AsInt16());
+
+ // in = out * Q
+ ref short inputRef = ref MemoryMarshal.GetReference(input);
+ Unsafe.As>(ref inputRef) = input0;
+ Unsafe.As>(ref Unsafe.Add(ref inputRef, 8)) = input8;
+
+ // zigzag the output before storing it. The re-ordering is:
+ // 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15
+ // -> 0 1 4[8]5 2 3 6 | 9 12 13 10 [7]11 14 15
+ // There's only two misplaced entries ([8] and [7]) that are crossing the
+ // reg's boundaries.
+ // We use pshufb instead of pshuflo/pshufhi.
+ Vector128 tmpLo = Ssse3.Shuffle(out0.AsByte(), CstLo);
+ Vector128 tmp7 = Ssse3.Shuffle(out0.AsByte(), Cst7); // extract #7
+ Vector128 tmpHi = Ssse3.Shuffle(out8.AsByte(), CstHi);
+ Vector128 tmp8 = Ssse3.Shuffle(out8.AsByte(), Cst8); // extract #8
+ Vector128 outZ0 = Sse2.Or(tmpLo, tmp8);
+ Vector128 outZ8 = Sse2.Or(tmpHi, tmp7);
+
+ ref short outputRef = ref MemoryMarshal.GetReference(output);
+ Unsafe.As>(ref outputRef) = outZ0.AsInt16();
+ Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = outZ8.AsInt16();
+
+ Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16());
+
+ // Detect if all 'out' values are zeros or not.
+ Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero);
+ return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0;
+ }
+ else
+#endif
+ {
+ int last = -1;
+ int n;
+ for (n = 0; n < 16; ++n)
{
- uint q = mtx.Q[j];
- uint iQ = mtx.IQ[j];
- uint b = mtx.Bias[j];
- int level = QuantDiv(coeff, iQ, b);
- if (level > MaxLevel)
+ int j = Zigzag[n];
+ bool sign = input[j] < 0;
+ uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]);
+ if (coeff > mtx.ZThresh[j])
{
- level = MaxLevel;
- }
+ uint q = mtx.Q[j];
+ uint iQ = mtx.IQ[j];
+ uint b = mtx.Bias[j];
+ int level = QuantDiv(coeff, iQ, b);
+ if (level > MaxLevel)
+ {
+ level = MaxLevel;
+ }
- if (sign)
- {
- level = -level;
- }
+ if (sign)
+ {
+ level = -level;
+ }
- input[j] = (short)(level * (int)q);
- output[n] = (short)level;
- if (level != 0)
+ input[j] = (short)(level * (int)q);
+ output[n] = (short)level;
+ if (level != 0)
+ {
+ last = n;
+ }
+ }
+ else
{
- last = n;
+ output[n] = 0;
+ input[j] = 0;
}
}
- else
- {
- output[n] = 0;
- input[j] = 0;
- }
- }
- return last >= 0 ? 1 : 0;
+ return last >= 0 ? 1 : 0;
+ }
}
// Quantize as usual, but also compute and return the quantization error.
// Error is already divided by DSHIFT.
- public static int QuantizeSingle(Span v, Vp8Matrix mtx)
+ public static int QuantizeSingle(Span v, ref Vp8Matrix mtx)
{
int v0 = v[0];
bool sign = v0 < 0;
@@ -580,7 +694,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return (sign ? -v0 : v0) >> DSCALE;
}
- public static void CorrectDcValues(Vp8EncIterator it, Vp8Matrix mtx, Span tmp, Vp8ModeScore rd)
+ public static void CorrectDcValues(Vp8EncIterator it, ref Vp8Matrix mtx, Span tmp, Vp8ModeScore rd)
{
#pragma warning disable SA1005 // Single line comments should begin with single space
// | top[0] | top[1]
@@ -597,13 +711,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Span left = it.LeftDerr.AsSpan(ch, 2);
Span c = tmp.Slice(ch * 4 * 16, 4 * 16);
c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE));
- int err0 = QuantizeSingle(c, mtx);
+ int err0 = QuantizeSingle(c, ref mtx);
c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE));
- int err1 = QuantizeSingle(c.Slice(1 * 16), mtx);
+ int err1 = QuantizeSingle(c.Slice(1 * 16), ref mtx);
c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE));
- int err2 = QuantizeSingle(c.Slice(2 * 16), mtx);
+ int err2 = QuantizeSingle(c.Slice(2 * 16), ref mtx);
c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE));
- int err3 = QuantizeSingle(c.Slice(3 * 16), mtx);
+ int err3 = QuantizeSingle(c.Slice(3 * 16), ref mtx);
rd.Derr[ch, 0] = err1;
rd.Derr[ch, 1] = err2;
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index 728574682..8a4115d21 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -502,7 +502,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
this.ResetStats();
}
- private void AdjustFilterStrength()
+ private unsafe void AdjustFilterStrength()
{
if (this.filterStrength > 0)
{
@@ -806,7 +806,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
proba.NbSkip = 0;
}
- private void SetupMatrices(Vp8SegmentInfo[] dqm)
+ private unsafe void SetupMatrices(Vp8SegmentInfo[] dqm)
{
int tlambdaScale = this.method >= WebpEncodingMethod.Default ? this.spatialNoiseShaping : 0;
for (int i = 0; i < dqm.Length; i++)
@@ -814,10 +814,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
Vp8SegmentInfo m = dqm[i];
int q = m.Quant;
- m.Y1 = new Vp8Matrix();
- m.Y2 = new Vp8Matrix();
- m.Uv = new Vp8Matrix();
-
m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY1Dc, 0, 127)];
m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)];
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs
index 4276b887f..66c91e44a 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs
@@ -3,7 +3,7 @@
namespace SixLabors.ImageSharp.Formats.Webp.Lossy
{
- internal class Vp8Matrix
+ internal unsafe struct Vp8Matrix
{
private static readonly int[][] BiasMatrices =
{
@@ -23,41 +23,29 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
private const int SharpenBits = 11;
///
- /// Initializes a new instance of the class.
+ /// The quantizer steps.
///
- public Vp8Matrix()
- {
- this.Q = new ushort[16];
- this.IQ = new ushort[16];
- this.Bias = new uint[16];
- this.ZThresh = new uint[16];
- this.Sharpen = new short[16];
- }
-
- ///
- /// Gets the quantizer steps.
- ///
- public ushort[] Q { get; }
+ public fixed ushort Q[16];
///
- /// Gets the reciprocals, fixed point.
+ /// The reciprocals, fixed point.
///
- public ushort[] IQ { get; }
+ public fixed ushort IQ[16];
///
- /// Gets the rounding bias.
+ /// The rounding bias.
///
- public uint[] Bias { get; }
+ public fixed uint Bias[16];
///
- /// Gets the value below which a coefficient is zeroed.
+ /// The value below which a coefficient is zeroed.
///
- public uint[] ZThresh { get; }
+ public fixed uint ZThresh[16];
///
- /// Gets the frequency boosters for slight sharpening.
+ /// The frequency boosters for slight sharpening.
///
- public short[] Sharpen { get; }
+ public fixed short Sharpen[16];
///
/// Returns the average quantizer.
@@ -72,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
int isAcCoeff = i > 0 ? 1 : 0;
int bias = BiasMatrices[type][isAcCoeff];
this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]);
- this.Bias[i] = (uint)this.BIAS(bias);
+ this.Bias[i] = (uint)BIAS(bias);
// zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is:
// * zero if coeff <= zthresh
@@ -106,6 +94,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
return (sum + 8) >> 4;
}
- private int BIAS(int b) => b << (WebpConstants.QFix - 8);
+ private static int BIAS(int b) => b << (WebpConstants.QFix - 8);
}
}
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs
index cf2a5c177..2ce383d9e 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs
@@ -8,19 +8,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy
internal class Vp8SegmentInfo
{
///
- /// Gets or sets the quantization matrix y1.
+ /// Gets the quantization matrix y1.
///
- public Vp8Matrix Y1 { get; set; }
+#pragma warning disable SA1401 // Fields should be private
+ public Vp8Matrix Y1;
///
- /// Gets or sets the quantization matrix y2.
+ /// Gets the quantization matrix y2.
///
- public Vp8Matrix Y2 { get; set; }
+ public Vp8Matrix Y2;
///
- /// Gets or sets the quantization matrix uv.
+ /// Gets the quantization matrix uv.
///
- public Vp8Matrix Uv { get; set; }
+ public Vp8Matrix Uv;
+#pragma warning restore SA1401 // Fields should be private
///
/// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness.
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
index 44a55a4c6..09071406c 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
@@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
// Check for VP8 magic bytes.
this.currentStream.Read(this.buffer, 0, 3);
- if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
+ if (!this.buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
{
WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found");
}
diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
index f85f65b63..bdcbb194b 100644
--- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Webp
public bool UseAlphaCompression { get; set; }
///
- public int EntropyPasses { get; set; }
+ public int EntropyPasses { get; set; } = 1;
///
public int SpatialNoiseShaping { get; set; } = 50;
diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs
index b9220c56a..e28baf879 100644
--- a/src/ImageSharp/IO/ChunkedMemoryStream.cs
+++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs
@@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.IO
const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
- return this.ReadImpl(buffer.AsSpan().Slice(offset, count));
+ return this.ReadImpl(buffer.AsSpan(offset, count));
}
#if SUPPORTS_SPAN_STREAM
@@ -359,7 +359,7 @@ namespace SixLabors.ImageSharp.IO
const string bufferMessage = "Offset subtracted from the buffer length is less than count.";
Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage);
- this.WriteImpl(buffer.AsSpan().Slice(offset, count));
+ this.WriteImpl(buffer.AsSpan(offset, count));
}
#if SUPPORTS_SPAN_STREAM
diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs
index 94da2c995..ee340bf86 100644
--- a/src/ImageSharp/Image.Decode.cs
+++ b/src/ImageSharp/Image.Decode.cs
@@ -58,31 +58,42 @@ namespace SixLabors.ImageSharp
return null;
}
- using (IMemoryOwner buffer = config.MemoryAllocator.Allocate(headerSize, AllocationOptions.Clean))
+ // Header sizes are so small, that headersBuffer will be always stackalloc-ed in practice,
+ // and heap allocation will never happen, there is no need for the usual try-finally ArrayPool dance.
+ // The array case is only a safety mechanism following stackalloc best practices.
+ Span headersBuffer = headerSize > 512 ? new byte[headerSize] : stackalloc byte[headerSize];
+ long startPosition = stream.Position;
+
+ // Read doesn't always guarantee the full returned length so read a byte
+ // at a time until we get either our count or hit the end of the stream.
+ int n = 0;
+ int i;
+ do
{
- Span bufferSpan = buffer.GetSpan();
- long startPosition = stream.Position;
+ i = stream.Read(headersBuffer, n, headerSize - n);
+ n += i;
+ }
+ while (n < headerSize && i > 0);
- // Read doesn't always guarantee the full returned length so read a byte
- // at a time until we get either our count or hit the end of the stream.
- int n = 0;
- int i;
- do
+ stream.Position = startPosition;
+
+ // Does the given stream contain enough data to fit in the header for the format
+ // and does that data match the format specification?
+ // Individual formats should still check since they are public.
+ IImageFormat format = null;
+ foreach (IImageFormatDetector formatDetector in config.ImageFormatsManager.FormatDetectors)
+ {
+ if (formatDetector.HeaderSize <= headerSize)
{
- i = stream.Read(bufferSpan, n, headerSize - n);
- n += i;
+ IImageFormat attemptFormat = formatDetector.DetectFormat(headersBuffer);
+ if (attemptFormat != null)
+ {
+ format = attemptFormat;
+ }
}
- while (n < headerSize && i > 0);
-
- stream.Position = startPosition;
-
- // Does the given stream contain enough data to fit in the header for the format
- // and does that data match the format specification?
- // Individual formats should still check since they are public.
- return config.ImageFormatsManager.FormatDetectors
- .Where(x => x.HeaderSize <= headerSize)
- .Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null);
}
+
+ return format;
}
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
index a58c20f68..9cc468060 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
@@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right);
- Span kernelValues = this.tempValues.AsSpan().Slice(0, kernel.Length);
+ Span kernelValues = this.tempValues.AsSpan(0, kernel.Length);
double sum = 0;
for (int j = left; j <= right; j++)
diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs
index 407a4ef3b..878929823 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs
@@ -76,34 +76,29 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
return image.Height;
}
- /* Results 17.06.2021
- * BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
+ /* Results 04.11.2021
+ * BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
- .NET Core SDK=3.1.202
- [Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
- Job-AQFZAV : .NET Framework 4.8 (4.8.4180.0), X64 RyuJIT
- Job-YCDAPQ : .NET Core 2.1.18 (CoreCLR 4.6.28801.04, CoreFX 4.6.28802.05), X64 RyuJIT
- Job-WMTYOZ : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
-
- IterationCount=3 LaunchCount=1 WarmupCount=3
- | Method | Job | Runtime | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
- |--------------------------- |----------- |-------------- |---------------------- |------------------------- |-----------:|----------:|---------:|----------:|----------:|------:|------------:|
- | 'Magick Lossy Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.8 ms | 6.28 ms | 0.34 ms | - | - | - | 17.65 KB |
- | 'ImageSharp Lossy Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,145.0 ms | 110.82 ms | 6.07 ms | - | - | - | 2779.53 KB |
- | 'Magick Lossless Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 145.9 ms | 8.55 ms | 0.47 ms | - | - | - | 18.05 KB |
- | 'ImageSharp Lossless Webp' | Job-IERNAB | .NET 4.7.2 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,694.1 ms | 55.09 ms | 3.02 ms | 4000.0000 | 1000.0000 | - | 30556.87 KB |
- | 'Magick Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.7 ms | 1.89 ms | 0.10 ms | - | - | - | 15.75 KB |
- | 'ImageSharp Lossy Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 741.6 ms | 21.45 ms | 1.18 ms | - | - | - | 2767.85 KB |
- | 'Magick Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.1 ms | 9.52 ms | 0.52 ms | - | - | - | 16.54 KB |
- | 'ImageSharp Lossless Webp' | Job-IMRAGJ | .NET Core 2.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 522.5 ms | 21.15 ms | 1.16 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB |
- | 'Magick Lossy Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.9 ms | 5.34 ms | 0.29 ms | - | - | - | 15.45 KB |
- | 'ImageSharp Lossy Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 748.8 ms | 290.47 ms | 15.92 ms | - | - | - | 2767.84 KB |
- | 'Magick Lossless Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.1 ms | 1.14 ms | 0.06 ms | - | - | - | 15.9 KB |
- | 'ImageSharp Lossless Webp' | Job-NAASQX | .NET Core 3.1 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 480.7 ms | 25.25 ms | 1.38 ms | 4000.0000 | 1000.0000 | - | 22859.7 KB |
- | 'Magick Lossy Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 105.7 ms | 4.71 ms | 0.26 ms | - | - | - | 15.48 KB |
- | 'ImageSharp Lossy Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 645.7 ms | 61.00 ms | 3.34 ms | - | - | - | 2768.13 KB |
- | 'Magick Lossless Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 146.5 ms | 18.63 ms | 1.02 ms | - | - | - | 15.8 KB |
- | 'ImageSharp Lossless Webp' | Job-GLNACU | .NET Core 5.0 | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 306.7 ms | 32.31 ms | 1.77 ms | 4000.0000 | 1000.0000 | - | 22860.02 KB |
+ .NET SDK=6.0.100-rc.2.21505.57
+ [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
+ Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
+ Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT
+ Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT
+
+ | Method | Job | Runtime | Arguments | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
+ |--------------------------- |----------- |--------------------- |---------------------- |---------------------- |------------------------- |-----------:|----------:|--------:|---------:|------:|------:|----------:|
+ | 'Magick Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 107.9 ms | 28.91 ms | 1.58 ms | - | - | - | 25 KB |
+ | 'ImageSharp Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 282.3 ms | 25.40 ms | 1.39 ms | 500.0000 | - | - | 2,428 KB |
+ | 'Magick Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.3 ms | 11.99 ms | 0.66 ms | - | - | - | 16 KB |
+ | 'ImageSharp Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 280.2 ms | 6.21 ms | 0.34 ms | - | - | - | 2,092 KB |
+ | 'Magick Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 9.32 ms | 0.51 ms | - | - | - | 15 KB |
+ | 'ImageSharp Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 295.8 ms | 21.25 ms | 1.16 ms | 500.0000 | - | - | 2,427 KB |
+ | 'Magick Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.5 ms | 4.07 ms | 0.22 ms | - | - | - | 15 KB |
+ | 'ImageSharp Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 464.0 ms | 55.70 ms | 3.05 ms | - | - | - | 2,090 KB |
+ | 'Magick Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 108.0 ms | 29.60 ms | 1.62 ms | - | - | - | 32 KB |
+ | 'ImageSharp Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 564.9 ms | 29.69 ms | 1.63 ms | - | - | - | 2,436 KB |
+ | 'Magick Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 4.74 ms | 0.26 ms | - | - | - | 18 KB |
+ | 'ImageSharp Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,767.5 ms | 106.33 ms | 5.83 ms | - | - | - | 9,729 KB |
*/
}
}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs
index 7d3dfe693..43d8c464c 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs
@@ -4,6 +4,7 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using ImageMagick;
+using ImageMagick.Formats;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
@@ -44,8 +45,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void MagickWebpLossy()
{
using var memoryStream = new MemoryStream();
- this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", false);
- this.webpMagick.Write(memoryStream, MagickFormat.WebP);
+
+ var defines = new WebPWriteDefines
+ {
+ Lossless = false,
+ Method = 4,
+ AlphaCompression = WebPAlphaCompression.None,
+ FilterStrength = 60,
+ SnsStrength = 50,
+ Pass = 1,
+
+ // 100 means off.
+ NearLossless = 100
+ };
+
+ this.webpMagick.Quality = 75;
+ this.webpMagick.Write(memoryStream, defines);
}
[Benchmark(Description = "ImageSharp Webp Lossy")]
@@ -54,7 +69,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
using var memoryStream = new MemoryStream();
this.webp.Save(memoryStream, new WebpEncoder()
{
- FileFormat = WebpFileFormatType.Lossy
+ FileFormat = WebpFileFormatType.Lossy,
+ Method = WebpEncodingMethod.Level4,
+ UseAlphaCompression = false,
+ FilterStrength = 60,
+ SpatialNoiseShaping = 50,
+ EntropyPasses = 1
});
}
@@ -62,8 +82,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void MagickWebpLossless()
{
using var memoryStream = new MemoryStream();
- this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "lossless", true);
- this.webpMagick.Write(memoryStream, MagickFormat.WebP);
+ var defines = new WebPWriteDefines
+ {
+ Lossless = true,
+ Method = 4,
+
+ // 100 means off.
+ NearLossless = 100
+ };
+
+ this.webpMagick.Quality = 75;
+ this.webpMagick.Write(memoryStream, defines);
}
[Benchmark(Description = "ImageSharp Webp Lossless")]
@@ -72,41 +101,43 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
using var memoryStream = new MemoryStream();
this.webp.Save(memoryStream, new WebpEncoder()
{
- FileFormat = WebpFileFormatType.Lossless
+ FileFormat = WebpFileFormatType.Lossless,
+ Method = WebpEncodingMethod.Level4,
+ NearLossless = false,
+
+ // This is equal to exact = false in libwebp, which is the default.
+ TransparentColorMode = WebpTransparentColorMode.Clear
});
}
- /* Results 17.06.2021
+ /* Results 04.11.2021
* Summary *
- BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.630 (2004/?/20H1)
+ BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update)
Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
- .NET Core SDK=5.0.100
- [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
- Job-OUUGWL : .NET Framework 4.8 (4.8.4250.0), X64 RyuJIT
- Job-GAIITM : .NET Core 2.1.23 (CoreCLR 4.6.29321.03, CoreFX 4.6.29321.01), X64 RyuJIT
- Job-HWOBSO : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT
-
- | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
- |--------------------------- |----------- |-------------- |------------- |----------:|-----------:|----------:|------:|--------:|-----------:|----------:|----------:|-------------:|
- | 'Magick Webp Lossy' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 23.30 ms | 0.869 ms | 0.048 ms | 0.14 | 0.00 | - | - | - | 68.19 KB |
- | 'ImageSharp Webp Lossy' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 68.22 ms | 16.454 ms | 0.902 ms | 0.42 | 0.01 | 6125.0000 | 125.0000 | - | 26359.49 KB |
- | 'Magick Webp Lossless' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 161.96 ms | 9.879 ms | 0.541 ms | 1.00 | 0.00 | - | - | - | 520.28 KB |
- | 'ImageSharp Webp Lossless' | Job-RYVNHD | .NET 4.7.2 | Png/Bike.png | 370.88 ms | 58.875 ms | 3.227 ms | 2.29 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 163177.15 KB |
- | | | | | | | | | | | | | |
- | 'Magick Webp Lossy' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 23.35 ms | 0.428 ms | 0.023 ms | 0.14 | 0.00 | - | - | - | 67.76 KB |
- | 'ImageSharp Webp Lossy' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 43.95 ms | 2.850 ms | 0.156 ms | 0.27 | 0.00 | 6250.0000 | 250.0000 | 83.3333 | 26284.72 KB |
- | 'Magick Webp Lossless' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 161.44 ms | 3.749 ms | 0.206 ms | 1.00 | 0.00 | - | - | - | 519.26 KB |
- | 'ImageSharp Webp Lossless' | Job-GOZXWU | .NET Core 2.1 | Png/Bike.png | 335.78 ms | 78.666 ms | 4.312 ms | 2.08 | 0.03 | 34000.0000 | 5000.0000 | 2000.0000 | 162727.56 KB |
- | | | | | | | | | | | | | |
- | 'Magick Webp Lossy' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 23.48 ms | 4.325 ms | 0.237 ms | 0.15 | 0.00 | - | - | - | 67.66 KB |
- | 'ImageSharp Webp Lossy' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 43.29 ms | 16.503 ms | 0.905 ms | 0.27 | 0.01 | 6272.7273 | 272.7273 | 90.9091 | 26284.86 KB |
- | 'Magick Webp Lossless' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 161.81 ms | 10.693 ms | 0.586 ms | 1.00 | 0.00 | - | - | - | 523.25 KB |
- | 'ImageSharp Webp Lossless' | Job-VRDVKW | .NET Core 3.1 | Png/Bike.png | 323.97 ms | 235.468 ms | 12.907 ms | 2.00 | 0.08 | 34000.0000 | 5000.0000 | 2000.0000 | 162724.84 KB |
- | | | | | | | | | | | | | |
- | 'Magick Webp Lossy' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 23.36 ms | 0.448 ms | 0.025 ms | 0.14 | 0.00 | - | - | - | 67.66 KB |
- | 'ImageSharp Webp Lossy' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 40.11 ms | 2.465 ms | 0.135 ms | 0.25 | 0.00 | 6307.6923 | 230.7692 | 76.9231 | 26284.71 KB |
- | 'Magick Webp Lossless' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 161.55 ms | 6.662 ms | 0.365 ms | 1.00 | 0.00 | - | - | - | 518.84 KB |
- | 'ImageSharp Webp Lossless' | Job-ZJRLRB | .NET Core 5.0 | Png/Bike.png | 298.73 ms | 17.953 ms | 0.984 ms | 1.85 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 162725.13 KB |
+ .NET SDK=6.0.100-rc.2.21505.57
+ [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
+ Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT
+ Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT
+ Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT
+
+ IterationCount=3 LaunchCount=1 WarmupCount=3
+
+ | Method | Job | Runtime | Arguments | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
+ |--------------------------- |----------- |--------------------- |---------------------- |------------- |----------:|----------:|---------:|------:|--------:|------------:|----------:|----------:|-----------:|
+ | 'Magick Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 23.33 ms | 1.491 ms | 0.082 ms | 0.15 | 0.00 | - | - | - | 67 KB |
+ | 'ImageSharp Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 245.80 ms | 24.288 ms | 1.331 ms | 1.53 | 0.01 | 135000.0000 | - | - | 552,713 KB |
+ | 'Magick Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 160.36 ms | 11.131 ms | 0.610 ms | 1.00 | 0.00 | - | - | - | 518 KB |
+ | 'ImageSharp Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 313.93 ms | 45.605 ms | 2.500 ms | 1.96 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,670 KB |
+ | | | | | | | | | | | | | | |
+ | 'Magick Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 23.36 ms | 2.289 ms | 0.125 ms | 0.15 | 0.00 | - | - | - | 67 KB |
+ | 'ImageSharp Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 254.64 ms | 19.620 ms | 1.075 ms | 1.59 | 0.00 | 135000.0000 | - | - | 552,713 KB |
+ | 'Magick Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 160.30 ms | 9.549 ms | 0.523 ms | 1.00 | 0.00 | - | - | - | 518 KB |
+ | 'ImageSharp Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 320.35 ms | 22.924 ms | 1.257 ms | 2.00 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,669 KB |
+ | | | | | | | | | | | | | | |
+ | 'Magick Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 23.37 ms | 0.908 ms | 0.050 ms | 0.15 | 0.00 | - | - | - | 68 KB |
+ | 'ImageSharp Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 378.67 ms | 25.540 ms | 1.400 ms | 2.36 | 0.01 | 135000.0000 | - | - | 554,351 KB |
+ | 'Magick Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 160.13 ms | 5.115 ms | 0.280 ms | 1.00 | 0.00 | - | - | - | 520 KB |
+ | 'ImageSharp Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 379.01 ms | 71.192 ms | 3.902 ms | 2.37 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 162,119 KB |
*/
}
}
diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs
index ee1820de7..3003265ca 100644
--- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs
+++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs
@@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Rgb24()
{
- var source = new Rgb24(1, 22, 231);
+ var source = new Rgb24(1, 22, 231);
// Act:
var color = new Color(source);
@@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Bgr24()
{
- var source = new Bgr24(1, 22, 231);
+ var source = new Bgr24(1, 22, 231);
// Act:
var color = new Color(source);
@@ -88,6 +88,28 @@ namespace SixLabors.ImageSharp.Tests
Bgr24 data = color;
Assert.Equal(source, data);
}
+
+ [Fact]
+ public void GenericPixel()
+ {
+ AssertGenericPixel(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue));
+ AssertGenericPixel(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1));
+ AssertGenericPixel(new Rgb48(1, 2, ushort.MaxValue - 1));
+ AssertGenericPixel(new La32(1, ushort.MaxValue - 1));
+ AssertGenericPixel(new L16(ushort.MaxValue - 1));
+ AssertGenericPixel(new Rgba32(1, 2, 255, 254));
+ }
+
+ private static void AssertGenericPixel(TPixel source)
+ where TPixel : unmanaged, IPixel
+ {
+ // Act:
+ var color = Color.FromPixel(source);
+
+ // Assert:
+ TPixel actual = color.ToPixel();
+ Assert.Equal(source, actual);
+ }
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs
index bf381ebda..c70f332ef 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs
@@ -153,9 +153,55 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
}
}
+ private static void RunPredictor12Test()
+ {
+ // arrange
+ uint[] topData = { 4294844413, 4294779388 };
+ uint left = 4294844413;
+ uint expectedResult = 4294779388;
+
+ // act
+ unsafe
+ {
+ fixed (uint* top = &topData[1])
+ {
+ uint actual = LosslessUtils.Predictor12(left, top);
+
+ // assert
+ Assert.Equal(expectedResult, actual);
+ }
+ }
+ }
+
+ private static void RunPredictor13Test()
+ {
+ // arrange
+ uint[] topData = { 4278193922, 4278193666 };
+ uint left = 4278193410;
+ uint expectedResult = 4278193154;
+
+ // act
+ unsafe
+ {
+ fixed (uint* top = &topData[1])
+ {
+ uint actual = LosslessUtils.Predictor13(left, top);
+
+ // assert
+ Assert.Equal(expectedResult, actual);
+ }
+ }
+ }
+
[Fact]
public void Predictor11_Works() => RunPredictor11Test();
+ [Fact]
+ public void Predictor12_Works() => RunPredictor12Test();
+
+ [Fact]
+ public void Predictor13_Works() => RunPredictor13Test();
+
[Fact]
public void SubtractGreen_Works() => RunSubtractGreenTest();
@@ -175,6 +221,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
[Fact]
public void Predictor11_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.DisableSSE2);
+ [Fact]
+ public void Predictor12_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.AllowAll);
+
+ [Fact]
+ public void Predictor12_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.DisableSSE2);
+
+ [Fact]
+ public void Predictor13_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.AllowAll);
+
+ [Fact]
+ public void Predictor13_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.DisableSSE2);
+
[Fact]
public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll);
diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs
new file mode 100644
index 000000000..f8b488fde
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Formats.Webp.Lossy;
+using SixLabors.ImageSharp.Tests.TestUtilities;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.WebP
+{
+ [Trait("Format", "Webp")]
+ public class LossyUtilsTests
+ {
+ private static void RunHadamardTransformTest()
+ {
+ byte[] a =
+ {
+ 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129,
+ 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28,
+ 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26,
+ 26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128,
+ 128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27
+ };
+
+ byte[] b =
+ {
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204,
+ 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204,
+ 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28
+ };
+
+ ushort[] w = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 };
+ int expected = 2;
+
+ int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]);
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void HadamardTransform_Works() => RunHadamardTransformTest();
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ [Fact]
+ public void HadamardTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll);
+
+ [Fact]
+ public void HadamardTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableHWIntrinsic);
+#endif
+
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs
new file mode 100644
index 000000000..55738199b
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Linq;
+using SixLabors.ImageSharp.Formats.Webp.Lossy;
+using SixLabors.ImageSharp.Tests.TestUtilities;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.WebP
+{
+ [Trait("Format", "Webp")]
+ public class QuantEncTests
+ {
+ private static unsafe void RunQuantizeBlockTest()
+ {
+ // arrange
+ short[] input = { 378, 777, -851, 888, 259, 148, 0, -111, -185, -185, -74, -37, 148, 74, 111, 74 };
+ short[] output = new short[16];
+ ushort[] q = { 42, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37 };
+ ushort[] iq = { 3120, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542 };
+ uint[] bias = { 49152, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296 };
+ uint[] zthresh = { 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21 };
+ short[] expectedOutput = { 9, 21, 7, -5, 4, -23, 24, 0, -5, 4, 2, -2, -3, -1, 3, 2 };
+ int expectedResult = 1;
+ Vp8Matrix vp8Matrix = default;
+ for (int i = 0; i < 16; i++)
+ {
+ vp8Matrix.Q[i] = q[i];
+ vp8Matrix.IQ[i] = iq[i];
+ vp8Matrix.Bias[i] = bias[i];
+ vp8Matrix.ZThresh[i] = zthresh[i];
+ }
+
+ // act
+ int actualResult = QuantEnc.QuantizeBlock(input, output, ref vp8Matrix);
+
+ // assert
+ Assert.True(output.SequenceEqual(expectedOutput));
+ Assert.Equal(expectedResult, actualResult);
+ }
+
+ [Fact]
+ public void QuantizeBlock_Works() => RunQuantizeBlockTest();
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+ [Fact]
+ public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll);
+
+ [Fact]
+ public void QuantizeBlock_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableHWIntrinsic);
+#endif
+ }
+}