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