mirror of https://github.com/SixLabors/ImageSharp
15 changed files with 706 additions and 190 deletions
@ -0,0 +1,149 @@ |
|||
// <copyright file="Cmyk.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 CMYK (cyan, magenta, yellow, keyline) color.
|
|||
/// </summary>
|
|||
public struct Cmyk : IEquatable<Cmyk>, IAlmostEquatable<Cmyk, float> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Cmyk"/> that has C, M, Y, and K values set to zero.
|
|||
/// </summary>
|
|||
public static readonly Cmyk Empty = default(Cmyk); |
|||
|
|||
/// <summary>
|
|||
/// The backing vector for SIMD support.
|
|||
/// </summary>
|
|||
private readonly Vector4 backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="c">The cyan component.</param>
|
|||
/// <param name="m">The magenta component.</param>
|
|||
/// <param name="y">The yellow component.</param>
|
|||
/// <param name="k">The keyline black component.</param>
|
|||
public Cmyk(float c, float m, float y, float k) |
|||
: this() |
|||
{ |
|||
this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), Vector4.Zero, Vector4.One); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the cyan color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float C => this.backingVector.X; |
|||
|
|||
/// <summary>
|
|||
/// Gets the magenta color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float M => this.backingVector.Y; |
|||
|
|||
/// <summary>
|
|||
/// Gets the yellow color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y => this.backingVector.Z; |
|||
|
|||
/// <summary>
|
|||
/// Gets the keyline black color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float K => this.backingVector.W; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Cmyk"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Cmyk"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Cmyk"/> 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 ==(Cmyk left, Cmyk right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Cmyk"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Cmyk"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Cmyk"/> 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 !=(Cmyk left, Cmyk right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.backingVector.GetHashCode(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
if (this.IsEmpty) |
|||
{ |
|||
return "Cmyk [Empty]"; |
|||
} |
|||
|
|||
return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is Cmyk) |
|||
{ |
|||
return this.Equals((Cmyk)obj); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(Cmyk other) |
|||
{ |
|||
return this.backingVector.Equals(other.backingVector); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool AlmostEquals(Cmyk other, float precision) |
|||
{ |
|||
Vector4 result = Vector4.Abs(this.backingVector - other.backingVector); |
|||
|
|||
return result.X <= precision |
|||
&& result.Y <= precision |
|||
&& result.Z <= precision |
|||
&& result.W <= precision; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
// <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; |
|||
using ImageSharp.Colors.Spaces.Conversion.Implementation.Cmyk; |
|||
|
|||
/// <summary>
|
|||
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
|
|||
/// </summary>
|
|||
public partial class ColorSpaceConverter |
|||
{ |
|||
private static readonly CmykAndRgbConverter CmykAndRgbConverter = new CmykAndRgbConverter(); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieLab"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(CieLab color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCmyk(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieLch"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(CieLch color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCmyk(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieXyy"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(CieXyy color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCmyk(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieXyz"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(CieXyz color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
Rgb rgb = this.ToRgb(color); |
|||
|
|||
return CmykAndRgbConverter.Convert(rgb); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="HunterLab"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(HunterLab color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCmyk(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="LinearRgb"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(LinearRgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
Rgb rgb = this.ToRgb(color); |
|||
|
|||
return CmykAndRgbConverter.Convert(rgb); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Lms"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(Lms color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToCmyk(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Rgb"/> into a <see cref="Cmyk"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Cmyk"/></returns>
|
|||
public Cmyk ToCmyk(Rgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
return CmykAndRgbConverter.Convert(color); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// <copyright file="CmykAndRgbConverter.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.Cmyk |
|||
{ |
|||
using System; |
|||
|
|||
using ImageSharp.Colors.Spaces; |
|||
|
|||
/// <summary>
|
|||
/// Color converter between CMYK and Rgb
|
|||
/// </summary>
|
|||
internal class CmykAndRgbConverter : IColorConversion<Cmyk, Rgb>, IColorConversion<Rgb, Cmyk> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public Rgb Convert(Cmyk input) |
|||
{ |
|||
float r = (1F - input.C) * (1F - input.K); |
|||
float g = (1F - input.M) * (1F - input.K); |
|||
float b = (1F - input.Y) * (1F - input.K); |
|||
|
|||
return new Rgb(r, g, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Cmyk Convert(Rgb input) |
|||
{ |
|||
// To CMYK
|
|||
float c = 1F - input.R; |
|||
float m = 1F - input.G; |
|||
float y = 1F - input.B; |
|||
|
|||
// To CMYK
|
|||
float k = MathF.Min(c, MathF.Min(m, y)); |
|||
|
|||
if (Math.Abs(k - 1F) < Constants.Epsilon) |
|||
{ |
|||
return new Cmyk(0, 0, 0, 1F); |
|||
} |
|||
|
|||
c = (c - k) / (1F - k); |
|||
m = (m - k) / (1F - k); |
|||
y = (y - k) / (1F - k); |
|||
|
|||
return new Cmyk(c, m, y, k); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
namespace ImageSharp.Tests.Colors.Colorspaces |
|||
{ |
|||
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:
|
|||
/// <see href="http://www.colorhexa.com"/>
|
|||
/// <see href="http://www.rapidtables.com/convert/color/cmyk-to-rgb.htm"/>
|
|||
/// </remarks>
|
|||
public class RgbAndCmykConversionTest |
|||
{ |
|||
private static readonly IEqualityComparer<float> FloatRoundingComparer = new FloatRoundingComparer(4); |
|||
|
|||
private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion from <see cref="Cmyk"/> to <see cref="Rgb"/>.
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(1, 1, 1, 1, 0, 0, 0)] |
|||
[InlineData(0, 0, 0, 0, 1, 1, 1)] |
|||
[InlineData(0, 0.84, 0.037, 0.365, 0.635, 0.1016, 0.6115)] |
|||
public void Convert_Cmyk_To_Rgb(float c, float m, float y, float k, float r, float g, float b) |
|||
{ |
|||
// Arrange
|
|||
Cmyk input = new Cmyk(c, m, y, k); |
|||
|
|||
// Act
|
|||
Rgb output = Converter.ToRgb(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); |
|||
Assert.Equal(r, output.R, FloatRoundingComparer); |
|||
Assert.Equal(g, output.G, FloatRoundingComparer); |
|||
Assert.Equal(b, output.B, FloatRoundingComparer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion from <see cref="Rgb"/> to <see cref="Cmyk"/>.
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(1, 1, 1, 0, 0, 0, 0)] |
|||
[InlineData(0, 0, 0, 0, 0, 0, 1)] |
|||
[InlineData(0.635, 0.1016, 0.6115, 0, 0.84, 0.037, 0.365)] |
|||
public void Convert_Rgb_To_Cmyk(float r, float g, float b, float c, float m, float y, float k) |
|||
{ |
|||
// Arrange
|
|||
Rgb input = new Rgb(r, g, b); |
|||
|
|||
// Act
|
|||
Cmyk output = Converter.ToCmyk(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(c, output.C, FloatRoundingComparer); |
|||
Assert.Equal(m, output.M, FloatRoundingComparer); |
|||
Assert.Equal(y, output.Y, FloatRoundingComparer); |
|||
Assert.Equal(k, output.K, FloatRoundingComparer); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue