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