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/18] 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/18] 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/18] 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 fd07436736d721bedfbafc308d902aa1e7765778 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 12:40:04 +0100 Subject: [PATCH 04/18] Replace Guard with DebugGuard in FastSLog2Slow --- src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 22c2333607..ebebe79547 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -780,7 +780,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static float FastSLog2Slow(uint v) { - Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); if (v < ApproxLogWithCorrectionMax) { int logCnt = 0; From 2bf16bcb58556d6f3cbee5298472db42af60bd02 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 12:41:43 +0100 Subject: [PATCH 05/18] Reverse access to output array to remove bounds checks --- src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index ebebe79547..b278b12bc9 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -1262,11 +1262,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 pb = Sse2.UnpackLow(bc, Vector128.Zero); // |b - c| Vector128 diff = Sse2.Subtract(pb.AsUInt16(), pa.AsUInt16()); Sse2.Store((ushort*)p, diff); + int paMinusPb = output[3] + output[2] + output[1] + output[0]; + return (paMinusPb <= 0) ? a : b; } - - int paMinusPb = output[0] + output[1] + output[2] + output[3]; - - return (paMinusPb <= 0) ? a : b; } else #endif From a7ed1884e0f9439c03d913f4d4a5f2b36d38071e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 14:15:13 +0100 Subject: [PATCH 06/18] Add sse2 version of ClampedAddSubtractHalf --- .../Formats/Webp/Lossless/LosslessUtils.cs | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index b278b12bc9..0dda5a79a6 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -1219,12 +1219,32 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) { - uint ave = Average2(c0, c1); - int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24)); - int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); - int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); - int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero); + Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero); + Vector128 b0 = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); + Vector128 avg = Sse2.Add(c1Vec.AsInt16(), c0Vec.AsInt16()); + Vector128 a0 = Sse2.ShiftRightLogical(avg, 1); + Vector128 a1 = Sse2.Subtract(a0, b0.AsInt16()); + Vector128 bgta = Sse2.CompareGreaterThan(b0.AsInt16(), a0.AsInt16()); + Vector128 a2 = Sse2.Subtract(a1, bgta); + Vector128 a3 = Sse2.ShiftRightArithmetic(a2.AsInt16(), 1); + Vector128 a4 = Sse2.Add(a0.AsInt16(), a3).AsInt16(); + Vector128 a5 = Sse2.PackUnsignedSaturate(a4, a4); + uint output = Sse2.ConvertToUInt32(a5.AsUInt32()); + return output; + } +#endif + { + uint ave = Average2(c0, c1); + int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24)); + int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); + int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); + int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; + } } [MethodImpl(InliningOptions.ShortMethod)] From 28053739a9beeed006fd256a0ea8016631660841 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 14:20:33 +0100 Subject: [PATCH 07/18] Add sse2 version of ClampedAddSubtractFull --- .../Formats/Webp/Lossless/LosslessUtils.cs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 0dda5a79a6..7740dc0515 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -1201,20 +1201,34 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) { - int a = AddSubtractComponentFull( - (int)(c0 >> 24), - (int)(c1 >> 24), - (int)(c2 >> 24)); - int r = AddSubtractComponentFull( - (int)((c0 >> 16) & 0xff), - (int)((c1 >> 16) & 0xff), - (int)((c2 >> 16) & 0xff)); - int g = AddSubtractComponentFull( - (int)((c0 >> 8) & 0xff), - (int)((c1 >> 8) & 0xff), - (int)((c2 >> 8) & 0xff)); - int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero); + Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero); + Vector128 c2Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); + Vector128 v1 = Sse2.Add(c0Vec, c1Vec); + Vector128 v2 = Sse2.Subtract(v1, c2Vec); + Vector128 b = Sse2.PackUnsignedSaturate(v2.AsInt16(), v2.AsInt16()); + uint output = Sse2.ConvertToUInt32(b.AsUInt32()); + } +#endif + { + int a = AddSubtractComponentFull( + (int)(c0 >> 24), + (int)(c1 >> 24), + (int)(c2 >> 24)); + int r = AddSubtractComponentFull( + (int)((c0 >> 16) & 0xff), + (int)((c1 >> 16) & 0xff), + (int)((c2 >> 16) & 0xff)); + int g = AddSubtractComponentFull( + (int)((c0 >> 8) & 0xff), + (int)((c1 >> 8) & 0xff), + (int)((c2 >> 8) & 0xff)); + int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; + } } private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) From f6dbc7dd8ee95115315805dab2b9b38684e505b2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 14:40:59 +0100 Subject: [PATCH 08/18] Fix issue in ClampedAddSubtractFull --- src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 7740dc0515..65b39bd2d7 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -1207,10 +1207,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero); Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero); Vector128 c2Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); - Vector128 v1 = Sse2.Add(c0Vec, c1Vec); - Vector128 v2 = Sse2.Subtract(v1, c2Vec); + Vector128 v1 = Sse2.Add(c0Vec.AsInt16(), c1Vec.AsInt16()); + Vector128 v2 = Sse2.Subtract(v1, c2Vec.AsInt16()); Vector128 b = Sse2.PackUnsignedSaturate(v2.AsInt16(), v2.AsInt16()); uint output = Sse2.ConvertToUInt32(b.AsUInt32()); + return output; } #endif { From 8fe280e9918e14ca2abb7ffd21ae35c969429447 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 16:04:29 +0100 Subject: [PATCH 09/18] Add predictor 12 and 13 tests --- .../Formats/WebP/LosslessUtilsTests.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index bf381ebdaa..c70f332ef6 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -153,9 +153,55 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + private static void RunPredictor12Test() + { + // arrange + uint[] topData = { 4294844413, 4294779388 }; + uint left = 4294844413; + uint expectedResult = 4294779388; + + // act + unsafe + { + fixed (uint* top = &topData[1]) + { + uint actual = LosslessUtils.Predictor12(left, top); + + // assert + Assert.Equal(expectedResult, actual); + } + } + } + + private static void RunPredictor13Test() + { + // arrange + uint[] topData = { 4278193922, 4278193666 }; + uint left = 4278193410; + uint expectedResult = 4278193154; + + // act + unsafe + { + fixed (uint* top = &topData[1]) + { + uint actual = LosslessUtils.Predictor13(left, top); + + // assert + Assert.Equal(expectedResult, actual); + } + } + } + [Fact] public void Predictor11_Works() => RunPredictor11Test(); + [Fact] + public void Predictor12_Works() => RunPredictor12Test(); + + [Fact] + public void Predictor13_Works() => RunPredictor13Test(); + [Fact] public void SubtractGreen_Works() => RunSubtractGreenTest(); @@ -175,6 +221,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void Predictor11_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Predictor12_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.AllowAll); + + [Fact] + public void Predictor12_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.DisableSSE2); + + [Fact] + public void Predictor13_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.AllowAll); + + [Fact] + public void Predictor13_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.DisableSSE2); + [Fact] public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll); From ffdf99bad2d8f4fb9d52a3938f3c64d750f09957 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 16:29:52 +0100 Subject: [PATCH 10/18] Add aggressive inlining --- src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs | 8 ++++++++ src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs | 1 + 2 files changed, 9 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs index 8596d85558..02bbc38fcf 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Webp.Lossless { /// @@ -41,6 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Inserts a new color into the cache. /// /// The color to insert. + [MethodImpl(InliningOptions.ShortMethod)] public void Insert(uint bgra) { int key = HashPix(bgra, this.HashShift); @@ -52,6 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// The key to lookup. /// The color for the key. + [MethodImpl(InliningOptions.ShortMethod)] public uint Lookup(int key) => this.Colors[key]; /// @@ -59,6 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// The color to check. /// The index of the color in the cache or -1 if its not present. + [MethodImpl(InliningOptions.ShortMethod)] public int Contains(uint bgra) { int key = HashPix(bgra, this.HashShift); @@ -70,6 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// The color. /// The index for the color. + [MethodImpl(InliningOptions.ShortMethod)] public int GetIndex(uint bgra) => HashPix(bgra, this.HashShift); /// @@ -77,8 +83,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// The key. /// The color to add. + [MethodImpl(InliningOptions.ShortMethod)] public void Set(uint key, uint bgra) => this.Colors[key] = bgra; + [MethodImpl(InliningOptions.ShortMethod)] public static int HashPix(uint argb, int shift) => (int)((argb * HashMul) >> shift); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 65b39bd2d7..9baa6c3c33 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -752,6 +752,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Fast calculation of log2(v) for integer input. /// + [MethodImpl(InliningOptions.ShortMethod)] public static float FastLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); /// From fc8d8b81d98201955655595fe682a0c5533eb6ea Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Nov 2021 21:56:19 +0100 Subject: [PATCH 11/18] Remove unnecessary cast AsInt16() --- src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 9baa6c3c33..8bd3163ccb 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -1210,7 +1210,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 c2Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); Vector128 v1 = Sse2.Add(c0Vec.AsInt16(), c1Vec.AsInt16()); Vector128 v2 = Sse2.Subtract(v1, c2Vec.AsInt16()); - Vector128 b = Sse2.PackUnsignedSaturate(v2.AsInt16(), v2.AsInt16()); + Vector128 b = Sse2.PackUnsignedSaturate(v2, v2); uint output = Sse2.ConvertToUInt32(b.AsUInt32()); return output; } From f9212f7adca384b1147af10a38e3ec0d8dcc12d2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 3 Nov 2021 22:38:52 +1100 Subject: [PATCH 12/18] 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 13/18] 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 d6d952e477b0653b2750210ad4cd2d3fc14bbaec Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 4 Nov 2021 23:12:01 +0100 Subject: [PATCH 14/18] Remove another unnecessary cast AsInt16() --- src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 8bd3163ccb..ee9ea51237 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -1246,8 +1246,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 a1 = Sse2.Subtract(a0, b0.AsInt16()); Vector128 bgta = Sse2.CompareGreaterThan(b0.AsInt16(), a0.AsInt16()); Vector128 a2 = Sse2.Subtract(a1, bgta); - Vector128 a3 = Sse2.ShiftRightArithmetic(a2.AsInt16(), 1); - Vector128 a4 = Sse2.Add(a0.AsInt16(), a3).AsInt16(); + Vector128 a3 = Sse2.ShiftRightArithmetic(a2, 1); + Vector128 a4 = Sse2.Add(a0, a3).AsInt16(); Vector128 a5 = Sse2.PackUnsignedSaturate(a4, a4); uint output = Sse2.ConvertToUInt32(a5.AsUInt32()); return output; From b9e8f76990206843b485006bac8b9ff2cceb05ed Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 7 Nov 2021 18:07:43 +1100 Subject: [PATCH 15/18] 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 af90336173a1ee20a6c894c113e5f799b139bf9f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Nov 2021 15:25:47 +0100 Subject: [PATCH 16/18] stackalloc header buffer in InternalDetectFormat --- src/ImageSharp/Image.Decode.cs | 51 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 94da2c9958..ee340bf86e 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -58,31 +58,42 @@ namespace SixLabors.ImageSharp return null; } - using (IMemoryOwner buffer = config.MemoryAllocator.Allocate(headerSize, AllocationOptions.Clean)) + // Header sizes are so small, that headersBuffer will be always stackalloc-ed in practice, + // and heap allocation will never happen, there is no need for the usual try-finally ArrayPool dance. + // The array case is only a safety mechanism following stackalloc best practices. + Span headersBuffer = headerSize > 512 ? new byte[headerSize] : stackalloc byte[headerSize]; + long startPosition = stream.Position; + + // Read doesn't always guarantee the full returned length so read a byte + // at a time until we get either our count or hit the end of the stream. + int n = 0; + int i; + do { - Span bufferSpan = buffer.GetSpan(); - long startPosition = stream.Position; + i = stream.Read(headersBuffer, n, headerSize - n); + n += i; + } + while (n < headerSize && i > 0); - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do + stream.Position = startPosition; + + // Does the given stream contain enough data to fit in the header for the format + // and does that data match the format specification? + // Individual formats should still check since they are public. + IImageFormat format = null; + foreach (IImageFormatDetector formatDetector in config.ImageFormatsManager.FormatDetectors) + { + if (formatDetector.HeaderSize <= headerSize) { - i = stream.Read(bufferSpan, n, headerSize - n); - n += i; + IImageFormat attemptFormat = formatDetector.DetectFormat(headersBuffer); + if (attemptFormat != null) + { + format = attemptFormat; + } } - while (n < headerSize && i > 0); - - stream.Position = startPosition; - - // Does the given stream contain enough data to fit in the header for the format - // and does that data match the format specification? - // Individual formats should still check since they are public. - return config.ImageFormatsManager.FormatDetectors - .Where(x => x.HeaderSize <= headerSize) - .Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); } + + return format; } /// From 90bab3939770a028a45e3d824dc6949fa124c492 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 8 Nov 2021 16:56:38 +1100 Subject: [PATCH 17/18] 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 670e2eeafc14b7c16757f1b909eb552a9e61b1ca Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 9 Nov 2021 11:43:19 +1100 Subject: [PATCH 18/18] 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); } } }