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 + } +}