From 257ff1929e341e5b1af94d9adf557e5296ece957 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 29 Oct 2021 23:32:13 +1100 Subject: [PATCH 01/54] Use RgbaVector for color backing --- src/ImageSharp/Color/Color.Conversions.cs | 87 ++++++++++++++++--- src/ImageSharp/Color/Color.cs | 74 ++++++++-------- .../Color/ColorTests.CastFrom.cs | 17 +++- .../Color/ColorTests.ConstructFrom.cs | 4 +- 4 files changed, 125 insertions(+), 57 deletions(-) diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index 0455fd26a4..abcb54b807 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -17,56 +17,90 @@ namespace SixLabors.ImageSharp /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgba64 pixel) => this.data = pixel; + public Color(Rgba64 pixel) + { + RgbaVector vector = default; + vector.FromRgba64(pixel); + this.data = vector; + } /// /// 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) + { + RgbaVector vector = default; + vector.FromRgba32(pixel); + this.data = vector; + } /// /// 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) + { + RgbaVector vector = default; + vector.FromArgb32(pixel); + this.data = vector; + } /// /// 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) + { + RgbaVector vector = default; + vector.FromBgra32(pixel); + this.data = vector; + } /// /// 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) + { + RgbaVector vector = default; + vector.FromRgb24(pixel); + this.data = vector; + } /// /// 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) + { + RgbaVector vector = default; + vector.FromBgr24(pixel); + this.data = vector; + } /// /// 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.data = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W); + } /// /// Converts a to . /// /// The . /// The . - public static explicit operator Vector4(Color color) => color.data.ToVector4(); + public static explicit operator Vector4(Color color) => color.data.ToScaledVector4(); /// /// Converts an to . @@ -74,22 +108,47 @@ 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() + { + Rgba32 result = default; + result.FromScaledVector4(this.data.ToScaledVector4()); + return result; + } [MethodImpl(InliningOptions.ShortMethod)] - internal Bgra32 ToBgra32() => this.data.ToBgra32(); + internal Bgra32 ToBgra32() + { + Bgra32 result = default; + result.FromScaledVector4(this.data.ToScaledVector4()); + return result; + } [MethodImpl(InliningOptions.ShortMethod)] - internal Argb32 ToArgb32() => this.data.ToArgb32(); + internal Argb32 ToArgb32() + { + Argb32 result = default; + result.FromScaledVector4(this.data.ToScaledVector4()); + return result; + } [MethodImpl(InliningOptions.ShortMethod)] - internal Rgb24 ToRgb24() => this.data.ToRgb24(); + internal Rgb24 ToRgb24() + { + Rgb24 result = default; + result.FromScaledVector4(this.data.ToScaledVector4()); + return result; + } [MethodImpl(InliningOptions.ShortMethod)] - internal Bgr24 ToBgr24() => this.data.ToBgr24(); + internal Bgr24 ToBgr24() + { + Bgr24 result = default; + result.FromScaledVector4(this.data.ToScaledVector4()); + return result; + } [MethodImpl(InliningOptions.ShortMethod)] internal Vector4 ToVector4() => this.data.ToVector4(); diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index d5eedc160b..9a4df4e629 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -20,26 +20,22 @@ namespace SixLabors.ImageSharp /// public readonly partial struct Color : IEquatable { - private readonly Rgba64 data; + private readonly RgbaVector data; [MethodImpl(InliningOptions.ShortMethod)] private Color(byte r, byte g, byte b, byte a) { - this.data = new Rgba64( - ColorNumerics.UpscaleFrom8BitTo16Bit(r), - ColorNumerics.UpscaleFrom8BitTo16Bit(g), - ColorNumerics.UpscaleFrom8BitTo16Bit(b), - ColorNumerics.UpscaleFrom8BitTo16Bit(a)); + RgbaVector vector = default; + vector.FromRgba32(new(r, g, b, a)); + this.data = vector; } [MethodImpl(InliningOptions.ShortMethod)] private Color(byte r, byte g, byte b) { - this.data = new Rgba64( - ColorNumerics.UpscaleFrom8BitTo16Bit(r), - ColorNumerics.UpscaleFrom8BitTo16Bit(g), - ColorNumerics.UpscaleFrom8BitTo16Bit(b), - ushort.MaxValue); + RgbaVector vector = default; + vector.FromRgba32(new(r, g, b)); + this.data = vector; } /// @@ -52,10 +48,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); /// /// Checks whether two structures are equal. @@ -67,10 +60,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 +71,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 +81,17 @@ 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 => new(pixel.ToScaledVector4()); /// /// Creates a new instance of the struct @@ -207,13 +207,18 @@ namespace SixLabors.ImageSharp /// /// A hexadecimal string representation of the value. [MethodImpl(InliningOptions.ShortMethod)] - public string ToHex() => this.data.ToRgba32().ToHex(); + public string ToHex() + { + Rgba32 rgba = default; + this.data.ToRgba32(ref rgba); + return rgba.ToHex(); + } /// 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. @@ -222,12 +227,12 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel { TPixel pixel = default; - pixel.FromRgba64(this.data); + pixel.FromScaledVector4(this.data.ToScaledVector4()); 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 +245,19 @@ namespace SixLabors.ImageSharp Span destination) where TPixel : unmanaged, IPixel { - ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source); - PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination); + ReadOnlySpan rgbaSpan = MemoryMarshal.Cast(source); + PixelOperations.Instance.From(configuration, rgbaSpan, destination); } /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Color other) - { - return this.data.PackedValue == other.data.PackedValue; - } + public bool Equals(Color other) => this.data.Equals(other.data); /// - 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(); - } + public override int GetHashCode() => this.data.GetHashCode(); } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 38b94f486c..356ef7351e 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.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: Color 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: Color color = source; @@ -88,6 +88,19 @@ namespace SixLabors.ImageSharp.Tests Bgr24 data = color.ToPixel(); Assert.Equal(source, data); } + + [Fact] + public void TPixel() + { + var source = new RgbaVector(1, .1F, .133F, .864F); + + // Act: + var color = Color.FromPixel(source); + + // Assert: + RgbaVector data = color.ToPixel(); + Assert.Equal(source, data); + } } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs index 89276014b0..dd51f3a6c2 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.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); From ef90575a119335314ea69c4cbd556469d91f032f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 Nov 2021 21:42:32 +1100 Subject: [PATCH 02/54] Revert "Use RgbaVector for color backing" This reverts commit 257ff1929e341e5b1af94d9adf557e5296ece957. --- src/ImageSharp/Color/Color.Conversions.cs | 87 +++---------------- src/ImageSharp/Color/Color.cs | 74 ++++++++-------- .../Color/ColorTests.CastFrom.cs | 17 +--- .../Color/ColorTests.ConstructFrom.cs | 4 +- 4 files changed, 57 insertions(+), 125 deletions(-) diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index abcb54b807..0455fd26a4 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -17,90 +17,56 @@ namespace SixLabors.ImageSharp /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgba64 pixel) - { - RgbaVector vector = default; - vector.FromRgba64(pixel); - this.data = vector; - } + public Color(Rgba64 pixel) => this.data = pixel; /// /// Initializes a new instance of the struct. /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgba32 pixel) - { - RgbaVector vector = default; - vector.FromRgba32(pixel); - this.data = vector; - } + public Color(Rgba32 pixel) => this.data = new Rgba64(pixel); /// /// Initializes a new instance of the struct. /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Argb32 pixel) - { - RgbaVector vector = default; - vector.FromArgb32(pixel); - this.data = vector; - } + public Color(Argb32 pixel) => this.data = new Rgba64(pixel); /// /// Initializes a new instance of the struct. /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Bgra32 pixel) - { - RgbaVector vector = default; - vector.FromBgra32(pixel); - this.data = vector; - } + public Color(Bgra32 pixel) => this.data = new Rgba64(pixel); /// /// Initializes a new instance of the struct. /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgb24 pixel) - { - RgbaVector vector = default; - vector.FromRgb24(pixel); - this.data = vector; - } + public Color(Rgb24 pixel) => this.data = new Rgba64(pixel); /// /// Initializes a new instance of the struct. /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Bgr24 pixel) - { - RgbaVector vector = default; - vector.FromBgr24(pixel); - this.data = vector; - } + public Color(Bgr24 pixel) => this.data = new Rgba64(pixel); /// /// Initializes a new instance of the struct. /// /// The containing the color information. [MethodImpl(InliningOptions.ShortMethod)] - public Color(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.data = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W); - } + public Color(Vector4 vector) => this.data = new Rgba64(vector); /// /// Converts a to . /// /// The . /// The . - public static explicit operator Vector4(Color color) => color.data.ToScaledVector4(); + public static explicit operator Vector4(Color color) => color.data.ToVector4(); /// /// Converts an to . @@ -108,47 +74,22 @@ namespace SixLabors.ImageSharp /// The . /// The . [MethodImpl(InliningOptions.ShortMethod)] - public static explicit operator Color(Vector4 source) => new(source); + public static explicit operator Color(Vector4 source) => new Color(source); [MethodImpl(InliningOptions.ShortMethod)] - internal Rgba32 ToRgba32() - { - Rgba32 result = default; - result.FromScaledVector4(this.data.ToScaledVector4()); - return result; - } + internal Rgba32 ToRgba32() => this.data.ToRgba32(); [MethodImpl(InliningOptions.ShortMethod)] - internal Bgra32 ToBgra32() - { - Bgra32 result = default; - result.FromScaledVector4(this.data.ToScaledVector4()); - return result; - } + internal Bgra32 ToBgra32() => this.data.ToBgra32(); [MethodImpl(InliningOptions.ShortMethod)] - internal Argb32 ToArgb32() - { - Argb32 result = default; - result.FromScaledVector4(this.data.ToScaledVector4()); - return result; - } + internal Argb32 ToArgb32() => this.data.ToArgb32(); [MethodImpl(InliningOptions.ShortMethod)] - internal Rgb24 ToRgb24() - { - Rgb24 result = default; - result.FromScaledVector4(this.data.ToScaledVector4()); - return result; - } + internal Rgb24 ToRgb24() => this.data.ToRgb24(); [MethodImpl(InliningOptions.ShortMethod)] - internal Bgr24 ToBgr24() - { - Bgr24 result = default; - result.FromScaledVector4(this.data.ToScaledVector4()); - return result; - } + internal Bgr24 ToBgr24() => this.data.ToBgr24(); [MethodImpl(InliningOptions.ShortMethod)] internal Vector4 ToVector4() => this.data.ToVector4(); diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 9a4df4e629..d5eedc160b 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -20,22 +20,26 @@ namespace SixLabors.ImageSharp /// public readonly partial struct Color : IEquatable { - private readonly RgbaVector data; + private readonly Rgba64 data; [MethodImpl(InliningOptions.ShortMethod)] private Color(byte r, byte g, byte b, byte a) { - RgbaVector vector = default; - vector.FromRgba32(new(r, g, b, a)); - this.data = vector; + this.data = new Rgba64( + ColorNumerics.UpscaleFrom8BitTo16Bit(r), + ColorNumerics.UpscaleFrom8BitTo16Bit(g), + ColorNumerics.UpscaleFrom8BitTo16Bit(b), + ColorNumerics.UpscaleFrom8BitTo16Bit(a)); } [MethodImpl(InliningOptions.ShortMethod)] private Color(byte r, byte g, byte b) { - RgbaVector vector = default; - vector.FromRgba32(new(r, g, b)); - this.data = vector; + this.data = new Rgba64( + ColorNumerics.UpscaleFrom8BitTo16Bit(r), + ColorNumerics.UpscaleFrom8BitTo16Bit(g), + ColorNumerics.UpscaleFrom8BitTo16Bit(b), + ushort.MaxValue); } /// @@ -48,7 +52,10 @@ namespace SixLabors.ImageSharp /// otherwise, false. /// [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Color left, Color right) => left.Equals(right); + public static bool operator ==(Color left, Color right) + { + return left.Equals(right); + } /// /// Checks whether two structures are equal. @@ -60,7 +67,10 @@ namespace SixLabors.ImageSharp /// otherwise, false. /// [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Color left, Color right) => !left.Equals(right); + public static bool operator !=(Color left, Color right) + { + return !left.Equals(right); + } /// /// Creates a from RGBA bytes. @@ -71,7 +81,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(r, g, b, a); + public static Color FromRgba(byte r, byte g, byte b, byte a) => new Color(r, g, b, a); /// /// Creates a from RGB bytes. @@ -81,17 +91,7 @@ namespace SixLabors.ImageSharp /// The blue component (0-255). /// The . [MethodImpl(InliningOptions.ShortMethod)] - 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 => new(pixel.ToScaledVector4()); + public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); /// /// Creates a new instance of the struct @@ -207,18 +207,13 @@ namespace SixLabors.ImageSharp /// /// A hexadecimal string representation of the value. [MethodImpl(InliningOptions.ShortMethod)] - public string ToHex() - { - Rgba32 rgba = default; - this.data.ToRgba32(ref rgba); - return rgba.ToHex(); - } + public string ToHex() => this.data.ToRgba32().ToHex(); /// 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. @@ -227,12 +222,12 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel { TPixel pixel = default; - pixel.FromScaledVector4(this.data.ToScaledVector4()); + 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. @@ -245,19 +240,28 @@ namespace SixLabors.ImageSharp Span destination) where TPixel : unmanaged, IPixel { - ReadOnlySpan rgbaSpan = MemoryMarshal.Cast(source); - PixelOperations.Instance.From(configuration, rgbaSpan, destination); + ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source); + PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination); } /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Color other) => this.data.Equals(other.data); + public bool Equals(Color other) + { + return this.data.PackedValue == other.data.PackedValue; + } /// - public override bool Equals(object obj) => obj is Color other && this.Equals(other); + public override bool Equals(object obj) + { + return obj is Color other && this.Equals(other); + } /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.data.GetHashCode(); + public override int GetHashCode() + { + return this.data.PackedValue.GetHashCode(); + } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 356ef7351e..38b94f486c 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.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: Color 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: Color color = source; @@ -88,19 +88,6 @@ namespace SixLabors.ImageSharp.Tests Bgr24 data = color.ToPixel(); Assert.Equal(source, data); } - - [Fact] - public void TPixel() - { - var source = new RgbaVector(1, .1F, .133F, .864F); - - // Act: - var color = Color.FromPixel(source); - - // Assert: - RgbaVector data = color.ToPixel(); - Assert.Equal(source, data); - } } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs index dd51f3a6c2..89276014b0 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.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); From 2ec17e7c6a31b31fafb75cfd85613681fa4125d6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 Nov 2021 22:39:20 +1100 Subject: [PATCH 03/54] Use box pixel for high precision --- src/ImageSharp/Color/Color.Conversions.cs | 117 +++++++++++++++--- src/ImageSharp/Color/Color.cs | 77 ++++++++---- .../Color/ColorTests.CastTo.cs | 17 ++- 3 files changed, 171 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index 0455fd26a4..424b7dcdfe 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -17,56 +17,85 @@ 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(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 +103,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 d5eedc160b..fe66efcfb5 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,18 @@ 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 + => new(pixel); /// /// Creates a new instance of the struct @@ -213,7 +229,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 +237,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 +261,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.ToVector4().Equals(other.ToVector4()); } /// - 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/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index ee1820de77..d3f3cf126e 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,19 @@ namespace SixLabors.ImageSharp.Tests Bgr24 data = color; Assert.Equal(source, data); } + + [Fact] + public void TPixel() + { + var source = new RgbaVector(1, .1F, .133F, .864F); + + // Act: + var color = Color.FromPixel(source); + + // Assert: + RgbaVector data = color.ToPixel(); + Assert.Equal(source, data); + } } } } From f9212f7adca384b1147af10a38e3ec0d8dcc12d2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 3 Nov 2021 22:38:52 +1100 Subject: [PATCH 04/54] Update tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs Co-authored-by: Anton Firszov --- tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index d3f3cf126e..af35d1f895 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void TPixel() { - var source = new RgbaVector(1, .1F, .133F, .864F); + var source = new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue); // Act: var color = Color.FromPixel(source); From 425600459e96cc5d34857fd9e0de45952fa8e6ae Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 3 Nov 2021 23:49:32 +1100 Subject: [PATCH 05/54] Update Color.Equals --- src/ImageSharp/Color/Color.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index fe66efcfb5..61d6c8e6d5 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -277,7 +277,7 @@ namespace SixLabors.ImageSharp return this.data.PackedValue == other.data.PackedValue; } - return this.ToVector4().Equals(other.ToVector4()); + return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; } /// From 08785103e350266f626b3519b22e3966b4450caa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 4 Nov 2021 12:39:42 +0100 Subject: [PATCH 06/54] Add EntropyPasses default value explicit to 1 --- src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs | 1 + src/ImageSharp/Formats/Webp/WebpEncoder.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index 7dbf49d45e..000de4f88c 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/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index f85f65b635..bdcbb194b1 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; From 947dc8d5ecff64414247ede191452cf8c7a77c26 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 4 Nov 2021 12:40:39 +0100 Subject: [PATCH 07/54] Make sure magick.net and imagesharp use the same configuration --- .../Codecs/EncodeWebp.cs | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 7d3dfe693c..59814f465c 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.Settings.SetDefine(MagickFormat.WebP, "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,18 @@ 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.Settings.SetDefine(MagickFormat.WebP, "exact", false); + this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "quality", 75); + this.webpMagick.Write(memoryStream, defines); } [Benchmark(Description = "ImageSharp Webp Lossless")] @@ -72,7 +102,10 @@ 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, + TransparentColorMode = WebpTransparentColorMode.Clear }); } From 55b67ada2f659463f438303e77d0f1b1de4c47bc Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 4 Nov 2021 21:40:02 +0100 Subject: [PATCH 08/54] Use webpMagick.Quality for the quality parameter --- tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs index 59814f465c..2229849921 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs NearLossless = 100 }; - this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "quality", 75); + this.webpMagick.Quality = 75; this.webpMagick.Write(memoryStream, defines); } @@ -91,8 +91,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs NearLossless = 100 }; - this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "exact", false); - this.webpMagick.Settings.SetDefine(MagickFormat.WebP, "quality", 75); + this.webpMagick.Quality = 75; this.webpMagick.Write(memoryStream, defines); } @@ -105,6 +104,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs FileFormat = WebpFileFormatType.Lossless, Method = WebpEncodingMethod.Level4, NearLossless = false, + + // This is equal to exact = false in libwebp, which is the default. TransparentColorMode = WebpTransparentColorMode.Clear }); } From 2b6dbbce6fb6561a7fbddb0bd08afe69b9349382 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 5 Nov 2021 12:46:53 +0100 Subject: [PATCH 09/54] Update benchmark results --- .../Codecs/DecodeWebp.cs | 49 ++++++++--------- .../Codecs/EncodeWebp.cs | 55 +++++++++---------- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs index 407a4ef3b2..878929823d 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 2229849921..43d8c464ce 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs @@ -110,37 +110,34 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs }); } - /* 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 | */ } } From b9e8f76990206843b485006bac8b9ff2cceb05ed Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 7 Nov 2021 18:07:43 +1100 Subject: [PATCH 10/54] Update FromPixel --- src/ImageSharp/Color/Color.Conversions.cs | 11 +++++++++++ src/ImageSharp/Color/Color.cs | 22 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index 424b7dcdfe..96aa05c961 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -23,6 +23,17 @@ namespace SixLabors.ImageSharp 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. /// diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 61d6c8e6d5..c461d034eb 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -107,7 +107,27 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] public static Color FromPixel(TPixel pixel) where TPixel : unmanaged, IPixel - => new(pixel); + { + // 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 (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 From 5b1720eb8deccd3ea37248111a68df73ce632c3a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 13:27:08 +0100 Subject: [PATCH 11/54] Add sse41 version of Hadamard transform --- .../Formats/Webp/Lossy/LossyUtils.cs | 151 +++++++++++++++++- 1 file changed, 146 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 04ff80b2d9..0993e2a666 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -4,11 +4,15 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Webp.Lossy { - internal static class LossyUtils + internal static unsafe class LossyUtils { [MethodImpl(InliningOptions.ShortMethod)] public static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); @@ -61,11 +65,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static int Vp8Disto16X16(Span a, Span b, Span w) { 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); + d += Vp8Disto4X4(a.Slice(x + y, dataSize), b.Slice(x + y, dataSize), w); } } @@ -75,9 +80,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static int Vp8Disto4X4(Span a, Span b, Span w) { - int sum1 = TTransform(a, w); - int sum2 = TTransform(b, w); - return Math.Abs(sum2 - sum1) >> 5; +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported) + { + int diffSum = TTransformSse41(a, b, w); + return Math.Abs(diffSum) >> 5; + } + else +#endif + { + int sum1 = TTransform(a, w); + int sum2 = TTransform(b, w); + return Math.Abs(sum2 - sum1) >> 5; + } } public static void DC16(Span dst, Span yuv, int offset) @@ -591,6 +606,132 @@ 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 sum = stackalloc int[4]; +#pragma warning disable SA1503 // Braces should not be omitted + fixed (byte* inputAPtr = inputA) + fixed (byte* inputBPtr = inputB) + fixed (ushort* wPtr = w) + fixed (int* outputPtr = sum) + { + // Load and combine inputs. + Vector128 ina0 = Sse2.LoadVector128(inputAPtr); + Vector128 ina1 = Sse2.LoadVector128(inputAPtr + (WebpConstants.Bps * 1)); + Vector128 ina2 = Sse2.LoadVector128(inputAPtr + (WebpConstants.Bps * 2)); + Vector128 ina3 = Sse2.LoadVector128((long*)(inputAPtr + (WebpConstants.Bps * 3))); + Vector128 inb0 = Sse2.LoadVector128(inputBPtr); + Vector128 inb1 = Sse2.LoadVector128(inputBPtr + (WebpConstants.Bps * 1)); + Vector128 inb2 = Sse2.LoadVector128(inputBPtr + (WebpConstants.Bps * 2)); + Vector128 inb3 = Sse2.LoadVector128((long*)(inputBPtr + (WebpConstants.Bps * 3))); + + // 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 = Sse2.LoadVector128(wPtr); + Vector128 w8 = Sse2.LoadVector128(wPtr + 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()); + Sse2.Store(outputPtr, result.AsInt32()); + } + + return sum[3] + sum[2] + sum[1] + sum[0]; +#pragma warning restore SA1503 // Braces should not be omitted + } +#endif + public static void TransformTwo(Span src, Span dst) { TransformOne(src, dst); From d2017933d7042d3757062cfe3134206652ce7b27 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 13:31:11 +0100 Subject: [PATCH 12/54] Add HadamardTransform sse tests --- .../Formats/WebP/LossyUtilsTests.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs new file mode 100644 index 0000000000..6a9a078d7c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -0,0 +1,58 @@ +// 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); + 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_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void HadamardTransform_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableSSE41); + + [Fact] + public void HadamardTransform_WithoutSSE2AndSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableSSE41 | HwIntrinsics.DisableSSE2); +#endif + + } +} From 3a03fad75eaa8464d1bd84cccd307014f9417497 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 14:51:51 +0100 Subject: [PATCH 13/54] Add sse41 version of quantize block --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 176 ++++++++++++++---- 1 file changed, 144 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 2ed4381660..02087ceda4 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -3,13 +3,17 @@ using System; using System.Runtime.CompilerServices; +#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 +21,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 @@ -486,51 +502,147 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static int Quantize2Blocks(Span input, Span output, 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), mtx) << 0; + nz |= QuantizeBlock(input.Slice(1 * 16, 16), output.Slice(1 * 16, 16), mtx) << 1; return nz; } public static int QuantizeBlock(Span input, Span output, 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]) +#pragma warning disable SA1503 // Braces should not be omitted + fixed (ushort* mtxIqPtr = mtx.IQ) + fixed (ushort* mtxQPtr = mtx.Q) + fixed (uint* biasQPtr = mtx.Bias) + fixed (short* inputPtr = input) + fixed (short* outputPtr = output) { - uint q = mtx.Q[j]; - uint iQ = mtx.IQ[j]; - uint b = mtx.Bias[j]; - int level = QuantDiv(coeff, iQ, b); - if (level > MaxLevel) + // Load all inputs. + Vector128 input0 = Sse2.LoadVector128(inputPtr); + Vector128 input8 = Sse2.LoadVector128(inputPtr + 8); + Vector128 iq0 = Sse2.LoadVector128(mtxIqPtr); + Vector128 iq8 = Sse2.LoadVector128(mtxIqPtr + 8); + Vector128 q0 = Sse2.LoadVector128(mtxQPtr); + Vector128 q8 = Sse2.LoadVector128(mtxQPtr + 8); + + // coeff = abs(in) + Vector128 coeff0 = Ssse3.Abs(input0); + Vector128 coeff8 = Ssse3.Abs(input8); + + // 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 = Sse2.LoadVector128(biasQPtr); + Vector128 bias04 = Sse2.LoadVector128(biasQPtr + 4); + Vector128 bias08 = Sse2.LoadVector128(biasQPtr + 8); + Vector128 bias12 = Sse2.LoadVector128(biasQPtr + 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 + Sse2.Store(inputPtr, input0); + Sse2.Store(inputPtr + 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); + Sse2.Store(outputPtr, outZ0.AsInt16()); + Sse2.Store(outputPtr + 8, outZ8.AsInt16()); + Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16()); + + // Detect if all 'out' values are zeroes or not. + Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero); + return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0; + } +#pragma warning restore SA1503 // Braces should not be omitted + } + else +#endif + { + int last = -1; + int n; + for (n = 0; n < 16; ++n) + { + 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. From 020134ad8c15e58621635d4ca4b5fb4c6acdbe89 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 14:52:11 +0100 Subject: [PATCH 14/54] Add QuantizeBlock sse tests --- .../Formats/Webp/Lossy/Vp8Matrix.cs | 9 +++ .../Formats/WebP/QuantEncTests.cs | 56 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs index 4276b887f0..e525e388b8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs @@ -34,6 +34,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Sharpen = new short[16]; } + public Vp8Matrix(ushort[] q, ushort[] iq, uint[] bias, uint[] zThresh, short[] sharpen) + { + this.Q = q; + this.IQ = iq; + this.Bias = bias; + this.ZThresh = zThresh; + this.Sharpen = sharpen; + } + /// /// Gets the quantizer steps. /// diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs new file mode 100644 index 0000000000..280a7902ae --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -0,0 +1,56 @@ +// 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 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; + var vp8Matrix = new Vp8Matrix(q, iq, bias, zthresh, new short[16]); + + // act + int actualResult = QuantEnc.QuantizeBlock(input, output, 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_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void QuantizeBlock_WithoutSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSSE3); + + [Fact] + public void QuantizeBlock_WithoutSSE2AndSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); +#endif + } +} From a628909b8da58e9dbd10bfa3b70e9c8ce66ddc1d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 15:02:08 +0100 Subject: [PATCH 15/54] Add coeff = abs(in) + sharpen --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 02087ceda4..b812909b20 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -516,6 +516,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy fixed (ushort* mtxIqPtr = mtx.IQ) fixed (ushort* mtxQPtr = mtx.Q) fixed (uint* biasQPtr = mtx.Bias) + fixed (short* sharpenPtr = mtx.Sharpen) fixed (short* inputPtr = input) fixed (short* outputPtr = output) { @@ -531,6 +532,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 coeff0 = Ssse3.Abs(input0); Vector128 coeff8 = Ssse3.Abs(input8); + // coeff = abs(in) + sharpen + Vector128 sharpen0 = Sse2.LoadVector128(sharpenPtr); + Vector128 sharpen8 = Sse2.LoadVector128(sharpenPtr + 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) From 765f5a23138ce905056a2e7f69f4a3c0feaf4842 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 16:13:28 +0100 Subject: [PATCH 16/54] Add SSE2 version of Mean16x4 --- .../Formats/Webp/Lossy/Vp8EncIterator.cs | 73 ++++++++++++++++--- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index 79fd8d8543..489977cb82 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -2,6 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -9,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Iterator structure to iterate through macroblocks, pointing to the /// right neighbouring data (samples, predictions, contexts, ...) /// - internal class Vp8EncIterator + internal unsafe class Vp8EncIterator { public const int YOffEnc = 0; @@ -29,6 +33,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private readonly int mbh; +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector128 Mean16x4Mask = Vector128.Create(0x00ff).AsByte(); +#endif + /// /// Stride of the prediction plane(=4*mbw + 1). /// @@ -357,12 +365,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int q = quality; int kThreshold = 8 + ((17 - 8) * q / 100); int k; - uint[] dc = new uint[16]; + Span dc = stackalloc uint[16]; + Span tmp = stackalloc ushort[16]; uint m; uint m2; for (k = 0; k < 16; k += 4) { - this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.AsSpan(k)); + this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4), tmp); } for (m = 0, m2 = 0, k = 0; k < 16; ++k) @@ -823,21 +832,61 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Nz[this.nzIdx] = nz; } - private void Mean16x4(Span input, Span dc) + private void Mean16x4(Span input, Span dc, Span tmp) { - for (int k = 0; k < 4; k++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - uint avg = 0; - for (int y = 0; y < 4; y++) +#pragma warning disable SA1503 // Braces should not be omitted + tmp.Clear(); + fixed (byte* inputPtr = input) + fixed (ushort* tmpPtr = tmp) { - for (int x = 0; x < 4; x++) + Vector128 a0 = Sse2.LoadVector128(inputPtr); + Vector128 a1 = Sse2.LoadVector128(inputPtr + WebpConstants.Bps); + Vector128 a2 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 2)); + Vector128 a3 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 3)); + Vector128 b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte + Vector128 b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8); + Vector128 b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); + Vector128 b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); + Vector128 c0 = Sse2.And(a0, Mean16x4Mask); // lo byte + Vector128 c1 = Sse2.And(a1, Mean16x4Mask); + Vector128 c2 = Sse2.And(a2, Mean16x4Mask); + Vector128 c3 = Sse2.And(a3, Mean16x4Mask); + Vector128 d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); + Vector128 d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); + Vector128 d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32()); + Vector128 d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32()); + Vector128 e0 = Sse2.Add(d0, d1); + Vector128 e1 = Sse2.Add(d2, d3); + Vector128 f0 = Sse2.Add(e0, e1); + Sse2.Store(tmpPtr, f0.AsUInt16()); + } +#pragma warning restore SA1503 // Braces should not be omitted + + dc[0] = (uint)(tmp[1] + tmp[0]); + dc[1] = (uint)(tmp[3] + tmp[2]); + dc[2] = (uint)(tmp[5] + tmp[4]); + dc[3] = (uint)(tmp[7] + tmp[6]); + } + else +#endif + { + for (int k = 0; k < 4; k++) + { + uint avg = 0; + for (int y = 0; y < 4; y++) { - avg += input[x + (y * WebpConstants.Bps)]; + for (int x = 0; x < 4; x++) + { + avg += input[x + (y * WebpConstants.Bps)]; + } } - } - dc[k] = avg; - input = input.Slice(4); // go to next 4x4 block. + dc[k] = avg; + input = input.Slice(4); // go to next 4x4 block. + } } } From 8b8871b3ba75581ee2ff5f3fcb294bd640743136 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 16:39:42 +0100 Subject: [PATCH 17/54] Make Mean16x4 static and move to LossyUtils --- .../Formats/Webp/Lossy/LossyUtils.cs | 68 +++++++++++++++++- .../Formats/Webp/Lossy/Vp8EncIterator.cs | 72 +------------------ 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index d5db3dffa5..c3f6e522ac 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -4,12 +4,20 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Webp.Lossy { - internal static class LossyUtils + internal static unsafe class LossyUtils { +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector128 Mean16x4Mask = Vector128.Create(0x00ff).AsByte(); +#endif + [MethodImpl(InliningOptions.ShortMethod)] public static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); @@ -801,6 +809,64 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); } + public static void Mean16x4(Span input, Span dc, Span tmp) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { +#pragma warning disable SA1503 // Braces should not be omitted + tmp.Clear(); + fixed (byte* inputPtr = input) + fixed (ushort* tmpPtr = tmp) + { + Vector128 a0 = Sse2.LoadVector128(inputPtr); + Vector128 a1 = Sse2.LoadVector128(inputPtr + WebpConstants.Bps); + Vector128 a2 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 2)); + Vector128 a3 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 3)); + Vector128 b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte + Vector128 b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8); + Vector128 b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); + Vector128 b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); + Vector128 c0 = Sse2.And(a0, Mean16x4Mask); // lo byte + Vector128 c1 = Sse2.And(a1, Mean16x4Mask); + Vector128 c2 = Sse2.And(a2, Mean16x4Mask); + Vector128 c3 = Sse2.And(a3, Mean16x4Mask); + Vector128 d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); + Vector128 d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); + Vector128 d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32()); + Vector128 d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32()); + Vector128 e0 = Sse2.Add(d0, d1); + Vector128 e1 = Sse2.Add(d2, d3); + Vector128 f0 = Sse2.Add(e0, e1); + Sse2.Store(tmpPtr, f0.AsUInt16()); + } +#pragma warning restore SA1503 // Braces should not be omitted + + dc[0] = (uint)(tmp[1] + tmp[0]); + dc[1] = (uint)(tmp[3] + tmp[2]); + dc[2] = (uint)(tmp[5] + tmp[4]); + dc[3] = (uint)(tmp[7] + tmp[6]); + } + else +#endif + { + for (int k = 0; k < 4; k++) + { + uint avg = 0; + for (int y = 0; y < 4; y++) + { + for (int x = 0; x < 4; x++) + { + avg += input[x + (y * WebpConstants.Bps)]; + } + } + + dc[k] = avg; + input = input.Slice(4); // go to next 4x4 block. + } + } + } + [MethodImpl(InliningOptions.ShortMethod)] public static uint LoadUv(byte u, byte v) => (uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each). diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index 489977cb82..57e18832ed 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -2,10 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -13,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Iterator structure to iterate through macroblocks, pointing to the /// right neighbouring data (samples, predictions, contexts, ...) /// - internal unsafe class Vp8EncIterator + internal class Vp8EncIterator { public const int YOffEnc = 0; @@ -33,10 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private readonly int mbh; -#if SUPPORTS_RUNTIME_INTRINSICS - private static readonly Vector128 Mean16x4Mask = Vector128.Create(0x00ff).AsByte(); -#endif - /// /// Stride of the prediction plane(=4*mbw + 1). /// @@ -371,10 +363,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy uint m2; for (k = 0; k < 16; k += 4) { - this.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4), tmp); + LossyUtils.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4), tmp); } - for (m = 0, m2 = 0, k = 0; k < 16; ++k) + for (m = 0, m2 = 0, k = 0; k < 16; k++) { m += dc[k]; m2 += dc[k] * dc[k]; @@ -832,64 +824,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.Nz[this.nzIdx] = nz; } - private void Mean16x4(Span input, Span dc, Span tmp) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) - { -#pragma warning disable SA1503 // Braces should not be omitted - tmp.Clear(); - fixed (byte* inputPtr = input) - fixed (ushort* tmpPtr = tmp) - { - Vector128 a0 = Sse2.LoadVector128(inputPtr); - Vector128 a1 = Sse2.LoadVector128(inputPtr + WebpConstants.Bps); - Vector128 a2 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 2)); - Vector128 a3 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 3)); - Vector128 b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte - Vector128 b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8); - Vector128 b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); - Vector128 b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); - Vector128 c0 = Sse2.And(a0, Mean16x4Mask); // lo byte - Vector128 c1 = Sse2.And(a1, Mean16x4Mask); - Vector128 c2 = Sse2.And(a2, Mean16x4Mask); - Vector128 c3 = Sse2.And(a3, Mean16x4Mask); - Vector128 d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); - Vector128 d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); - Vector128 d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32()); - Vector128 d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32()); - Vector128 e0 = Sse2.Add(d0, d1); - Vector128 e1 = Sse2.Add(d2, d3); - Vector128 f0 = Sse2.Add(e0, e1); - Sse2.Store(tmpPtr, f0.AsUInt16()); - } -#pragma warning restore SA1503 // Braces should not be omitted - - dc[0] = (uint)(tmp[1] + tmp[0]); - dc[1] = (uint)(tmp[3] + tmp[2]); - dc[2] = (uint)(tmp[5] + tmp[4]); - dc[3] = (uint)(tmp[7] + tmp[6]); - } - else -#endif - { - for (int k = 0; k < 4; k++) - { - uint avg = 0; - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - avg += input[x + (y * WebpConstants.Bps)]; - } - } - - dc[k] = avg; - input = input.Slice(4); // go to next 4x4 block. - } - } - } - private void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) { int dstIdx = 0; From 984971e1d9aca406cfd41b742da96b2d8447fa1b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 16:48:10 +0100 Subject: [PATCH 18/54] Move yuv related methods to YuvConversion class --- .../Formats/Webp/Lossy/LossyUtils.cs | 31 ------------------- .../Formats/Webp/Lossy/WebpLossyDecoder.cs | 24 +++++++------- .../Formats/Webp/Lossy/YuvConversion.cs | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index c3f6e522ac..b2513feb55 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -867,27 +867,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - [MethodImpl(InliningOptions.ShortMethod)] - public static uint LoadUv(byte u, byte v) => - (uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each). - - [MethodImpl(InliningOptions.ShortMethod)] - public static void YuvToBgr(int y, int u, int v, Span bgr) - { - bgr[0] = (byte)YuvToB(y, u); - bgr[1] = (byte)YuvToG(y, u, v); - bgr[2] = (byte)YuvToR(y, v); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); - - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); - - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); - [MethodImpl(InliningOptions.ShortMethod)] public static byte Avg2(byte a, byte b) => (byte)((a + b + 1) >> 1); @@ -1092,9 +1071,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return WebpLookupTables.Abs0(p1 - p0) > thresh || WebpLookupTables.Abs0(q1 - q0) > thresh; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int MultHi(int v, int coeff) => (v * coeff) >> 8; - [MethodImpl(InliningOptions.ShortMethod)] private static void Store(Span dst, int x, int y, int v) { @@ -1117,13 +1093,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] private static int Mul2(int a) => (a * 35468) >> 16; - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Clip8(int v) - { - int yuvMask = (256 << 6) - 1; - return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255); - } - [MethodImpl(InliningOptions.ShortMethod)] private static void Put8x8uv(byte value, Span dst) { diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index 4f283f9f53..2f78842c63 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -747,21 +747,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int xStep = 3; int lastPixelPair = (len - 1) >> 1; - uint tluv = LossyUtils.LoadUv(topU[0], topV[0]); // top-left sample - uint luv = LossyUtils.LoadUv(curU[0], curV[0]); // left-sample + uint tluv = YuvConversion.LoadUv(topU[0], topV[0]); // top-left sample + uint luv = YuvConversion.LoadUv(curU[0], curV[0]); // left-sample uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - LossyUtils.YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); + YuvConversion.YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); if (bottomY != null) { uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - LossyUtils.YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); + YuvConversion.YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); } for (int x = 1; x <= lastPixelPair; x++) { - uint tuv = LossyUtils.LoadUv(topU[x], topV[x]); // top sample - uint uv = LossyUtils.LoadUv(curU[x], curV[x]); // sample + uint tuv = YuvConversion.LoadUv(topU[x], topV[x]); // top sample + uint uv = YuvConversion.LoadUv(curU[x], curV[x]); // sample // Precompute invariant values associated with first and second diagonals. uint avg = tluv + tuv + luv + uv + 0x00080008u; @@ -770,15 +770,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy uv0 = (diag12 + tluv) >> 1; uint uv1 = (diag03 + tuv) >> 1; int xMul2 = x * 2; - LossyUtils.YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep)); - LossyUtils.YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep)); + YuvConversion.YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep)); + YuvConversion.YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep)); if (bottomY != null) { uv0 = (diag03 + luv) >> 1; uv1 = (diag12 + uv) >> 1; - LossyUtils.YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((xMul2 - 1) * xStep)); - LossyUtils.YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice((xMul2 + 0) * xStep)); + YuvConversion.YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((xMul2 - 1) * xStep)); + YuvConversion.YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice((xMul2 + 0) * xStep)); } tluv = tuv; @@ -788,11 +788,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if ((len & 1) == 0) { uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - LossyUtils.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); + YuvConversion.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); if (bottomY != null) { uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - LossyUtils.YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); + YuvConversion.YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); } } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index ed03c2e71d..24143785ab 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -299,5 +299,36 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); return (uv & ~0xff) == 0 ? uv : uv < 0 ? 0 : 255; } + + [MethodImpl(InliningOptions.ShortMethod)] + public static uint LoadUv(byte u, byte v) => + (uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each). + + [MethodImpl(InliningOptions.ShortMethod)] + public static void YuvToBgr(int y, int u, int v, Span bgr) + { + bgr[0] = (byte)YuvToB(y, u); + bgr[1] = (byte)YuvToG(y, u, v); + bgr[2] = (byte)YuvToR(y, v); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); + + [MethodImpl(InliningOptions.ShortMethod)] + public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); + + [MethodImpl(InliningOptions.ShortMethod)] + public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); + + [MethodImpl(InliningOptions.ShortMethod)] + private static int MultHi(int v, int coeff) => (v * coeff) >> 8; + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Clip8(int v) + { + int yuvMask = (256 << 6) - 1; + return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255); + } } } From 0c96e37ba639d1d44b64840c41f01455a53eb9af Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Nov 2021 17:39:50 +0100 Subject: [PATCH 19/54] Add Mean16x4 sse tests --- .../Formats/Webp/Lossy/LossyUtils.cs | 2 +- .../Formats/WebP/LossyUtilsTests.cs | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index b2513feb55..74448cf528 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy internal static unsafe class LossyUtils { #if SUPPORTS_RUNTIME_INTRINSICS - private static readonly Vector128 Mean16x4Mask = Vector128.Create(0x00ff).AsByte(); + private static readonly Vector128 Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); #endif [MethodImpl(InliningOptions.ShortMethod)] diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs new file mode 100644 index 0000000000..5062f845ba --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -0,0 +1,49 @@ +// 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 LossyUtilsTests + { + private static void RunMean16x4Test() + { + // arrange + byte[] input = + { + 154, 145, 102, 115, 127, 129, 126, 125, 126, 120, 133, 152, 157, 153, 119, 94, 104, 116, 111, 113, + 113, 109, 105, 124, 173, 175, 177, 170, 175, 172, 166, 164, 151, 141, 99, 114, 125, 126, 135, 150, + 133, 115, 127, 149, 141, 168, 100, 54, 110, 117, 115, 116, 119, 115, 117, 130, 174, 174, 174, 157, + 146, 171, 166, 158, 117, 140, 96, 111, 119, 119, 136, 171, 188, 134, 121, 126, 136, 119, 59, 77, + 109, 115, 113, 120, 120, 117, 128, 115, 174, 173, 173, 161, 152, 148, 153, 162, 105, 140, 96, 114, + 115, 122, 141, 173, 190, 190, 142, 106, 151, 78, 66, 141, 110, 117, 123, 136, 118, 124, 127, 114, + 173, 175, 166, 155, 155, 159, 159, 158 + }; + uint[] dc = new uint[4]; + ushort[] tmp = new ushort[8]; + uint[] expectedDc = { 1940, 2139, 2252, 1813 }; + + // act + LossyUtils.Mean16x4(input, dc, tmp); + + // assert + Assert.True(dc.SequenceEqual(expectedDc)); + } + + [Fact] + public void Mean16x4_Works() => RunMean16x4Test(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); + + [Fact] + public void Mean16x4_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.DisableSSE2); +#endif + } +} From 90bab3939770a028a45e3d824dc6949fa124c492 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 8 Nov 2021 16:56:38 +1100 Subject: [PATCH 20/54] Special case La32 and L16 --- src/ImageSharp/Color/Color.Conversions.cs | 22 ++++++++++++++++++++++ src/ImageSharp/Color/Color.cs | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index 96aa05c961..bf7869e53d 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -34,6 +34,28 @@ namespace SixLabors.ImageSharp 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. /// diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index c461d034eb..7c21d62ddf 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -117,6 +117,14 @@ namespace SixLabors.ImageSharp { 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; From 8d19c2881da8da3a7a88a569b6f7784bbc1c210c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 8 Nov 2021 10:41:52 +0100 Subject: [PATCH 21/54] Add sse2 version of Vp8Sse4X4 --- .../Formats/Webp/Lossy/LossyUtils.cs | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index d5db3dffa5..82e2214701 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -4,11 +4,16 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Webp.Lossy { - internal static class LossyUtils + internal static unsafe class LossyUtils { [MethodImpl(InliningOptions.ShortMethod)] public static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); @@ -17,7 +22,57 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static int Vp8Sse16X8(Span a, Span b) => GetSse(a, b, 16, 8); [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Sse4X4(Span a, Span b) => GetSse(a, b, 4, 4); + public static int Vp8Sse4X4(Span a, Span b) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { +#pragma warning disable SA1503 // Braces should not be omitted + Span tmp = stackalloc int[4]; + fixed (byte* aPtr = a) + fixed (byte* bPtr = b) + fixed (int* tmpPtr = tmp) + { + // Load values. + Vector128 a0 = Sse2.LoadVector128(aPtr); + Vector128 a1 = Sse2.LoadVector128(aPtr + WebpConstants.Bps); + Vector128 a2 = Sse2.LoadVector128(aPtr + (WebpConstants.Bps * 2)); + Vector128 a3 = Sse2.LoadVector128(aPtr + (WebpConstants.Bps * 3)); + Vector128 b0 = Sse2.LoadVector128(bPtr); + Vector128 b1 = Sse2.LoadVector128(bPtr + WebpConstants.Bps); + Vector128 b2 = Sse2.LoadVector128(bPtr + (WebpConstants.Bps * 2)); + Vector128 b3 = Sse2.LoadVector128(bPtr + (WebpConstants.Bps * 3)); + + // Combine pair of lines. + Vector128 a01 = Sse2.UnpackLow(a0.AsInt32(), a1.AsInt32()); + Vector128 a23 = Sse2.UnpackLow(a2.AsInt32(), a3.AsInt32()); + Vector128 b01 = Sse2.UnpackLow(b0.AsInt32(), b1.AsInt32()); + Vector128 b23 = Sse2.UnpackLow(b2.AsInt32(), b3.AsInt32()); + + // Convert to 16b. + Vector128 a01s = Sse2.UnpackLow(a01.AsByte(), Vector128.Zero); + Vector128 a23s = Sse2.UnpackLow(a23.AsByte(), Vector128.Zero); + Vector128 b01s = Sse2.UnpackLow(b01.AsByte(), Vector128.Zero); + Vector128 b23s = Sse2.UnpackLow(b23.AsByte(), Vector128.Zero); + + // subtract, square and accumulate. + Vector128 d0 = Sse2.SubtractSaturate(a01s, b01s); + Vector128 d1 = Sse2.SubtractSaturate(a23s, b23s); + Vector128 e0 = Sse2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); + Vector128 e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16()); + Vector128 sum = Sse2.Add(e0, e1); + + Sse2.Store(tmpPtr, sum); + return tmp[3] + tmp[2] + tmp[1] + tmp[0]; + } +#pragma warning restore SA1503 // Braces should not be omitted + } + else +#endif + { + return GetSse(a, b, 4, 4); + } + } [MethodImpl(InliningOptions.ShortMethod)] public static int GetSse(Span a, Span b, int w, int h) From 5c6e08b80c39f3cd4e24774ee66b5b011c41aa00 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 8 Nov 2021 16:02:06 +0100 Subject: [PATCH 22/54] Avoid pinning of vp8 matrix data --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 169 +++++++++--------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index f935bd3ee0..b300b7b5c2 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -537,99 +538,99 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (Sse41.IsSupported) { #pragma warning disable SA1503 // Braces should not be omitted - fixed (ushort* mtxIqPtr = mtx.IQ) - fixed (ushort* mtxQPtr = mtx.Q) - fixed (uint* biasQPtr = mtx.Bias) - fixed (short* sharpenPtr = mtx.Sharpen) + // 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 MemoryMarshal.GetReference(mtx.IQ.AsSpan(0, 8))); + Vector128 iq8 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.IQ.AsSpan(8, 8))); + Vector128 q0 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Q.AsSpan(0, 8))); + Vector128 q8 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Q.AsSpan(8, 8))); + + // coeff = abs(in) + Vector128 coeff0 = Ssse3.Abs(input0); + Vector128 coeff8 = Ssse3.Abs(input8); + + // coeff = abs(in) + sharpen + Vector128 sharpen0 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Sharpen.AsSpan(0, 8))); + Vector128 sharpen8 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Sharpen.AsSpan(8, 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 MemoryMarshal.GetReference(mtx.Bias.AsSpan(0, 4))); + Vector128 bias04 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Bias.AsSpan(4, 4))); + Vector128 bias08 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Bias.AsSpan(8, 4))); + Vector128 bias12 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Bias.AsSpan(12, 4))); + 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()); + fixed (short* inputPtr = input) - fixed (short* outputPtr = output) { - // Load all inputs. - Vector128 input0 = Sse2.LoadVector128(inputPtr); - Vector128 input8 = Sse2.LoadVector128(inputPtr + 8); - Vector128 iq0 = Sse2.LoadVector128(mtxIqPtr); - Vector128 iq8 = Sse2.LoadVector128(mtxIqPtr + 8); - Vector128 q0 = Sse2.LoadVector128(mtxQPtr); - Vector128 q8 = Sse2.LoadVector128(mtxQPtr + 8); - - // coeff = abs(in) - Vector128 coeff0 = Ssse3.Abs(input0); - Vector128 coeff8 = Ssse3.Abs(input8); - - // coeff = abs(in) + sharpen - Vector128 sharpen0 = Sse2.LoadVector128(sharpenPtr); - Vector128 sharpen8 = Sse2.LoadVector128(sharpenPtr + 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 = Sse2.LoadVector128(biasQPtr); - Vector128 bias04 = Sse2.LoadVector128(biasQPtr + 4); - Vector128 bias08 = Sse2.LoadVector128(biasQPtr + 8); - Vector128 bias12 = Sse2.LoadVector128(biasQPtr + 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 Sse2.Store(inputPtr, input0); Sse2.Store(inputPtr + 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); + // 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); + + fixed (short* outputPtr = output) + { Sse2.Store(outputPtr, outZ0.AsInt16()); Sse2.Store(outputPtr + 8, outZ8.AsInt16()); - Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16()); - - // Detect if all 'out' values are zeroes or not. - Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero); - return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0; } + + Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16()); + + // Detect if all 'out' values are zeroes or not. + Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero); + return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0; #pragma warning restore SA1503 // Braces should not be omitted } else From 0c0812de82648be40a35dc63a9b6c914bdcbbbf7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 8 Nov 2021 16:58:40 +0100 Subject: [PATCH 23/54] Avoid pinning input and output data --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index b300b7b5c2..6e25dc003c 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -600,12 +600,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy input0 = Sse2.MultiplyLow(out0, q0.AsInt16()); input8 = Sse2.MultiplyLow(out8, q8.AsInt16()); - fixed (short* inputPtr = input) - { - // in = out * Q - Sse2.Store(inputPtr, input0); - Sse2.Store(inputPtr + 8, input8); - } + // 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 @@ -620,11 +618,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 outZ0 = Sse2.Or(tmpLo, tmp8); Vector128 outZ8 = Sse2.Or(tmpHi, tmp7); - fixed (short* outputPtr = output) - { - Sse2.Store(outputPtr, outZ0.AsInt16()); - Sse2.Store(outputPtr + 8, outZ8.AsInt16()); - } + 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()); From cffa4b0c366a3d80b7e5c315127ae0a27f1ddb8d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 8 Nov 2021 17:00:18 +0100 Subject: [PATCH 24/54] Only test with and without HardwareIntrinsics --- tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index 280a7902ae..d0cdfc1ded 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -44,13 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll); [Fact] - public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void QuantizeBlock_WithoutSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSSE3); - - [Fact] - public void QuantizeBlock_WithoutSSE2AndSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + public void QuantizeBlock_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableHWIntrinsic); #endif } } From c9fc5cdb56a21deaf78ae4eb73a6e8270c951841 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 8 Nov 2021 18:33:24 +0100 Subject: [PATCH 25/54] Collapse AsSpan().Slice(..) calls into AsSpan(..) --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 2 +- src/ImageSharp/Formats/Webp/WebpDecoderCore.cs | 2 +- src/ImageSharp/IO/ChunkedMemoryStream.cs | 4 ++-- .../Processors/Transforms/Resize/ResizeKernelMap.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 987dc150c2..cf3cd7eb14 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/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 44a55a4c65..09071406c5 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/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index b9220c56ab..e28baf879d 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/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index a58c20f687..9cc4680602 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++) From 670e2eeafc14b7c16757f1b909eb552a9e61b1ca Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 9 Nov 2021 11:43:19 +1100 Subject: [PATCH 26/54] Update ColorTests.CastTo.cs --- .../ImageSharp.Tests/Color/ColorTests.CastTo.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index af35d1f895..3003265ca6 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -90,16 +90,25 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void TPixel() + public void GenericPixel() { - var source = new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue); + 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: - RgbaVector data = color.ToPixel(); - Assert.Equal(source, data); + TPixel actual = color.ToPixel(); + Assert.Equal(source, actual); } } } From cb513a905c52e843440f14c70e40fe9192737e91 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 11:05:18 +0100 Subject: [PATCH 27/54] Use fixed sized arrays in Vp8Matrix --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 20 ++++---- .../Formats/Webp/Lossy/Vp8Encoder.cs | 8 +--- .../Formats/Webp/Lossy/Vp8Matrix.cs | 47 +++++-------------- .../Formats/Webp/Lossy/Vp8SegmentInfo.cs | 12 ++--- .../Formats/WebP/QuantEncTests.cs | 17 ++++--- 5 files changed, 41 insertions(+), 63 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 6e25dc003c..4c3a2ff5e3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -541,18 +541,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // 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 MemoryMarshal.GetReference(mtx.IQ.AsSpan(0, 8))); - Vector128 iq8 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.IQ.AsSpan(8, 8))); - Vector128 q0 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Q.AsSpan(0, 8))); - Vector128 q8 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Q.AsSpan(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 MemoryMarshal.GetReference(mtx.Sharpen.AsSpan(0, 8))); - Vector128 sharpen8 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Sharpen.AsSpan(8, 8))); + 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); @@ -569,10 +569,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 out12 = Sse2.UnpackHigh(coeffiQ8L, coeffiQ8H); // out = (coeff * iQ + B) - Vector128 bias00 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Bias.AsSpan(0, 4))); - Vector128 bias04 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Bias.AsSpan(4, 4))); - Vector128 bias08 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Bias.AsSpan(8, 4))); - Vector128 bias12 = Unsafe.As>(ref MemoryMarshal.GetReference(mtx.Bias.AsSpan(12, 4))); + 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(); diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 728574682f..8a4115d216 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 e525e388b8..66c91e44ad 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,50 +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]; - } - - public Vp8Matrix(ushort[] q, ushort[] iq, uint[] bias, uint[] zThresh, short[] sharpen) - { - this.Q = q; - this.IQ = iq; - this.Bias = bias; - this.ZThresh = zThresh; - this.Sharpen = sharpen; - } - - /// - /// 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. @@ -81,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 @@ -115,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 cf2a5c1775..71983055c0 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs @@ -8,19 +8,19 @@ 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; } + 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; /// /// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index d0cdfc1ded..7465c42cef 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -11,22 +11,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Trait("Format", "Webp")] public class QuantEncTests { - private static void RunQuantizeBlockTest() + 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[] 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; - var vp8Matrix = new Vp8Matrix(q, iq, bias, zthresh, new short[16]); + 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, vp8Matrix); From 3c9c1bb23eb63863fcac38ac4478f097d73e1e0f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 11:21:18 +0100 Subject: [PATCH 28/54] Avoid pinning --- .../Formats/Webp/Lossy/LossyUtils.cs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 74448cf528..6de2989bda 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -814,33 +815,28 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { -#pragma warning disable SA1503 // Braces should not be omitted tmp.Clear(); - fixed (byte* inputPtr = input) - fixed (ushort* tmpPtr = tmp) - { - Vector128 a0 = Sse2.LoadVector128(inputPtr); - Vector128 a1 = Sse2.LoadVector128(inputPtr + WebpConstants.Bps); - Vector128 a2 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 2)); - Vector128 a3 = Sse2.LoadVector128(inputPtr + (WebpConstants.Bps * 3)); - Vector128 b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte - Vector128 b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8); - Vector128 b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); - Vector128 b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); - Vector128 c0 = Sse2.And(a0, Mean16x4Mask); // lo byte - Vector128 c1 = Sse2.And(a1, Mean16x4Mask); - Vector128 c2 = Sse2.And(a2, Mean16x4Mask); - Vector128 c3 = Sse2.And(a3, Mean16x4Mask); - Vector128 d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); - Vector128 d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); - Vector128 d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32()); - Vector128 d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32()); - Vector128 e0 = Sse2.Add(d0, d1); - Vector128 e1 = Sse2.Add(d2, d3); - Vector128 f0 = Sse2.Add(e0, e1); - Sse2.Store(tmpPtr, f0.AsUInt16()); - } -#pragma warning restore SA1503 // Braces should not be omitted + Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); + Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16))); + Vector128 a2 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 2, 16))); + Vector128 a3 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 3, 16))); + Vector128 b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte + Vector128 b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8); + Vector128 b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); + Vector128 b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); + Vector128 c0 = Sse2.And(a0, Mean16x4Mask); // lo byte + Vector128 c1 = Sse2.And(a1, Mean16x4Mask); + Vector128 c2 = Sse2.And(a2, Mean16x4Mask); + Vector128 c3 = Sse2.And(a3, Mean16x4Mask); + Vector128 d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); + Vector128 d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); + Vector128 d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32()); + Vector128 d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32()); + Vector128 e0 = Sse2.Add(d0, d1); + Vector128 e1 = Sse2.Add(d2, d3); + Vector128 f0 = Sse2.Add(e0, e1); + ref ushort outputRef = ref MemoryMarshal.GetReference(tmp); + Unsafe.As>(ref outputRef) = f0.AsUInt16(); dc[0] = (uint)(tmp[1] + tmp[0]); dc[1] = (uint)(tmp[3] + tmp[2]); From 6e135cbd79f391f56ee69df0da2b8be505631491 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 12:38:41 +0100 Subject: [PATCH 29/54] Avoid pinning --- .../Formats/Webp/Lossy/LossyUtils.cs | 219 +++++++++--------- 1 file changed, 107 insertions(+), 112 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index b8f232a43b..ee224e0b0b 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -614,120 +615,114 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { Span sum = scratch.Slice(0, 4); sum.Clear(); -#pragma warning disable SA1503 // Braces should not be omitted - fixed (byte* inputAPtr = inputA) - fixed (byte* inputBPtr = inputB) - fixed (ushort* wPtr = w) - fixed (int* outputPtr = sum) - { - // Load and combine inputs. - Vector128 ina0 = Sse2.LoadVector128(inputAPtr); - Vector128 ina1 = Sse2.LoadVector128(inputAPtr + (WebpConstants.Bps * 1)); - Vector128 ina2 = Sse2.LoadVector128(inputAPtr + (WebpConstants.Bps * 2)); - Vector128 ina3 = Sse2.LoadVector128((long*)(inputAPtr + (WebpConstants.Bps * 3))); - Vector128 inb0 = Sse2.LoadVector128(inputBPtr); - Vector128 inb1 = Sse2.LoadVector128(inputBPtr + (WebpConstants.Bps * 1)); - Vector128 inb2 = Sse2.LoadVector128(inputBPtr + (WebpConstants.Bps * 2)); - Vector128 inb3 = Sse2.LoadVector128((long*)(inputBPtr + (WebpConstants.Bps * 3))); - - // 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 = Sse2.LoadVector128(wPtr); - Vector128 w8 = Sse2.LoadVector128(wPtr + 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()); - Sse2.Store(outputPtr, result.AsInt32()); - } + // 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]; -#pragma warning restore SA1503 // Braces should not be omitted } #endif From d6d1868343831184d94482895e5f4d3837e643cf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 12:40:27 +0100 Subject: [PATCH 30/54] Test Hadamard transform only with and without HardwareIntrinsics --- tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 349a0c8fca..f8b488fde5 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -45,13 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void HadamardTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll); [Fact] - public void HadamardTransform_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableSSE2); - - [Fact] - public void HadamardTransform_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableSSE41); - - [Fact] - public void HadamardTransform_WithoutSSE2AndSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableSSE41 | HwIntrinsics.DisableSSE2); + public void HadamardTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableHWIntrinsic); #endif } From 99a3510e279a38a8c7c733d1c29f63fb3772d49d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 12:53:54 +0100 Subject: [PATCH 31/54] Avoid pinning --- .../Formats/Webp/Lossy/LossyUtils.cs | 72 +++++++++---------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 82e2214701..aa35f9673c 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS using System.Numerics; using System.Runtime.Intrinsics; @@ -27,45 +28,40 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { -#pragma warning disable SA1503 // Braces should not be omitted Span tmp = stackalloc int[4]; - fixed (byte* aPtr = a) - fixed (byte* bPtr = b) - fixed (int* tmpPtr = tmp) - { - // Load values. - Vector128 a0 = Sse2.LoadVector128(aPtr); - Vector128 a1 = Sse2.LoadVector128(aPtr + WebpConstants.Bps); - Vector128 a2 = Sse2.LoadVector128(aPtr + (WebpConstants.Bps * 2)); - Vector128 a3 = Sse2.LoadVector128(aPtr + (WebpConstants.Bps * 3)); - Vector128 b0 = Sse2.LoadVector128(bPtr); - Vector128 b1 = Sse2.LoadVector128(bPtr + WebpConstants.Bps); - Vector128 b2 = Sse2.LoadVector128(bPtr + (WebpConstants.Bps * 2)); - Vector128 b3 = Sse2.LoadVector128(bPtr + (WebpConstants.Bps * 3)); - - // Combine pair of lines. - Vector128 a01 = Sse2.UnpackLow(a0.AsInt32(), a1.AsInt32()); - Vector128 a23 = Sse2.UnpackLow(a2.AsInt32(), a3.AsInt32()); - Vector128 b01 = Sse2.UnpackLow(b0.AsInt32(), b1.AsInt32()); - Vector128 b23 = Sse2.UnpackLow(b2.AsInt32(), b3.AsInt32()); - - // Convert to 16b. - Vector128 a01s = Sse2.UnpackLow(a01.AsByte(), Vector128.Zero); - Vector128 a23s = Sse2.UnpackLow(a23.AsByte(), Vector128.Zero); - Vector128 b01s = Sse2.UnpackLow(b01.AsByte(), Vector128.Zero); - Vector128 b23s = Sse2.UnpackLow(b23.AsByte(), Vector128.Zero); - - // subtract, square and accumulate. - Vector128 d0 = Sse2.SubtractSaturate(a01s, b01s); - Vector128 d1 = Sse2.SubtractSaturate(a23s, b23s); - Vector128 e0 = Sse2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); - Vector128 e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16()); - Vector128 sum = Sse2.Add(e0, e1); - - Sse2.Store(tmpPtr, sum); - return tmp[3] + tmp[2] + tmp[1] + tmp[0]; - } -#pragma warning restore SA1503 // Braces should not be omitted + + // Load values. + Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(a)); + Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(a.Slice(WebpConstants.Bps, 8))); + Vector128 a2 = Unsafe.As>(ref MemoryMarshal.GetReference(a.Slice(WebpConstants.Bps * 2, 8))); + Vector128 a3 = Unsafe.As>(ref MemoryMarshal.GetReference(a.Slice(WebpConstants.Bps * 3, 8))); + Vector128 b0 = Unsafe.As>(ref MemoryMarshal.GetReference(b)); + Vector128 b1 = Unsafe.As>(ref MemoryMarshal.GetReference(b.Slice(WebpConstants.Bps, 8))); + Vector128 b2 = Unsafe.As>(ref MemoryMarshal.GetReference(b.Slice(WebpConstants.Bps * 2, 8))); + Vector128 b3 = Unsafe.As>(ref MemoryMarshal.GetReference(b.Slice(WebpConstants.Bps * 3, 8))); + + // Combine pair of lines. + Vector128 a01 = Sse2.UnpackLow(a0.AsInt32(), a1.AsInt32()); + Vector128 a23 = Sse2.UnpackLow(a2.AsInt32(), a3.AsInt32()); + Vector128 b01 = Sse2.UnpackLow(b0.AsInt32(), b1.AsInt32()); + Vector128 b23 = Sse2.UnpackLow(b2.AsInt32(), b3.AsInt32()); + + // Convert to 16b. + Vector128 a01s = Sse2.UnpackLow(a01.AsByte(), Vector128.Zero); + Vector128 a23s = Sse2.UnpackLow(a23.AsByte(), Vector128.Zero); + Vector128 b01s = Sse2.UnpackLow(b01.AsByte(), Vector128.Zero); + Vector128 b23s = Sse2.UnpackLow(b23.AsByte(), Vector128.Zero); + + // subtract, square and accumulate. + Vector128 d0 = Sse2.SubtractSaturate(a01s, b01s); + Vector128 d1 = Sse2.SubtractSaturate(a23s, b23s); + Vector128 e0 = Sse2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); + Vector128 e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16()); + Vector128 sum = Sse2.Add(e0, e1); + + ref int outputRef = ref MemoryMarshal.GetReference(tmp); + Unsafe.As>(ref outputRef) = sum; + return tmp[3] + tmp[2] + tmp[1] + tmp[0]; } else #endif From 42c2cf7a799af7c5a6b504ec6233fc6a7308c030 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 13:40:40 +0100 Subject: [PATCH 32/54] Disable SA1401 in file: Fields should be private --- src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs index 71983055c0..2ce383d9e1 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs @@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Gets the quantization matrix y1. /// +#pragma warning disable SA1401 // Fields should be private public Vp8Matrix Y1; /// @@ -21,6 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// Gets the quantization matrix uv. /// 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. From 8160a0eeb6a7bb5e8dc65ca1827a754d5a0e1e81 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 13:40:54 +0100 Subject: [PATCH 33/54] Pass Vp8Matrix as ref --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 34 +++++++++---------- .../Formats/WebP/QuantEncTests.cs | 2 +- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 4c3a2ff5e3..97ef27d259 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -315,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. @@ -343,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; @@ -370,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) @@ -525,19 +525,18 @@ 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.Slice(0, 16), output.Slice(0, 16), mtx) << 0; - nz |= QuantizeBlock(input.Slice(1 * 16, 16), output.Slice(1 * 16, 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) { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse41.IsSupported) { -#pragma warning disable SA1503 // Braces should not be omitted // Load all inputs. Vector128 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); Vector128 input8 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(8, 8))); @@ -624,10 +623,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16()); - // Detect if all 'out' values are zeroes or not. + // Detect if all 'out' values are zeros or not. Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero); return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0; -#pragma warning restore SA1503 // Braces should not be omitted } else #endif @@ -675,7 +673,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // 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; @@ -696,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] @@ -713,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/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index 7465c42cef..55738199b7 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } // act - int actualResult = QuantEnc.QuantizeBlock(input, output, vp8Matrix); + int actualResult = QuantEnc.QuantizeBlock(input, output, ref vp8Matrix); // assert Assert.True(output.SequenceEqual(expectedOutput)); From 1418e53bfbb719c36d57f4ac46317ca990d2fba2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 14:58:31 +0100 Subject: [PATCH 34/54] Remove not need Clear of tmp buffer --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 408f6f066f..7c262a30ee 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -947,7 +947,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - tmp.Clear(); Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16))); Vector128 a2 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 2, 16))); From 3cfa040b2099a5c91c8b1e15e5f2fd4c440a6f77 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 15:38:20 +0100 Subject: [PATCH 35/54] Use Ssse3.HorizontalAdd --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 14 +++++++------- .../Formats/Webp/Lossy/Vp8EncIterator.cs | 2 +- .../Formats/WebP/LossyUtilsTests.cs | 5 ++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 7c262a30ee..5b27af821d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -942,7 +942,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); } - public static void Mean16x4(Span input, Span dc, Span tmp) + public static void Mean16x4(Span input, Span dc) { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) @@ -966,13 +966,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 e0 = Sse2.Add(d0, d1); Vector128 e1 = Sse2.Add(d2, d3); Vector128 f0 = Sse2.Add(e0, e1); - ref ushort outputRef = ref MemoryMarshal.GetReference(tmp); - Unsafe.As>(ref outputRef) = f0.AsUInt16(); + Vector128 hadd = Ssse3.HorizontalAdd(f0.AsInt16(), f0.AsInt16()); + Vector64 lower = hadd.GetLower(); - dc[0] = (uint)(tmp[1] + tmp[0]); - dc[1] = (uint)(tmp[3] + tmp[2]); - dc[2] = (uint)(tmp[5] + tmp[4]); - dc[3] = (uint)(tmp[7] + tmp[6]); + dc[0] = (uint)lower.GetElement(0); + dc[1] = (uint)lower.GetElement(1); + dc[2] = (uint)lower.GetElement(2); + dc[3] = (uint)lower.GetElement(3); } else #endif diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index 57e18832ed..6279aef656 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -363,7 +363,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy uint m2; for (k = 0; k < 16; k += 4) { - LossyUtils.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4), tmp); + LossyUtils.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4)); } for (m = 0, m2 = 0, k = 0; k < 16; k++) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 16b8e11660..09727293ce 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -25,11 +25,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP 173, 175, 166, 155, 155, 159, 159, 158 }; uint[] dc = new uint[4]; - ushort[] tmp = new ushort[8]; uint[] expectedDc = { 1940, 2139, 2252, 1813 }; // act - LossyUtils.Mean16x4(input, dc, tmp); + LossyUtils.Mean16x4(input, dc); // assert Assert.True(dc.SequenceEqual(expectedDc)); @@ -73,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); [Fact] - public void Mean16x4_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.DisableSSE2); + public void Mean16x4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.DisableHWIntrinsic); [Fact] public void HadamardTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll); From 84732bf14722ef50e01f1fd21c6c86e61a77eae2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 15:39:16 +0100 Subject: [PATCH 36/54] Reverse access to bgr --- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 24143785ab..a9cf876c80 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -307,9 +307,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void YuvToBgr(int y, int u, int v, Span bgr) { - bgr[0] = (byte)YuvToB(y, u); - bgr[1] = (byte)YuvToG(y, u, v); bgr[2] = (byte)YuvToR(y, v); + bgr[1] = (byte)YuvToG(y, u, v); + bgr[0] = (byte)YuvToB(y, u); } [MethodImpl(InliningOptions.ShortMethod)] From 50013d70f28c2d67e1a7e96e61174460e67fbc7f Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Tue, 9 Nov 2021 15:51:02 +0100 Subject: [PATCH 37/54] Update src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs Reverse access to dc Co-authored-by: James Jackson-South --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 5b27af821d..e6a4e61701 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -969,10 +969,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 hadd = Ssse3.HorizontalAdd(f0.AsInt16(), f0.AsInt16()); Vector64 lower = hadd.GetLower(); - dc[0] = (uint)lower.GetElement(0); - dc[1] = (uint)lower.GetElement(1); - dc[2] = (uint)lower.GetElement(2); dc[3] = (uint)lower.GetElement(3); + dc[2] = (uint)lower.GetElement(2); + dc[1] = (uint)lower.GetElement(1); + dc[0] = (uint)lower.GetElement(0); } else #endif From f0cb89e811be0fefc6a5a4d2f76797e7a2d8822c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 16:36:42 +0100 Subject: [PATCH 38/54] Change IsSupported check from SSE2 to Ssse3 --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index e6a4e61701..4ef9c56947 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -945,7 +945,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void Mean16x4(Span input, Span dc) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) + if (Ssse3.IsSupported) { Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16))); From 1452ba00836cca274719844100259606750d56b7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 16:40:55 +0100 Subject: [PATCH 39/54] Remove not needed GetLower --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 4ef9c56947..ac3b1d3806 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -967,12 +967,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 e1 = Sse2.Add(d2, d3); Vector128 f0 = Sse2.Add(e0, e1); Vector128 hadd = Ssse3.HorizontalAdd(f0.AsInt16(), f0.AsInt16()); - Vector64 lower = hadd.GetLower(); - dc[3] = (uint)lower.GetElement(3); - dc[2] = (uint)lower.GetElement(2); - dc[1] = (uint)lower.GetElement(1); - dc[0] = (uint)lower.GetElement(0); + dc[3] = (uint)hadd.GetElement(3); + dc[2] = (uint)hadd.GetElement(2); + dc[1] = (uint)hadd.GetElement(1); + dc[0] = (uint)hadd.GetElement(0); } else #endif From de3140bbc29f4914425564538c849731b531dbeb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 16:58:48 +0100 Subject: [PATCH 40/54] Use Numerics.ReduceSum(sum) --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index c1af2a4534..5b7d4d8981 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -27,8 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - Span tmp = stackalloc int[4]; - // Load values. Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(a)); Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(a.Slice(WebpConstants.Bps, 8))); @@ -58,9 +56,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16()); Vector128 sum = Sse2.Add(e0, e1); - ref int outputRef = ref MemoryMarshal.GetReference(tmp); - Unsafe.As>(ref outputRef) = sum; - return tmp[3] + tmp[2] + tmp[1] + tmp[0]; + return Numerics.ReduceSum(sum); } else #endif @@ -658,9 +654,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// 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))); @@ -765,9 +758,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // 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]; + return Numerics.ReduceSum(result); } #endif From 80a826f506ae94372b488c099969abd95dc6d16e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 17:28:30 +0100 Subject: [PATCH 41/54] Remove not needed clear --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 5b7d4d8981..febca037b5 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -13,7 +13,7 @@ using System.Runtime.Intrinsics.X86; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Webp.Lossy { - internal static unsafe class LossyUtils + internal static class LossyUtils { [MethodImpl(InliningOptions.ShortMethod)] public static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); @@ -771,7 +771,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void TransformOne(Span src, Span dst, Span scratch) { Span tmp = scratch.Slice(0, 16); - tmp.Clear(); int tmpOffset = 0; for (int srcOffset = 0; srcOffset < 4; srcOffset++) { From 5abd7740e81d8d54bd24db235c3f90e1e5d02803 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 18:05:14 +0100 Subject: [PATCH 42/54] Add Vp8Sse4X4 sse tests --- .../Formats/WebP/LossyUtilsTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index f8b488fde5..15b312835d 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -10,6 +10,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Trait("Format", "Webp")] public class LossyUtilsTests { + private static void RunVp8Sse4X4Test() + { + 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, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128 + }; + + byte[] b = + { + 26, 26, 26, 26, 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, 26, 26, 26, 26, 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, 26, 26, 26, + 26, 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, 26, 26, 26, 26, 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 + }; + + int expected = 27; + + int actual = LossyUtils.Vp8Sse4X4(a, b); + + Assert.Equal(expected, actual); + } + private static void RunHadamardTransformTest() { byte[] a = @@ -37,10 +66,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP Assert.Equal(expected, actual); } + [Fact] + public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test(); + [Fact] public void HadamardTransform_Works() => RunHadamardTransformTest(); #if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); + + [Fact] + public void Vp8Sse4X4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableHWIntrinsic); + [Fact] public void HadamardTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll); From 5ead84416dfc37e7fa41a36a9d58e15ac85d4232 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 18:56:30 +0100 Subject: [PATCH 43/54] Use Array.Clear to reset the arrays --- .../Formats/Webp/Lossy/Vp8ModeScore.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs index 1c92a9d2d9..69841b557e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs @@ -97,18 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public void Clear() { - this.YDcLevels.AsSpan().Clear(); - this.YAcLevels.AsSpan().Clear(); - this.UvLevels.AsSpan().Clear(); - this.ModesI4.AsSpan().Clear(); - - for (int i = 0; i < 2; i++) - { - for (int j = 0; j < 3; j++) - { - this.Derr[i, j] = 0; - } - } + Array.Clear(this.YDcLevels, 0, this.YDcLevels.Length); + Array.Clear(this.YAcLevels, 0, this.YAcLevels.Length); + Array.Clear(this.UvLevels, 0, this.UvLevels.Length); + Array.Clear(this.ModesI4, 0, this.ModesI4.Length); + Array.Clear(this.Derr, 0, this.Derr.Length); } public void InitScore() From 7d8225b59a633b08b51e74bbb960d4d52b420a84 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 19:38:12 +0100 Subject: [PATCH 44/54] Use UnpackLow to set the dc values --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index ac3b1d3806..3064ccc030 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -13,7 +13,7 @@ using System.Runtime.Intrinsics.X86; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Webp.Lossy { - internal static unsafe class LossyUtils + internal static class LossyUtils { #if SUPPORTS_RUNTIME_INTRINSICS private static readonly Vector128 Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); @@ -967,11 +967,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 e1 = Sse2.Add(d2, d3); Vector128 f0 = Sse2.Add(e0, e1); Vector128 hadd = Ssse3.HorizontalAdd(f0.AsInt16(), f0.AsInt16()); + Vector128 wide = Sse2.UnpackLow(hadd, Vector128.Zero).AsUInt32(); - dc[3] = (uint)hadd.GetElement(3); - dc[2] = (uint)hadd.GetElement(2); - dc[1] = (uint)hadd.GetElement(1); - dc[0] = (uint)hadd.GetElement(0); + ref uint outputRef = ref MemoryMarshal.GetReference(dc); + Unsafe.As>(ref outputRef) = wide; } else #endif From 7312b1a8389c1824409205a5bbfd4ad14224d9c3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 19:43:49 +0100 Subject: [PATCH 45/54] Dont use slice --- .../Formats/Webp/Lossy/LossyUtils.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index febca037b5..19a71c3e56 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -28,14 +28,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (Sse2.IsSupported) { // Load values. - Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(a)); - Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(a.Slice(WebpConstants.Bps, 8))); - Vector128 a2 = Unsafe.As>(ref MemoryMarshal.GetReference(a.Slice(WebpConstants.Bps * 2, 8))); - Vector128 a3 = Unsafe.As>(ref MemoryMarshal.GetReference(a.Slice(WebpConstants.Bps * 3, 8))); - Vector128 b0 = Unsafe.As>(ref MemoryMarshal.GetReference(b)); - Vector128 b1 = Unsafe.As>(ref MemoryMarshal.GetReference(b.Slice(WebpConstants.Bps, 8))); - Vector128 b2 = Unsafe.As>(ref MemoryMarshal.GetReference(b.Slice(WebpConstants.Bps * 2, 8))); - Vector128 b3 = Unsafe.As>(ref MemoryMarshal.GetReference(b.Slice(WebpConstants.Bps * 3, 8))); + ref byte aRef = ref MemoryMarshal.GetReference(a); + Vector128 a0 = Unsafe.As>(ref aRef); + Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps)); + Vector128 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)); + Vector128 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3)); + ref byte bRef = ref MemoryMarshal.GetReference(b); + Vector128 b0 = Unsafe.As>(ref bRef); + Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps)); + Vector128 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)); + Vector128 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3)); // Combine pair of lines. Vector128 a01 = Sse2.UnpackLow(a0.AsInt32(), a1.AsInt32()); From 3dd7c8ea41709173759b02eff4c51268eb2c9f33 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 20:55:23 +0100 Subject: [PATCH 46/54] Remove unnecessary Clear() and scratch buffer --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 4 ++-- src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 19a71c3e56..cb839559fa 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse41.IsSupported) { - int diffSum = TTransformSse41(a, b, w, scratch); + int diffSum = TTransformSse41(a, b, w); return Math.Abs(diffSum) >> 5; } else @@ -654,7 +654,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// 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) + public static int TTransformSse41(Span inputA, Span inputB, Span w) { // Load and combine inputs. Vector128 ina0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA)); diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs index 7192fa2d05..6e724e4758 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs @@ -49,7 +49,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.distribution.AsSpan().Clear(); for (j = startBlock; j < endBlock; j++) { - this.output.AsSpan().Clear(); this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), this.output); // Convert coefficients to bin. From 5630b25733e98b004b6a0bfe8996cbac47b6c304 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 9 Nov 2021 21:58:52 +0100 Subject: [PATCH 47/54] Remove more unnecessary Clear's --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 4 ---- src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs | 3 --- 2 files changed, 7 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 97ef27d259..d0baa260cc 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -340,8 +340,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); Span tmp = it.Scratch2.AsSpan(0, 16); Span scratch = it.Scratch3.AsSpan(0, 16); - tmp.Clear(); - scratch.Clear(); Vp8Encoding.FTransform(src, reference, tmp, scratch); int nz = QuantizeBlock(tmp, levels, ref dqm.Y1); Vp8Encoding.ITransform(reference, tmp, yuvOut, false, scratch); @@ -357,8 +355,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int n; Span tmp = it.Scratch2.AsSpan(0, 8 * 16); Span scratch = it.Scratch3.AsSpan(0, 16); - tmp.Clear(); - scratch.Clear(); for (n = 0; n < 8; n += 2) { diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 0567a0f27d..af7e8eaa36 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -81,7 +81,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int i; Span tmp = scratch.Slice(0, 16); - tmp.Clear(); for (i = 0; i < 4; i++) { // vertical pass. @@ -124,7 +123,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { int i; Span tmp = scratch.Slice(0, 16); - tmp.Clear(); int srcIdx = 0; int refIdx = 0; @@ -163,7 +161,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void FTransformWht(Span input, Span output, Span scratch) { Span tmp = scratch.Slice(0, 16); - tmp.Clear(); int i; int inputIdx = 0; From 6885d97c8834f3cf29b89380a5cd4cc3d05a9d79 Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Tue, 9 Nov 2021 19:59:36 -0800 Subject: [PATCH 48/54] hoist some calculations out --- .../Quantization/WuQuantizer{TPixel}.cs | 26 +++++++++++++++++++ .../Codecs/EncodeIndexedPng.cs | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index bf4a5ca41e..9b82631632 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -418,18 +418,44 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int r = 1; r < IndexCount; r++) { volumeSpan.Clear(); +#if ALTERNATE + int ind1_r = (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + r; +#endif for (int g = 1; g < IndexCount; g++) { areaSpan.Clear(); +#if ALTERNATE + int ind1_g = ind1_r + + (g << (IndexBits + IndexAlphaBits)) + + (g << IndexBits) + + g; + + int r_g = r + g; +#endif + for (int b = 1; b < IndexCount; b++) { +#if ALTERNATE + int ind1_b = ind1_g + + ((r_g + b) << IndexAlphaBits) + + b; +#endif + Moment line = default; for (int a = 1; a < IndexAlphaCount; a++) { +#if ALTERNATE + int ind1 = ind1_b + a; +#else int ind1 = GetPaletteIndex(r, g, b, a); +#endif line += momentSpan[ind1]; areaSpan[a] += line; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs index de8d412029..b339222627 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void PngCoreWuNoDither() { using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette }; this.bmpCore.SaveAsPng(memoryStream, options); } } From 7e20c5daaadefdd3c1073088bc74f1adf0d3436b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 10 Nov 2021 12:10:46 +0100 Subject: [PATCH 49/54] Rename Vp8Sse methods --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 13 ++++++++----- .../Formats/WebP/LossyUtilsTests.cs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index d019b5cd54..a10ec6eabb 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -19,14 +19,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static readonly Vector128 Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); #endif + // Note: method name in libwebp reference implementation is called VP8SSE16x16. [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Sse16X16(Span a, Span b) => GetSse(a, b, 16, 16); + public static int Vp8_Sse16X16(Span a, Span b) => Vp8_SseNxN(a, b, 16, 16); + // Note: method name in libwebp reference implementation is called VP8SSE16x8. [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Sse16X8(Span a, Span b) => GetSse(a, b, 16, 8); + public static int Vp8_Sse16X8(Span a, Span b) => Vp8_SseNxN(a, b, 16, 8); + // Note: method name in libwebp reference implementation is called VP8SSE4x4. [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Sse4X4(Span a, Span b) + public static int Vp8_Sse4X4(Span a, Span b) { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) @@ -67,12 +70,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy else #endif { - return GetSse(a, b, 4, 4); + return Vp8_SseNxN(a, b, 4, 4); } } [MethodImpl(InliningOptions.ShortMethod)] - public static int GetSse(Span a, Span b, int w, int h) + public static int Vp8_SseNxN(Span a, Span b, int w, int h) { int count = 0; int aOffset = 0; diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 9d7545c321..d176a5933d 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP int expected = 27; - int actual = LossyUtils.Vp8Sse4X4(a, b); + int actual = LossyUtils.Vp8_Sse4X4(a, b); Assert.Equal(expected, actual); } From 1997d595e7d496c031e861b8f094a3ba05f94fd0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 10 Nov 2021 12:14:08 +0100 Subject: [PATCH 50/54] Fix build error due to renaming --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index d0baa260cc..38ed80590d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy rdCur.Nz = (uint)ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); // Measure RD-score. - rdCur.D = LossyUtils.Vp8Sse16X16(src, tmpDst); + rdCur.D = LossyUtils.Vp8_Sse16X16(src, tmpDst); rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY, scratch)) : 0; rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; rdCur.R = it.GetCostLuma16(rdCur, proba, res); @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); // Compute RD-score. - rdTmp.D = LossyUtils.Vp8Sse4X4(src, tmpDst); + rdTmp.D = LossyUtils.Vp8_Sse4X4(src, tmpDst); rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY, scratch)) : 0; rdTmp.H = modeCosts[mode]; @@ -251,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode); // Compute RD-score - rdUv.D = LossyUtils.Vp8Sse16X8(src, tmpDst); + rdUv.D = LossyUtils.Vp8_Sse16X8(src, tmpDst); rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; rdUv.R = it.GetCostUv(rdUv, proba, res); @@ -407,7 +407,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - long score = (LossyUtils.Vp8Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); + long score = (LossyUtils.Vp8_Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) { @@ -454,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (mode = 0; mode < WebpConstants.NumBModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - long score = (LossyUtils.Vp8Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); + long score = (LossyUtils.Vp8_Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); if (score < bestI4Score) { bestI4Mode = mode; @@ -503,7 +503,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) { Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - long score = (LossyUtils.Vp8Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); + long score = (LossyUtils.Vp8_Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); if (score < bestUvScore) { bestMode = mode; From 55040a094b97a2941a6de5452b93d407a1af7f89 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 11 Nov 2021 12:52:07 +1100 Subject: [PATCH 51/54] Update codcov and config --- ImageSharp.sln | 5 +++-- codecov.yml | 11 +++++++++++ shared-infrastructure | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index c188d93150..f16f98ac59 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28902.138 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1 ci-build.ps1 = ci-build.ps1 ci-pack.ps1 = ci-pack.ps1 ci-test.ps1 = ci-test.ps1 + codecov.yml = codecov.yml Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets LICENSE = LICENSE diff --git a/codecov.yml b/codecov.yml index 833fc0a51a..310eefb8c2 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,3 +9,14 @@ codecov: # Avoid Report Expired # https://docs.codecov.io/docs/codecov-yaml#section-expired-reports max_report_age: off + +coverage: + # Use integer precision + # https://docs.codecov.com/docs/codecovyml-reference#coverageprecision + precision: 0 + + # Explicitly control coverage status checks + # https://docs.codecov.com/docs/commit-status#disabling-a-status + status: + project: on + patch: off diff --git a/shared-infrastructure b/shared-infrastructure index a042aba176..ac1f5ee0ca 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 +Subproject commit ac1f5ee0ca70c070ecdda8771198a052623ac247 From 3ac8b2b713f97d2840e022dd4eebfc5ba2738cf9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 11 Nov 2021 13:22:12 +1100 Subject: [PATCH 52/54] Use shared config --- shared-infrastructure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-infrastructure b/shared-infrastructure index ac1f5ee0ca..33cb12ca77 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit ac1f5ee0ca70c070ecdda8771198a052623ac247 +Subproject commit 33cb12ca77f919b44de56f344d2627cc2a108c3a From c0d5dfbac59d1ce6937f3be61ccb0841f59e702d Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Wed, 10 Nov 2021 23:55:01 -0800 Subject: [PATCH 53/54] Better variable names --- .../Quantization/WuQuantizer{TPixel}.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 9b82631632..2824f53eef 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -417,45 +417,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int r = 1; r < IndexCount; r++) { - volumeSpan.Clear(); -#if ALTERNATE - int ind1_r = (r << ((IndexBits * 2) + IndexAlphaBits)) + + // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the + // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 + // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations + // in the form of ind1* manually. + int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1)) + (r << (IndexBits * 2)) + (r << (IndexBits + 1)) + r; -#endif + + volumeSpan.Clear(); for (int g = 1; g < IndexCount; g++) { - areaSpan.Clear(); - -#if ALTERNATE - int ind1_g = ind1_r + + int ind1G = ind1R + (g << (IndexBits + IndexAlphaBits)) + (g << IndexBits) + g; - int r_g = r + g; -#endif + + areaSpan.Clear(); for (int b = 1; b < IndexCount; b++) { -#if ALTERNATE - int ind1_b = ind1_g + + int ind1B = ind1G + ((r_g + b) << IndexAlphaBits) + b; -#endif Moment line = default; for (int a = 1; a < IndexAlphaCount; a++) { -#if ALTERNATE - int ind1 = ind1_b + a; -#else - int ind1 = GetPaletteIndex(r, g, b, a); -#endif + int ind1 = ind1B + a; + line += momentSpan[ind1]; areaSpan[a] += line; From 57357b076a939011b3a80f5f83700e90e0899c9a Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Fri, 12 Nov 2021 07:41:08 -0800 Subject: [PATCH 54/54] Optimize Mark --- .../Quantization/WuQuantizer{TPixel}.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 2824f53eef..4218810d77 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -649,13 +649,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int r = cube.RMin + 1; r <= cube.RMax; r++) { + // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the + // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 + // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations + // in the form of ind1* manually. + int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + r; + for (int g = cube.GMin + 1; g <= cube.GMax; g++) { + int ind1G = ind1R + + (g << (IndexBits + IndexAlphaBits)) + + (g << IndexBits) + + g; + int r_g = r + g; + for (int b = cube.BMin + 1; b <= cube.BMax; b++) { + int ind1B = ind1G + + ((r_g + b) << IndexAlphaBits) + + b; + for (int a = cube.AMin + 1; a <= cube.AMax; a++) { - tagSpan[GetPaletteIndex(r, g, b, a)] = label; + int index = ind1B + a; + + tagSpan[index] = label; } } }