Skip to content

Commit

Permalink
Added support for converting to/from HSV and HSL
Browse files Browse the repository at this point in the history
  • Loading branch information
mattleibow committed Sep 1, 2016
1 parent a76f50b commit 555a626
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 0 deletions.
225 changes: 225 additions & 0 deletions binding/Binding/Definitions.cs
Expand Up @@ -82,6 +82,8 @@ public enum SKEncodedFormat {
}

public partial struct SKColor {
private const float EPSILON = 0.001f;

public static readonly SKColor Empty;

private uint color;
Expand All @@ -101,6 +103,21 @@ public SKColor (byte red, byte green, byte blue)
color = (uint)(0xff000000u | (red << 16) | (green << 8) | blue);
}

public SKColor WithRed (byte red)
{
return new SKColor (red, Green, Blue, Alpha);
}

public SKColor WithGreen (byte green)
{
return new SKColor (Red, green, Blue, Alpha);
}

public SKColor WithBlue (byte blue)
{
return new SKColor (Red, Green, blue, Alpha);
}

public SKColor WithAlpha (byte alpha)
{
return new SKColor (Red, Green, Blue, alpha);
Expand All @@ -111,6 +128,214 @@ public SKColor WithAlpha (byte alpha)
public byte Green => (byte)((color >> 8) & 0xff);
public byte Blue => (byte)((color) & 0xff);

public static SKColor FromHsl (float h, float s, float l, byte a = 255)
{
// convert from percentages
h = h / 360f;
s = s / 100f;
l = l / 100f;

// RGB results from 0 to 255
var r = l * 255f;
var g = l * 255f;
var b = l * 255f;

// HSL from 0 to 1
if (Math.Abs (s) > EPSILON)
{
float v2;
if (l < 0.5f)
v2 = l * (1f + s);
else
v2 = (l + s) - (s * l);

var v1 = 2f * l - v2;

r = 255 * HueToRgb (v1, v2, h + (1f / 3f));
g = 255 * HueToRgb (v1, v2, h);
b = 255 * HueToRgb (v1, v2, h - (1f / 3f));
}

return new SKColor ((byte)r, (byte)g, (byte)b, a);
}

private static float HueToRgb (float v1, float v2, float vH)
{
if (vH < 0f)
vH += 1f;
if (vH > 1f)
vH -= 1f;

if ((6f * vH) < 1f)
return (v1 + (v2 - v1) * 6f * vH);
if ((2f * vH) < 1f)
return (v2);
if ((3f * vH) < 2f)
return (v1 + (v2 - v1) * ((2f / 3f) - vH) * 6f);
return (v1);
}

public static SKColor FromHsv(float h, float s, float v, byte a = 255)
{
// convert from percentages
h = h / 360f;
s = s / 100f;
v = v / 100f;

// RGB results from 0 to 255
var r = v;
var g = v;
var b = v;

// HSL from 0 to 1
if (Math.Abs (s) > EPSILON)
{
h = h * 6f;
if (Math.Abs (h - 6f) < EPSILON)
h = 0f; // H must be < 1

var hInt = (int)h;
var v1 = v * (1f - s);
var v2 = v * (1f - s * (h - hInt));
var v3 = v * (1f - s * (1f - (h - hInt)));

if (hInt == 0)
{
r = v;
g = v3;
b = v1;
}
else if (hInt == 1)
{
r = v2;
g = v;
b = v1;
}
else if (hInt == 2)
{
r = v1;
g = v;
b = v3;
}
else if (hInt == 3)
{
r = v1;
g = v2;
b = v;
}
else if (hInt == 4)
{
r = v3;
g = v1;
b = v;
}
else
{
r = v;
g = v1;
b = v2;
}
}

// RGB results from 0 to 255
r = r * 255f;
g = g * 255f;
b = b * 255f;

return new SKColor ((byte)r, (byte)g, (byte)b, a);
}

public void ToHsl (out float h, out float s, out float l)
{
// RGB from 0 to 255
var r = (Red / 255f);
var g = (Green / 255f);
var b = (Blue / 255f);

var min = Math.Min (Math.Min (r, g), b); // min value of RGB
var max = Math.Max (Math.Max (r, g), b); // max value of RGB
var delta = max - min; // delta RGB value

// default to a gray, no chroma...
h = 0f;
s = 0f;
l = (max + min) / 2f;

// chromatic data...
if (Math.Abs (delta) > EPSILON)
{
if (l < 0.5f)
s = delta / (max + min);
else
s = delta / (2f - max - min);

var deltaR = (((max - r) / 6f) + (delta / 2f)) / delta;
var deltaG = (((max - g) / 6f) + (delta / 2f)) / delta;
var deltaB = (((max - b) / 6f) + (delta / 2f)) / delta;

if (Math.Abs (r - max) < EPSILON) // r == max
h = deltaB - deltaG;
else if (Math.Abs (g - max) < EPSILON) // g == max
h = (1f / 3f) + deltaR - deltaB;
else // b == max
h = (2f / 3f) + deltaG - deltaR;

if (h < 0f)
h += 1f;
if (h > 1f)
h -= 1f;
}

// convert to percentages
h = h * 360f;
s = s * 100f;
l = l * 100f;
}

public void ToHsv (out float h, out float s, out float v)
{
// RGB from 0 to 255
var r = (Red / 255f);
var g = (Green / 255f);
var b = (Blue / 255f);

var min = Math.Min (Math.Min (r, g), b); // min value of RGB
var max = Math.Max (Math.Max (r, g), b); // max value of RGB
var delta = max - min; // delta RGB value

// default to a gray, no chroma...
h = 0;
s = 0;
v = max;

// chromatic data...
if (Math.Abs (delta) > EPSILON)
{
s = delta / max;

var deltaR = (((max - r) / 6f) + (delta / 2f)) / delta;
var deltaG = (((max - g) / 6f) + (delta / 2f)) / delta;
var deltaB = (((max - b) / 6f) + (delta / 2f)) / delta;

if (Math.Abs (r - max) < EPSILON) // r == max
h = deltaB - deltaG;
else if (Math.Abs (g - max) < EPSILON) // g == max
h = (1f / 3f) + deltaR - deltaB;
else // b == max
h = (2f / 3f) + deltaG - deltaR;

if (h < 0f)
h += 1f;
if (h > 1f)
h -= 1f;
}

// convert to percentages
h = h * 360f;
s = s * 100f;
v = v * 100f;
}

public override string ToString ()
{
return string.Format (CultureInfo.InvariantCulture, "#{0:x2}{1:x2}{2:x2}{3:x2}", Alpha, Red, Green, Blue);
Expand Down
1 change: 1 addition & 0 deletions binding/SkiaSharp.iOS/SkiaSharp.iOS.csproj
Expand Up @@ -35,6 +35,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="Xamarin.iOS" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
Expand Down
1 change: 1 addition & 0 deletions binding/SkiaSharp.tvOS/SkiaSharp.tvOS.csproj
Expand Up @@ -35,6 +35,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="Xamarin.TVOS" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
Expand Down
3 changes: 3 additions & 0 deletions tests/SkiaSharp.Desktop.Tests/SkiaSharp.Desktop.Tests.csproj
Expand Up @@ -106,6 +106,9 @@
<Compile Include="..\Tests\SKPathTest.cs" >
<Link>SKPathTest.cs</Link>
</Compile>
<Compile Include="..\Tests\SKColorTest.cs" >
<Link>SKColorTest.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Include="..\..\samples\SharedDemo\content-font.ttf">
Expand Down
125 changes: 125 additions & 0 deletions tests/Tests/SKColorTest.cs
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;

using SKOtherColor = System.Tuple<float, float, float>;
using ToOtherColor = System.Tuple<SkiaSharp.SKColor, System.Tuple<float, float, float>, string>;

namespace SkiaSharp.Tests
{
[TestFixture]
public class SKColorTest : SKTest
{
private const float EPSILON = 0.01f;

[Test]
public void ColorWithComponent()
{
var color = new SKColor();
Assert.AreEqual(0, color.Red);
Assert.AreEqual(0, color.Green);
Assert.AreEqual(0, color.Blue);
Assert.AreEqual(0, color.Alpha);

var red = color.WithRed(255);
Assert.AreEqual(255, red.Red);
Assert.AreEqual(0, red.Green);
Assert.AreEqual(0, red.Blue);
Assert.AreEqual(0, red.Alpha);

var green = color.WithGreen(255);
Assert.AreEqual(0, green.Red);
Assert.AreEqual(255, green.Green);
Assert.AreEqual(0, green.Blue);
Assert.AreEqual(0, green.Alpha);

var blue = color.WithBlue(255);
Assert.AreEqual(0, blue.Red);
Assert.AreEqual(0, blue.Green);
Assert.AreEqual(255, blue.Blue);
Assert.AreEqual(0, blue.Alpha);

var alpha = color.WithAlpha(255);
Assert.AreEqual(0, alpha.Red);
Assert.AreEqual(0, alpha.Green);
Assert.AreEqual(0, alpha.Blue);
Assert.AreEqual(255, alpha.Alpha);
}

[Test]
public void ColorRgbToHsl()
{
var tuples = new List<ToOtherColor> {
new ToOtherColor(new SKColor(000, 000, 000), new SKOtherColor(000f, 000.0f, 000.0f), "Black"),
new ToOtherColor(new SKColor(255, 000, 000), new SKOtherColor(000f, 100.0f, 050.0f), "Red"),
new ToOtherColor(new SKColor(255, 255, 000), new SKOtherColor(060f, 100.0f, 050.0f), "Yellow"),
new ToOtherColor(new SKColor(255, 255, 255), new SKOtherColor(000f, 000.0f, 100.0f), "White"),
new ToOtherColor(new SKColor(128, 128, 128), new SKOtherColor(000f, 000.0f, 050.2f), "Gray"),
new ToOtherColor(new SKColor(128, 128, 000), new SKOtherColor(060f, 100.0f, 025.1f), "Olive"),
new ToOtherColor(new SKColor(000, 128, 000), new SKOtherColor(120f, 100.0f, 025.1f), "Green"),
new ToOtherColor(new SKColor(000, 000, 128), new SKOtherColor(240f, 100.0f, 025.1f), "Navy"),
};

foreach (var item in tuples)
{
// values
SKColor rgb = item.Item1;
SKOtherColor other = item.Item2;

// to HSL
float h, s, l;
rgb.ToHsl(out h, out s, out l);

Assert.AreEqual(other.Item1, h, EPSILON, item.Item3 + " H");
Assert.AreEqual(other.Item2, s, EPSILON, item.Item3 + " S");
Assert.AreEqual(other.Item3, l, EPSILON, item.Item3 + " L");

// to RGB
SKColor back = SKColor.FromHsl(other.Item1, other.Item2, other.Item3);

Assert.AreEqual(rgb.Red, back.Red, item.Item3 + " R");
Assert.AreEqual(rgb.Green, back.Green, item.Item3 + " G");
Assert.AreEqual(rgb.Blue, back.Blue, item.Item3 + " B");
Assert.AreEqual(rgb.Alpha, back.Alpha, item.Item3 + " A");
}
}

[Test]
public void ColorRgbToHsv()
{
var tuples = new List<ToOtherColor> {
new ToOtherColor(new SKColor(000, 000, 000), new SKOtherColor(000f, 000.0f, 000.0f), "Black"),
new ToOtherColor(new SKColor(255, 000, 000), new SKOtherColor(000f, 100.0f, 100.0f), "Red"),
new ToOtherColor(new SKColor(255, 255, 000), new SKOtherColor(060f, 100.0f, 100.0f), "Yellow"),
new ToOtherColor(new SKColor(255, 255, 255), new SKOtherColor(000f, 000.0f, 100.0f), "White"),
new ToOtherColor(new SKColor(128, 128, 128), new SKOtherColor(000f, 000.0f, 050.2f), "Gray"),
new ToOtherColor(new SKColor(128, 128, 000), new SKOtherColor(060f, 100.0f, 050.2f), "Olive"),
new ToOtherColor(new SKColor(000, 128, 000), new SKOtherColor(120f, 100.0f, 050.2f), "Green"),
new ToOtherColor(new SKColor(000, 000, 128), new SKOtherColor(240f, 100.0f, 050.2f), "Navy"),
};

foreach (var item in tuples)
{
// values
SKColor rgb = item.Item1;
SKOtherColor other = item.Item2;

// to HSV
float h, s, v;
rgb.ToHsv(out h, out s, out v);

Assert.AreEqual(other.Item1, h, EPSILON, item.Item3 + " H");
Assert.AreEqual(other.Item2, s, EPSILON, item.Item3 + " S");
Assert.AreEqual(other.Item3, v, EPSILON, item.Item3 + " V");

// to RGB
SKColor back = SKColor.FromHsv(other.Item1, other.Item2, other.Item3);

Assert.AreEqual(rgb.Red, back.Red, item.Item3 + " R");
Assert.AreEqual(rgb.Green, back.Green, item.Item3 + " G");
Assert.AreEqual(rgb.Blue, back.Blue, item.Item3 + " B");
Assert.AreEqual(rgb.Alpha, back.Alpha, item.Item3 + " A");
}
}
}
}

0 comments on commit 555a626

Please sign in to comment.