diff --git a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs index 5f8432dfd..5123fd0a2 100644 --- a/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs +++ b/src/ImageSharp/Colors/Spaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Colors.Spaces.Conversion using System; using ImageSharp.Colors.Spaces; + using ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb; /// /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation. @@ -15,7 +16,7 @@ namespace ImageSharp.Colors.Spaces.Conversion public partial class ColorSpaceConverter { /// - /// Performs chromatic adaptation of given XYZ color. + /// Performs chromatic adaptation of given color. /// Target white point is . /// /// The color to adapt @@ -33,5 +34,73 @@ namespace ImageSharp.Colors.Spaces.Conversion return this.ChromaticAdaptation.Transform(color, sourceWhitePoint, this.WhitePoint); } + + /// + /// Adapts a color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + public LinearRgb Adapt(LinearRgb color) + { + Guard.NotNull(color, nameof(color)); + + if (!this.IsChromaticAdaptationPerformed) + { + throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); + } + + if (color.WorkingSpace.Equals(this.TargetRgbWorkingSpace)) + { + return color; + } + + // Conversion to XYZ + LinearRgbToCieXyzConverter converterToXYZ = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); + CieXyz unadapted = converterToXYZ.Convert(color); + + // Adaptation + CieXyz adapted = this.ChromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint); + + // Conversion back to RGB + CieXyzToLinearRgbConverter converterToRGB = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace); + return converterToRGB.Convert(adapted); + } + + /// + /// Adapts an color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + public Rgb Adapt(Rgb color) + { + Guard.NotNull(color, nameof(color)); + + LinearRgb linearInput = this.ToLinearRgb(color); + LinearRgb linearOutput = this.Adapt(linearInput); + return this.ToRgb(linearOutput); + } + + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLab Adapt(CieLab color) + { + Guard.NotNull(color, nameof(color)); + + if (!this.IsChromaticAdaptationPerformed) + { + throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); + } + + if (color.WhitePoint.Equals(this.TargetLabWhitePoint)) + { + return color; + } + + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Spaces/LinearRgb.cs b/src/ImageSharp/Colors/Spaces/LinearRgb.cs index 049fc5c72..c101d66ec 100644 --- a/src/ImageSharp/Colors/Spaces/LinearRgb.cs +++ b/src/ImageSharp/Colors/Spaces/LinearRgb.cs @@ -40,6 +40,18 @@ namespace ImageSharp.Colors.Spaces { } + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + public LinearRgb(float r, float g, float b, IRgbWorkingSpace workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + /// /// Initializes a new instance of the struct. /// diff --git a/src/ImageSharp/Colors/Spaces/Rgb.cs b/src/ImageSharp/Colors/Spaces/Rgb.cs index db71f8a02..e97b57bee 100644 --- a/src/ImageSharp/Colors/Spaces/Rgb.cs +++ b/src/ImageSharp/Colors/Spaces/Rgb.cs @@ -40,6 +40,18 @@ namespace ImageSharp.Colors.Spaces { } + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + public Rgb(float r, float g, float b, IRgbWorkingSpace workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + /// /// Initializes a new instance of the struct. /// diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs new file mode 100644 index 000000000..bb8fb8f6e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -0,0 +1,34 @@ +namespace ImageSharp.Benchmarks.Color +{ + using BenchmarkDotNet.Attributes; + + using Colourful; + using Colourful.Conversion; + + using ImageSharp.Colors.Spaces; + using ImageSharp.Colors.Spaces.Conversion; + + public class RgbWorkingSpaceAdapt + { + private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb); + + private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717, RGBWorkingSpaces.WideGamutRGB); + + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter { TargetRGBWorkingSpace = RGBWorkingSpaces.sRGB }; + + + [Benchmark(Baseline = true, Description = "Colourful Adapt")] + public RGBColor ColourfulConvert() + { + return ColourfulConverter.Adapt(RGBColor); + } + + [Benchmark(Description = "ImageSharp Adapt")] + public Rgb ColorSpaceConvert() + { + return ColorSpaceConverter.Adapt(Rgb); + } + } +} diff --git a/tests/ImageSharp.Tests/App.config b/tests/ImageSharp.Tests/App.config new file mode 100644 index 000000000..7b47f780e --- /dev/null +++ b/tests/ImageSharp.Tests/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs index e28b451e0..91acb1d93 100644 --- a/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs +++ b/tests/ImageSharp.Tests/Colors/Colorspaces/ColorConverterAdaptTest.cs @@ -12,6 +12,89 @@ namespace ImageSharp.Tests { private static readonly IEqualityComparer FloatComparer = new ApproximateFloatComparer(4); + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.206162, 0.260277, 0.746717, 0.220000, 0.130000, 0.780000)] + public void Adapt_RGB_WideGamutRGB_To_sRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + Rgb input = new Rgb(r1, g1, b1, RgbWorkingSpaces.WideGamutRgb); + Rgb expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.SRgb); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + // Action + Rgb output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(output.R, expectedOutput.R, FloatComparer); + Assert.Equal(output.G, expectedOutput.G, FloatComparer); + Assert.Equal(output.B, expectedOutput.B, FloatComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.220000, 0.130000, 0.780000, 0.206162, 0.260277, 0.746717)] + public void Adapt_RGB_SRGB_To_WideGamutRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + Rgb input = new Rgb(r1, g1, b1, RgbWorkingSpaces.SRgb); + Rgb expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.WideGamutRgb); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.WideGamutRgb }; + + // Action + Rgb output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(output.R, expectedOutput.R, FloatComparer); + Assert.Equal(output.G, expectedOutput.G, FloatComparer); + Assert.Equal(output.B, expectedOutput.B, FloatComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] + public void Adapt_Lab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) + { + // Arrange + CieLab input = new CieLab(l1, a1, b1, Illuminants.D65); + CieLab expectedOutput = new CieLab(l2, a2, b2); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetLabWhitePoint = Illuminants.D50 }; + + // Action + CieLab output = converter.Adapt(input); + + // Assert + Assert.Equal(output.L, expectedOutput.L, FloatComparer); + Assert.Equal(output.A, expectedOutput.A, FloatComparer); + Assert.Equal(output.B, expectedOutput.B, FloatComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] + public void Adapt_XYZ_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(output.X, expectedOutput.X, FloatComparer); + Assert.Equal(output.Y, expectedOutput.Y, FloatComparer); + Assert.Equal(output.Z, expectedOutput.Z, FloatComparer); + } + [Theory] [InlineData(0, 0, 0, 0, 0, 0)] [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] @@ -34,5 +117,28 @@ namespace ImageSharp.Tests Assert.Equal(output.Y, expectedOutput.Y, FloatComparer); Assert.Equal(output.Z, expectedOutput.Z, FloatComparer); } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_XYZ_D65_To_D50_XYZScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XYZScaling), + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(output.X, expectedOutput.X, FloatComparer); + Assert.Equal(output.Y, expectedOutput.Y, FloatComparer); + Assert.Equal(output.Z, expectedOutput.Z, FloatComparer); + } } } \ No newline at end of file