Browse Source

Add CMYK

Former-commit-id: a4c7eb73821870766ed4e40b63b942f8333e9088
Former-commit-id: f7ce24745b68fd67dfd01f89d9dbc2235c6ef573
Former-commit-id: 0443af7d07ec788618550c05d68ebf530aea9c37
af/merge-core
James Jackson-South 10 years ago
parent
commit
b2eeeeb886
  1. 18
      src/ImageProcessor/Colors/Bgra.cs
  2. 227
      src/ImageProcessor/Colors/Cmyk.cs
  3. 2
      src/ImageProcessor/Colors/Hsv.cs
  4. 1
      src/ImageProcessor/ImageProcessor.csproj
  5. 84
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

18
src/ImageProcessor/Colors/Bgra.cs

@ -273,6 +273,24 @@ namespace ImageProcessor
return new Bgra(b, g, r, 255);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Cmyk"/> to a
/// <see cref="Bgra"/>.
/// </summary>
/// <param name="cmykColor">
/// The instance of <see cref="Cmyk"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra"/>.
/// </returns>
public static implicit operator Bgra(Cmyk cmykColor)
{
int red = Convert.ToInt32((1 - (cmykColor.C / 100)) * (1 - (cmykColor.K / 100)) * 255.0);
int green = Convert.ToInt32((1 - (cmykColor.M / 100)) * (1 - (cmykColor.K / 100)) * 255.0);
int blue = Convert.ToInt32((1 - (cmykColor.Y / 100)) * (1 - (cmykColor.K / 100)) * 255.0);
return new Bgra(blue.ToByte(), green.ToByte(), red.ToByte());
}
/// <summary>
/// Compares two <see cref="Bgra"/> objects. The result specifies whether the values
/// of the <see cref="Bgra.B"/>, <see cref="Bgra.G"/>, <see cref="Bgra.R"/>, and <see cref="Bgra.A"/>

227
src/ImageProcessor/Colors/Cmyk.cs

@ -0,0 +1,227 @@
// <copyright file="Cmyk.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
using System;
using System.ComponentModel;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
/// </summary>
public struct Cmyk : IEquatable<Cmyk>
{
/// <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>
/// Gets the cyan color component.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float C;
/// <summary>
/// Gets the magenta color component.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float M;
/// <summary>
/// Gets the yellow color component.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float Y;
/// <summary>
/// Gets the keyline black color component.
/// </summary>
/// <remarks>A value ranging between 0 and 100.</remarks>
public readonly float K;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// </summary>
/// <param name="cyan">The cyan component.</param>
/// <param name="magenta">The magenta component.</param>
/// <param name="yellow">The yellow component.</param>
/// <param name="keyline">The keyline black component.</param>
public Cmyk(float cyan, float magenta, float yellow, float keyline)
{
this.C = Clamp(cyan);
this.M = Clamp(magenta);
this.Y = Clamp(yellow);
this.K = Clamp(keyline);
}
/// <summary>
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.C) < Epsilon
&& Math.Abs(this.M) < Epsilon
&& Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.K) < Epsilon;
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Bgra"/> to a
/// <see cref="Cmyk"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Cmyk"/>.
/// </returns>
public static implicit operator Cmyk(Bgra color)
{
float c = (255f - color.R) / 255;
float m = (255f - color.G) / 255;
float y = (255f - color.B) / 255;
float k = Math.Min(c, Math.Min(m, y));
if (Math.Abs(k - 1.0) <= Epsilon)
{
return new Cmyk(0, 0, 0, 100);
}
c = ((c - k) / (1 - k)) * 100;
m = ((m - k) / (1 - k)) * 100;
y = ((y - k) / (1 - k)) * 100;
return new Cmyk(c, m, y, k * 100);
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects. The result specifies whether the values
/// of the <see cref="Cmyk.C"/>, <see cref="Cmyk.M"/>, <see cref="Cmyk.Y"/>, and <see cref="Cmyk.K"/>
/// properties of the two <see cref="Cmyk"/> objects are equal.
/// </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. The result specifies whether the values
/// of the <see cref="Cmyk.C"/>, <see cref="Cmyk.M"/>, <see cref="Cmyk.Y"/>, and <see cref="Cmyk.K"/>
/// properties of the two <see cref="Cmyk"/> objects are unequal.
/// </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);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
public override bool Equals(object obj)
{
if (obj is Cmyk)
{
Cmyk color = (Cmyk)obj;
return Math.Abs(this.C - color.C) < Epsilon
&& Math.Abs(this.M - color.M) < Epsilon
&& Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.K - color.K) < Epsilon;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.C.GetHashCode();
hashCode = (hashCode * 397) ^ this.M.GetHashCode();
hashCode = (hashCode * 397) ^ this.Y.GetHashCode();
hashCode = (hashCode * 397) ^ this.K.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
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.##}]";
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(Cmyk other)
{
return Math.Abs(this.C - other.C) < Epsilon
&& Math.Abs(this.M - other.M) < Epsilon
&& Math.Abs(this.Y - other.Y) < Epsilon
&& Math.Abs(this.K - other.Y) < Epsilon;
}
/// <summary>
/// Checks the range of the given value to ensure that it remains within the acceptable boundaries.
/// </summary>
/// <param name="value">
/// The value to check.
/// </param>
/// <returns>
/// The sanitized <see cref="float"/>.
/// </returns>
private static float Clamp(float value)
{
return value.Clamp(0, 100);
}
}
}

2
src/ImageProcessor/Colors/Hsv.cs

@ -149,7 +149,7 @@ namespace ImageProcessor
/// The <see cref="Hsv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Hsv left, Hsv right)
{

1
src/ImageProcessor/ImageProcessor.csproj

@ -40,6 +40,7 @@
<!-- A reference to the entire .NET Framework is automatically included -->
</ItemGroup>
<ItemGroup>
<Compile Include="Colors\Cmyk.cs" />
<Compile Include="Common\Helpers\ImageMaths.cs" />
<Compile Include="Common\Helpers\PixelOperations.cs" />
<Compile Include="Filters\Contrast.cs" />

84
tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

@ -160,12 +160,94 @@ namespace ImageProcessor.Tests
// Check others.
Random random = new Random(0);
for (var i = 0; i < 1000; i++)
for (int i = 0; i < 1000; i++)
{
Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
Hsv hsb4 = bgra4;
Assert.Equal(bgra4, (Bgra)hsb4);
}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Bgra"/> to <see cref="Cmyk"/>.
/// </summary>
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.")]
public void BgrToCmyk()
{
// White
Bgra color = new Bgra(255, 255, 255, 255);
Cmyk cmyk = color;
Assert.Equal(0, cmyk.C);
Assert.Equal(0, cmyk.M);
Assert.Equal(0, cmyk.Y);
Assert.Equal(0, cmyk.K);
// Black
Bgra color2 = new Bgra(0, 0, 0, 255);
Cmyk cmyk2 = color2;
Assert.Equal(0, cmyk2.C);
Assert.Equal(0, cmyk2.M);
Assert.Equal(0, cmyk2.Y);
Assert.Equal(100, cmyk2.K);
// Grey
Bgra color3 = new Bgra(128, 128, 128, 255);
Cmyk cmyk3 = color3;
Assert.Equal(0, cmyk3.C);
Assert.Equal(0, cmyk3.M);
Assert.Equal(0, cmyk3.Y);
Assert.Equal(49.8, cmyk3.K, 1); // Checked with other tools.
// Cyan
Bgra color4 = new Bgra(255, 255, 0, 255);
Cmyk cmyk4 = color4;
Assert.Equal(100, cmyk4.C);
Assert.Equal(0, cmyk4.M);
Assert.Equal(0, cmyk4.Y);
Assert.Equal(0, cmyk4.K);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Bgra"/>.
/// </summary>
[Fact]
public void CmykToBgr()
{
// Dark moderate pink.
Cmyk cmyk = new Cmyk(49.8f, 74.9f, 58.4f, 0);
Bgra bgra = cmyk;
Assert.Equal(bgra.B, 106);
Assert.Equal(bgra.G, 64);
Assert.Equal(bgra.R, 128);
// Ochre
Cmyk cmyk2 = new Cmyk(20, 53.3f, 86.7f, 0);
Bgra bgra2 = cmyk2;
Assert.Equal(bgra2.B, 34);
Assert.Equal(bgra2.G, 119);
Assert.Equal(bgra2.R, 204);
// White
Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
Bgra bgra3 = cmyk3;
Assert.Equal(bgra3.B, 255);
Assert.Equal(bgra3.G, 255);
Assert.Equal(bgra3.R, 255);
// Check others.
Random random = new Random(0);
for (int i = 0; i < 1000; i++)
{
Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
Cmyk cmyk4 = bgra4;
Assert.Equal(bgra4, (Bgra)cmyk4);
}
}
}
}

Loading…
Cancel
Save