mirror of https://github.com/SixLabors/ImageSharp
16 changed files with 761 additions and 1 deletions
@ -0,0 +1,170 @@ |
|||
// <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.ColorSpaces.Conversion |
|||
{ |
|||
using ImageSharp.ColorSpaces; |
|||
using ImageSharp.ColorSpaces.Conversion.Implementation.Hsv; |
|||
|
|||
/// <summary>
|
|||
/// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
|
|||
/// </summary>
|
|||
public partial class ColorSpaceConverter |
|||
{ |
|||
private static readonly HsvAndRgbConverter HsvAndRgbConverter = new HsvAndRgbConverter(); |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieLab"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(CieLab color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToHsv(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieLch"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(CieLch color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToHsv(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieXyy"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(CieXyy color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToHsv(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="CieXyz"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(CieXyz color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
Rgb rgb = this.ToRgb(color); |
|||
|
|||
return HsvAndRgbConverter.Convert(rgb); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Cmyk"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(Cmyk color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
Rgb rgb = this.ToRgb(color); |
|||
|
|||
return HsvAndRgbConverter.Convert(rgb); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Hsl"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(Hsl color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
Rgb rgb = this.ToRgb(color); |
|||
|
|||
return HsvAndRgbConverter.Convert(rgb); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="HunterLab"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(HunterLab color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToHsv(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="LinearRgb"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(LinearRgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
Rgb rgb = this.ToRgb(color); |
|||
|
|||
return HsvAndRgbConverter.Convert(rgb); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Lms"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(Lms color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
CieXyz xyzColor = this.ToCieXyz(color); |
|||
|
|||
return this.ToHsv(xyzColor); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Rgb"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(Rgb color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
return HsvAndRgbConverter.Convert(color); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="YCbCr"/> into a <see cref="Hsv"/>
|
|||
/// </summary>
|
|||
/// <param name="color">The color to convert.</param>
|
|||
/// <returns>The <see cref="Hsv"/></returns>
|
|||
public Hsv ToHsv(YCbCr color) |
|||
{ |
|||
Guard.NotNull(color, nameof(color)); |
|||
|
|||
Rgb rgb = this.ToRgb(color); |
|||
|
|||
return HsvAndRgbConverter.Convert(rgb); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
// <copyright file="HsvAndRgbConverter.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.ColorSpaces.Conversion.Implementation.Hsv |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using ImageSharp.ColorSpaces; |
|||
|
|||
/// <summary>
|
|||
/// Color converter between HSV and Rgb
|
|||
/// See <see href="http://www.poynton.com/PDFs/coloureq.pdf"/> for formulas.
|
|||
/// </summary>
|
|||
internal class HsvAndRgbConverter : IColorConversion<Hsv, Rgb>, IColorConversion<Rgb, Hsv> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Rgb Convert(Hsv input) |
|||
{ |
|||
DebugGuard.NotNull(input, nameof(input)); |
|||
|
|||
float s = input.S; |
|||
float v = input.V; |
|||
|
|||
if (MathF.Abs(s) < Constants.Epsilon) |
|||
{ |
|||
return new Rgb(v, v, v); |
|||
} |
|||
|
|||
float h = (MathF.Abs(input.H - 360) < Constants.Epsilon) ? 0 : input.H / 60; |
|||
int i = (int)Math.Truncate(h); |
|||
float f = h - i; |
|||
|
|||
float p = v * (1F - s); |
|||
float q = v * (1F - (s * f)); |
|||
float t = v * (1F - (s * (1F - f))); |
|||
|
|||
float r, g, b; |
|||
switch (i) |
|||
{ |
|||
case 0: |
|||
r = v; |
|||
g = t; |
|||
b = p; |
|||
break; |
|||
|
|||
case 1: |
|||
r = q; |
|||
g = v; |
|||
b = p; |
|||
break; |
|||
|
|||
case 2: |
|||
r = p; |
|||
g = v; |
|||
b = t; |
|||
break; |
|||
|
|||
case 3: |
|||
r = p; |
|||
g = q; |
|||
b = v; |
|||
break; |
|||
|
|||
case 4: |
|||
r = t; |
|||
g = p; |
|||
b = v; |
|||
break; |
|||
|
|||
default: |
|||
r = v; |
|||
g = p; |
|||
b = q; |
|||
break; |
|||
} |
|||
|
|||
return new Rgb(r, g, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Hsv Convert(Rgb input) |
|||
{ |
|||
DebugGuard.NotNull(input, nameof(input)); |
|||
|
|||
float r = input.R; |
|||
float g = input.G; |
|||
float b = input.B; |
|||
|
|||
float max = MathF.Max(r, MathF.Max(g, b)); |
|||
float min = MathF.Min(r, MathF.Min(g, b)); |
|||
float chroma = max - min; |
|||
float h = 0; |
|||
float s = 0; |
|||
float v = max; |
|||
|
|||
if (MathF.Abs(chroma) < Constants.Epsilon) |
|||
{ |
|||
return new Hsv(0, s, v); |
|||
} |
|||
|
|||
if (MathF.Abs(r - max) < Constants.Epsilon) |
|||
{ |
|||
h = (g - b) / chroma; |
|||
} |
|||
else if (MathF.Abs(g - max) < Constants.Epsilon) |
|||
{ |
|||
h = 2 + ((b - r) / chroma); |
|||
} |
|||
else if (MathF.Abs(b - max) < Constants.Epsilon) |
|||
{ |
|||
h = 4 + ((r - g) / chroma); |
|||
} |
|||
|
|||
h *= 60; |
|||
if (h < 0.0) |
|||
{ |
|||
h += 360; |
|||
} |
|||
|
|||
s = chroma / v; |
|||
|
|||
return new Hsv(h, s, v); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,233 @@ |
|||
// <copyright file="Hsv.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.ColorSpaces |
|||
{ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
|
|||
/// </summary>
|
|||
public struct Hsv : IColorVector, IEquatable<Hsv>, IAlmostEquatable<Hsv, float> |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a <see cref="Hsv"/> that has H, S, and V values set to zero.
|
|||
/// </summary>
|
|||
public static readonly Hsv Empty = default(Hsv); |
|||
|
|||
/// <summary>
|
|||
/// Max range used for clamping
|
|||
/// </summary>
|
|||
private static readonly Vector3 VectorMax = new Vector3(360, 1, 1); |
|||
|
|||
/// <summary>
|
|||
/// The backing vector for SIMD support.
|
|||
/// </summary>
|
|||
private readonly Vector3 backingVector; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Hsv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="h">The h hue component.</param>
|
|||
/// <param name="s">The s saturation component.</param>
|
|||
/// <param name="v">The v value (brightness) component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Hsv(float h, float s, float v) |
|||
: this(new Vector3(h, s, v)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Hsv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the h, s, v components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Hsv(Vector3 vector) |
|||
{ |
|||
this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the hue component.
|
|||
/// <remarks>A value ranging between 0 and 360.</remarks>
|
|||
/// </summary>
|
|||
public float H |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => this.backingVector.X; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the saturation component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float S |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => this.backingVector.Y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value (brightness) component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float V |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => this.backingVector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this <see cref="Hsv"/> is empty.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public bool IsEmpty => this.Equals(Empty); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector3 Vector |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get => this.backingVector; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Allows the implicit conversion of an instance of <see cref="Rgba32"/> to a
|
|||
/// <see cref="Hsv"/>.
|
|||
/// </summary>
|
|||
/// <param name="color">The instance of <see cref="Rgba32"/> to convert.</param>
|
|||
/// <returns>
|
|||
/// An instance of <see cref="Hsv"/>.
|
|||
/// </returns>
|
|||
public static implicit operator Hsv(Rgba32 color) |
|||
{ |
|||
float r = color.R / 255F; |
|||
float g = color.G / 255F; |
|||
float b = color.B / 255F; |
|||
|
|||
float max = MathF.Max(r, MathF.Max(g, b)); |
|||
float min = MathF.Min(r, MathF.Min(g, b)); |
|||
float chroma = max - min; |
|||
float h = 0; |
|||
float s = 0; |
|||
float v = max; |
|||
|
|||
if (MathF.Abs(chroma) < Constants.Epsilon) |
|||
{ |
|||
return new Hsv(0, s, v); |
|||
} |
|||
|
|||
if (MathF.Abs(r - max) < Constants.Epsilon) |
|||
{ |
|||
h = (g - b) / chroma; |
|||
} |
|||
else if (MathF.Abs(g - max) < Constants.Epsilon) |
|||
{ |
|||
h = 2 + ((b - r) / chroma); |
|||
} |
|||
else if (MathF.Abs(b - max) < Constants.Epsilon) |
|||
{ |
|||
h = 4 + ((r - g) / chroma); |
|||
} |
|||
|
|||
h *= 60; |
|||
if (h < 0.0) |
|||
{ |
|||
h += 360; |
|||
} |
|||
|
|||
s = chroma / v; |
|||
|
|||
return new Hsv(h, s, v); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Hsv"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Hsv"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// 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.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Hsv left, Hsv right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Hsv"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Hsv"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="Hsv"/> 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>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Hsv left, Hsv right) |
|||
{ |
|||
return !left.Equals(right); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.backingVector.GetHashCode(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
if (this.IsEmpty) |
|||
{ |
|||
return "Hsv [ Empty ]"; |
|||
} |
|||
|
|||
return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (obj is Hsv) |
|||
{ |
|||
return this.Equals((Hsv)obj); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Hsv other) |
|||
{ |
|||
return this.backingVector.Equals(other.backingVector); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool AlmostEquals(Hsv other, float precision) |
|||
{ |
|||
Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); |
|||
|
|||
return result.X <= precision |
|||
&& result.Y <= precision |
|||
&& result.Z <= precision; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
namespace ImageSharp.Tests.Colors.Colorspaces |
|||
{ |
|||
using System.Collections.Generic; |
|||
|
|||
using ImageSharp.ColorSpaces; |
|||
using ImageSharp.ColorSpaces.Conversion; |
|||
|
|||
using Xunit; |
|||
|
|||
/// <summary>
|
|||
/// Tests <see cref="Rgb"/>-<see cref="Hsv"/> conversions.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Test data generated using:
|
|||
/// <see href="http://www.colorhexa.com"/>
|
|||
/// </remarks>
|
|||
public class RgbAndHsvConversionTest |
|||
{ |
|||
private static readonly IEqualityComparer<float> FloatRoundingComparer = new FloatRoundingComparer(4); |
|||
|
|||
private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); |
|||
|
|||
/// <summary>
|
|||
/// Tests conversion from <see cref="Hsv"/> to <see cref="Rgb"/>.
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0, 0, 0)] |
|||
[InlineData(0, 0, 1, 1, 1, 1)] |
|||
[InlineData(360, 1, 1, 1, 0, 0)] |
|||
[InlineData(0, 1, 1, 1, 0, 0)] |
|||
[InlineData(120, 1, 1, 0, 1, 0)] |
|||
[InlineData(240, 1, 1, 0, 0, 1)] |
|||
public void Convert_Hsv_To_Rgb(float h, float s, float v, float r, float g, float b) |
|||
{ |
|||
// Arrange
|
|||
Hsv input = new Hsv(h, s, v); |
|||
|
|||
// 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="Hsv"/>.
|
|||
/// </summary>
|
|||
[Theory] |
|||
[InlineData(0, 0, 0, 0, 0, 0)] |
|||
[InlineData(1, 1, 1, 0, 0, 1)] |
|||
[InlineData(1, 0, 0, 0, 1, 1)] |
|||
[InlineData(0, 1, 0, 120, 1, 1)] |
|||
[InlineData(0, 0, 1, 240, 1, 1)] |
|||
public void Convert_Rgb_To_Hsv(float r, float g, float b, float h, float s, float v) |
|||
{ |
|||
// Arrange
|
|||
Rgb input = new Rgb(r, g, b); |
|||
|
|||
// Act
|
|||
Hsv output = Converter.ToHsv(input); |
|||
|
|||
// Assert
|
|||
Assert.Equal(h, output.H, FloatRoundingComparer); |
|||
Assert.Equal(s, output.S, FloatRoundingComparer); |
|||
Assert.Equal(v, output.V, FloatRoundingComparer); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue