mirror of https://github.com/SixLabors/ImageSharp
18 changed files with 927 additions and 12 deletions
@ -0,0 +1,66 @@ |
|||
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion |
|||
{ |
|||
using ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb; |
|||
|
|||
/// <summary>
|
|||
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
|
|||
/// </summary>
|
|||
public partial class ColorSpaceConverter |
|||
{ |
|||
private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter(); |
|||
|
|||
private CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter; |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Rgb"/> into a <see cref="LinearRgb"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="LinearRgb"/></returns>
|
|||
public LinearRgb ToLinearRgb(Rgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
// Conversion
|
|||
return RgbToLinearRgbConverter.Convert(color); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieXyz"/> into a <see cref="LinearRgb"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="LinearRgb"/></returns>
|
|||
public LinearRgb ToLinearRgb(CieXyz color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
// Adaptation
|
|||
CieXyz adapted = this.TargetRgbWorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed |
|||
? color |
|||
: this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint); |
|||
|
|||
// Conversion
|
|||
CieXyzToLinearRgbConverter xyzConverter = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace); |
|||
return xyzConverter.Convert(adapted); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the correct converter for the given rgb working space.
|
|||
/// </summary>
|
|||
/// <param name="workingSpace">The target working space</param>
|
|||
/// <returns>The <see cref="CieXyzToLinearRgbConverter"/></returns>
|
|||
private CieXyzToLinearRgbConverter GetCieXyxToLinearRgbConverter(IRgbWorkingSpace workingSpace) |
|||
{ |
|||
if (this.cieXyzToLinearRgbConverter != null && this.cieXyzToLinearRgbConverter.TargetWorkingSpace.Equals(workingSpace)) |
|||
{ |
|||
return this.cieXyzToLinearRgbConverter; |
|||
} |
|||
|
|||
return this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(workingSpace); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// <copyright file="ColorSpaceConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion |
|||
{ |
|||
using ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb; |
|||
|
|||
/// <summary>
|
|||
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
|
|||
/// </summary>
|
|||
public partial class ColorSpaceConverter |
|||
{ |
|||
private static readonly LinearRgbToRgbConverter LinearRgbToRgbConverter = new LinearRgbToRgbConverter(); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="LinearRgb"/> into a <see cref="Rgb"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Rgb"/></returns>
|
|||
public Rgb ToRgb(LinearRgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
// Conversion
|
|||
return LinearRgbToRgbConverter.Convert(color); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieXyz"/> into a <see cref="Rgb"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Rgb"/></returns>
|
|||
public Rgb ToRgb(CieXyz color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
// Conversion
|
|||
LinearRgb linear = this.ToLinearRgb(color); |
|||
|
|||
// Compand
|
|||
return this.ToRgb(linear); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// <copyright file="LinearRgbToRgbConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using Rgb = ImageSharp.Colors.Spaces.Rgb; |
|||
|
|||
/// <summary>
|
|||
/// Color converter between CieXyz and LinearRgb
|
|||
/// </summary>
|
|||
internal class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase, IColorConversion<CieXyz, LinearRgb> |
|||
{ |
|||
private readonly Matrix4x4 conversionMatrix; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyzToLinearRgbConverter"/> class.
|
|||
/// </summary>
|
|||
public CieXyzToLinearRgbConverter() |
|||
: this(Rgb.DefaultWorkingSpace) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyzToLinearRgbConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="workingSpace">The target working space.</param>
|
|||
public CieXyzToLinearRgbConverter(IRgbWorkingSpace workingSpace) |
|||
{ |
|||
this.TargetWorkingSpace = workingSpace; |
|||
this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the target working space
|
|||
/// </summary>
|
|||
public IRgbWorkingSpace TargetWorkingSpace { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public LinearRgb Convert(CieXyz input) |
|||
{ |
|||
DebugGuard.NotNull(input, nameof(input)); |
|||
|
|||
Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix); |
|||
return new LinearRgb(vector, this.TargetWorkingSpace); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// <copyright file="LinearRgbAndCieXyzConverterBase.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
/// <summary>
|
|||
/// Provides base methods for converting between Rgb and CieXyz color spaces.
|
|||
/// </summary>
|
|||
internal abstract class LinearRgbAndCieXyzConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Geturns the correct matrix to convert between the Rgb and CieXyz color space.
|
|||
/// </summary>
|
|||
/// <param name="workingSpace">The Rgb working space.</param>
|
|||
/// <returns>The <see cref="Matrix4x4"/> based on the chromaticity and working space.</returns>
|
|||
public static Matrix4x4 GetRgbToCieXyzMatrix(IRgbWorkingSpace workingSpace) |
|||
{ |
|||
DebugGuard.NotNull(workingSpace, nameof(workingSpace)); |
|||
|
|||
RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; |
|||
|
|||
float xr = chromaticity.R.X; |
|||
float xg = chromaticity.G.X; |
|||
float xb = chromaticity.B.X; |
|||
float yr = chromaticity.R.Y; |
|||
float yg = chromaticity.G.Y; |
|||
float yb = chromaticity.B.Y; |
|||
|
|||
float mXr = xr / yr; |
|||
const float Yr = 1; |
|||
float mZr = (1 - xr - yr) / yr; |
|||
|
|||
float mXg = xg / yg; |
|||
const float Yg = 1; |
|||
float mZg = (1 - xg - yg) / yg; |
|||
|
|||
float mXb = xb / yb; |
|||
const float Yb = 1; |
|||
float mZb = (1 - xb - yb) / yb; |
|||
|
|||
Matrix4x4 xyzMatrix = new Matrix4x4 |
|||
{ |
|||
M11 = mXr, M12 = mXg, M13 = mXb, |
|||
M21 = Yr, M22 = Yg, M23 = Yb, |
|||
M31 = mZr, M32 = mZg, M33 = mZb, |
|||
M44 = 1F |
|||
}; |
|||
|
|||
Matrix4x4 inverseXyzMatrix; |
|||
Matrix4x4.Invert(xyzMatrix, out inverseXyzMatrix); |
|||
|
|||
Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix); |
|||
|
|||
// TODO: Is there a built in method for this?
|
|||
return new Matrix4x4 |
|||
{ |
|||
M11 = vector.X * mXr, M12 = vector.Y * mXg, M13 = vector.Z * mXb, |
|||
M21 = vector.X * Yr, M22 = vector.Y * Yg, M23 = vector.Z * Yb, |
|||
M31 = vector.X * mZr, M32 = vector.Y * mZg, M33 = vector.Z * mZb, |
|||
M44 = 1F |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// <copyright file="LinearRgbToCieXyzConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using Rgb = ImageSharp.Colors.Spaces.Rgb; |
|||
|
|||
/// <summary>
|
|||
/// Color converter between LinearRgb and CieXyz
|
|||
/// </summary>
|
|||
internal class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase, IColorConversion<LinearRgb, CieXyz> |
|||
{ |
|||
private readonly Matrix4x4 conversionMatrix; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearRgbToCieXyzConverter"/> class.
|
|||
/// </summary>
|
|||
public LinearRgbToCieXyzConverter() |
|||
: this(Rgb.DefaultWorkingSpace) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearRgbToCieXyzConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="workingSpace">The target working space.</param>
|
|||
public LinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace) |
|||
{ |
|||
this.SourceWorkingSpace = workingSpace; |
|||
this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the source working space
|
|||
/// </summary>
|
|||
public IRgbWorkingSpace SourceWorkingSpace { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz Convert(LinearRgb input) |
|||
{ |
|||
DebugGuard.NotNull(input, nameof(input)); |
|||
Guard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal."); |
|||
|
|||
Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix); |
|||
return new CieXyz(vector); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// <copyright file="LinearRgbToRgbConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using Rgb = ImageSharp.Colors.Spaces.Rgb; |
|||
|
|||
/// <summary>
|
|||
/// Color converter between LinearRgb and Rgb
|
|||
/// </summary>
|
|||
public class LinearRgbToRgbConverter : IColorConversion<LinearRgb, Rgb> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public Rgb Convert(LinearRgb input) |
|||
{ |
|||
DebugGuard.NotNull(input, nameof(input)); |
|||
|
|||
Vector3 vector = input.Vector; |
|||
vector.X = input.WorkingSpace.Companding.Compress(vector.X); |
|||
vector.Y = input.WorkingSpace.Companding.Compress(vector.Y); |
|||
vector.Z = input.WorkingSpace.Companding.Compress(vector.Z); |
|||
|
|||
return new Rgb(vector, input.WorkingSpace); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// <copyright file="RgbToLinearRgbConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces.Conversion.Implementation.Rgb |
|||
{ |
|||
using System.Numerics; |
|||
|
|||
using Rgb = ImageSharp.Colors.Spaces.Rgb; |
|||
|
|||
/// <summary>
|
|||
/// Color converter between Rgb and LinearRgb
|
|||
/// </summary>
|
|||
public class RgbToLinearRgbConverter : IColorConversion<Rgb, LinearRgb> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public LinearRgb Convert(Rgb input) |
|||
{ |
|||
Guard.NotNull(input, nameof(input)); |
|||
|
|||
Vector3 vector = input.Vector; |
|||
vector.X = input.WorkingSpace.Companding.Expand(vector.X); |
|||
vector.Y = input.WorkingSpace.Companding.Expand(vector.Y); |
|||
vector.Z = input.WorkingSpace.Companding.Expand(vector.Z); |
|||
|
|||
return new LinearRgb(vector, input.WorkingSpace); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
// <copyright file="LinearRgb.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces |
|||
{ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
|
|||
/// <summary>
|
|||
/// Represents an linear Rgb color with specified <see cref="IRgbWorkingSpace"/> working space
|
|||
/// </summary>
|
|||
public struct LinearRgb : IColorVector, IEquatable<LinearRgb>, IAlmostEquatable<LinearRgb, float> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="LinearRgb"/> that has R, G, and B values set to zero.
|
|||
/// </summary>
|
|||
public static readonly LinearRgb Empty = default(LinearRgb); |
|||
|
|||
/// <summary>
|
|||
/// The default LinearRgb working space
|
|||
/// </summary>
|
|||
private static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; |
|||
|
|||
/// <summary>
|
|||
/// The backing vector for SIMD support.
|
|||
/// </summary>
|
|||
private readonly Vector3 backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="r">The red component ranging between 0 and 1.</param>
|
|||
/// <param name="g">The green component ranging between 0 and 1.</param>
|
|||
/// <param name="b">The blue component ranging between 0 and 1.</param>
|
|||
public LinearRgb(float r, float g, float b) |
|||
: this(new Vector3(r, g, b)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the r, g, b components.</param>
|
|||
public LinearRgb(Vector3 vector) |
|||
: this(vector, DefaultWorkingSpace) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearRgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the r, g, b components.</param>
|
|||
/// <param name="workingSpace">The LinearRgb working space.</param>
|
|||
public LinearRgb(Vector3 vector, IRgbWorkingSpace workingSpace) |
|||
: this() |
|||
{ |
|||
// Clamp to 0-1 range.
|
|||
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); |
|||
this.WorkingSpace = workingSpace; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the red component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float R => this.backingVector.X; |
|||
|
|||
/// <summary>
|
|||
/// Gets the green component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float G => this.backingVector.Y; |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float B => this.backingVector.Z; |
|||
|
|||
/// <summary>
|
|||
/// Gets the LinearRgb color space <seealso cref="RgbWorkingSpaces"/>
|
|||
/// </summary>
|
|||
public IRgbWorkingSpace WorkingSpace { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="LinearRgb"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <inheritdoc />
|
|||
public Vector3 Vector => this.backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="LinearRgb"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="LinearRgb"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="LinearRgb"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator ==(LinearRgb left, LinearRgb right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="LinearRgb"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="LinearRgb"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="LinearRgb"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator !=(LinearRgb left, LinearRgb right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.backingVector.GetHashCode(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
if (this.IsEmpty) |
|||
{ |
|||
return "LinearRgb [ Empty ]"; |
|||
} |
|||
|
|||
return $"LinearRgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is LinearRgb) |
|||
{ |
|||
return this.Equals((LinearRgb)obj); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(LinearRgb other) |
|||
{ |
|||
return this.backingVector.Equals(other.backingVector); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool AlmostEquals(LinearRgb other, float precision) |
|||
{ |
|||
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); |
|||
|
|||
return result.X <= precision |
|||
&& result.Y <= precision |
|||
&& result.Z <= precision; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
// <copyright file="Rgb.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Colors.Spaces |
|||
{ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
|
|||
/// <summary>
|
|||
/// Represents an RGB color with specified <see cref="IRgbWorkingSpace"/> working space
|
|||
/// </summary>
|
|||
public struct Rgb : IColorVector, IEquatable<Rgb>, IAlmostEquatable<Rgb, float> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Rgb"/> that has R, G, and B values set to zero.
|
|||
/// </summary>
|
|||
public static readonly Rgb Empty = default(Rgb); |
|||
|
|||
/// <summary>
|
|||
/// The default rgb working space
|
|||
/// </summary>
|
|||
internal static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; |
|||
|
|||
/// <summary>
|
|||
/// The backing vector for SIMD support.
|
|||
/// </summary>
|
|||
private readonly Vector3 backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="r">The red component ranging between 0 and 1.</param>
|
|||
/// <param name="g">The green component ranging between 0 and 1.</param>
|
|||
/// <param name="b">The blue component ranging between 0 and 1.</param>
|
|||
public Rgb(float r, float g, float b) |
|||
: this(new Vector3(r, g, b)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the r, g, b components.</param>
|
|||
public Rgb(Vector3 vector) |
|||
: this(vector, DefaultWorkingSpace) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the r, g, b components.</param>
|
|||
/// <param name="workingSpace">The rgb working space.</param>
|
|||
public Rgb(Vector3 vector, IRgbWorkingSpace workingSpace) |
|||
: this() |
|||
{ |
|||
// Clamp to 0-1 range.
|
|||
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); |
|||
this.WorkingSpace = workingSpace; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the red component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float R => this.backingVector.X; |
|||
|
|||
/// <summary>
|
|||
/// Gets the green component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float G => this.backingVector.Y; |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float B => this.backingVector.Z; |
|||
|
|||
/// <summary>
|
|||
/// Gets the Rgb color space <seealso cref="RgbWorkingSpaces"/>
|
|||
/// </summary>
|
|||
public IRgbWorkingSpace WorkingSpace { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="Rgb"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <inheritdoc />
|
|||
public Vector3 Vector => this.backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rgb"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Rgb"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Rgb"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator ==(Rgb left, Rgb right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rgb"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Rgb"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Rgb"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator !=(Rgb left, Rgb right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.backingVector.GetHashCode(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
if (this.IsEmpty) |
|||
{ |
|||
return "Rgb [ Empty ]"; |
|||
} |
|||
|
|||
return $"Rgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is Rgb) |
|||
{ |
|||
return this.Equals((Rgb)obj); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(Rgb other) |
|||
{ |
|||
return this.backingVector.Equals(other.backingVector); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool AlmostEquals(Rgb other, float precision) |
|||
{ |
|||
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); |
|||
|
|||
return result.X <= precision |
|||
&& result.Y <= precision |
|||
&& result.Z <= precision; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
namespace ImageSharp.Benchmarks.Color |
|||
{ |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using Colourful; |
|||
using Colourful.Conversion; |
|||
|
|||
using ImageSharp.Colors.Spaces; |
|||
|
|||
using ColorSpaceConverter = ImageSharp.Colors.Spaces.Conversion.ColorSpaceConverter; |
|||
|
|||
public class ColorspaceCieXyzToRgbConvert |
|||
{ |
|||
private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); |
|||
|
|||
private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); |
|||
|
|||
private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); |
|||
|
|||
private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); |
|||
|
|||
|
|||
[Benchmark(Baseline = true, Description = "Colourful Convert")] |
|||
public RGBColor ColourfulConvert() |
|||
{ |
|||
return ColourfulConverter.ToRGB(XYZColor); |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Convert")] |
|||
public Rgb ColorSpaceConvert() |
|||
{ |
|||
return ColorSpaceConverter.ToRgb(CieXyz); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
namespace ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using ImageSharp.Colors.Spaces; |
|||
using ImageSharp.Colors.Spaces.Conversion; |
|||
|
|||
using Xunit; |
|||
|
|||
/// <summary>
|
|||
/// Tests <see cref="CieXyz"/>-<see cref="CieLab"/> conversions.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Test data generated using:
|
|||
/// http://www.brucelindbloom.com/index.html?ColorCalculator.html
|
|||
/// </remarks>
|
|||
public class RgbAndCieXyzConversionTest |
|||
{ |
|||
private static readonly IEqualityComparer<float> FloatComparerPrecision = new ApproximateFloatComparer(6); |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion from <see cref="CieXyz"/> (<see cref="Illuminants.D50"/>)
|
|||
/// to <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>).
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] |
|||
[InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] |
|||
[InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] |
|||
[InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] |
|||
[InlineData(0, 0, 0, 0, 0, 0)] |
|||
[InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] |
|||
public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b) |
|||
{ |
|||
// Arrange
|
|||
CieXyz input = new CieXyz(x, y, z); |
|||
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; |
|||
|
|||
// Act
|
|||
Rgb output = converter.ToRgb(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(output.WorkingSpace, Rgb.DefaultWorkingSpace); |
|||
Assert.Equal(output.R, r, FloatComparerPrecision); |
|||
Assert.Equal(output.G, g, FloatComparerPrecision); |
|||
Assert.Equal(output.B, b, FloatComparerPrecision); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion
|
|||
/// from <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>)
|
|||
/// to <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>).
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] |
|||
[InlineData(0, 1.000000, 0, 0, 1, 0)] |
|||
[InlineData(0.950470, 0, 0, 1, 0, 0.254967)] |
|||
[InlineData(0, 0, 1.088830, 0, 0.235458, 1)] |
|||
[InlineData(0, 0, 0, 0, 0, 0)] |
|||
[InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] |
|||
public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) |
|||
{ |
|||
// Arrange
|
|||
CieXyz input = new CieXyz(x, y, z); |
|||
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; |
|||
|
|||
// Act
|
|||
Rgb output = converter.ToRgb(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(output.WorkingSpace, Rgb.DefaultWorkingSpace); |
|||
Assert.Equal(output.R, r, FloatComparerPrecision); |
|||
Assert.Equal(output.G, g, FloatComparerPrecision); |
|||
Assert.Equal(output.B, b, FloatComparerPrecision); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion from <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>)
|
|||
/// to <see cref="CieXyz"/> (<see cref="Illuminants.D50"/>).
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] |
|||
[InlineData(0, 0, 0, 0, 0, 0)] |
|||
[InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] |
|||
[InlineData(0, 1, 0, 0.385065, 0.716879, 0.097105)] |
|||
[InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] |
|||
[InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] |
|||
public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z) |
|||
{ |
|||
// Arrange
|
|||
Rgb input = new Rgb(r, g, b); |
|||
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50 }; |
|||
|
|||
// Act
|
|||
CieXyz output = converter.ToCieXyz(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(output.X, x, FloatComparerPrecision); |
|||
Assert.Equal(output.Y, y, FloatComparerPrecision); |
|||
Assert.Equal(output.Z, z, FloatComparerPrecision); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion from <see cref="Rgb"/> (<see cref="Rgb.DefaultWorkingSpace">default sRGB working space</see>)
|
|||
/// to <see cref="CieXyz"/> (<see cref="Illuminants.D65"/>).
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] |
|||
[InlineData(0, 0, 0, 0, 0, 0)] |
|||
[InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] |
|||
[InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] |
|||
[InlineData(0, 0, 1, 0.180437, 0.072175, 0.950304)] |
|||
[InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] |
|||
public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z) |
|||
{ |
|||
// Arrange
|
|||
Rgb input = new Rgb(r, g, b); |
|||
ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; |
|||
|
|||
// Act
|
|||
CieXyz output = converter.ToCieXyz(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(output.X, x, FloatComparerPrecision); |
|||
Assert.Equal(output.Y, y, FloatComparerPrecision); |
|||
Assert.Equal(output.Z, z, FloatComparerPrecision); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue