Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rgb struct #7

Merged
merged 2 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

This library uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.9.0

### Added

- Added `Rgb` struct.
- Added constructor to `Rgba` struct accepting `Rgb` and `byte` parameters.

## 0.8.0

### Added
Expand Down
24 changes: 24 additions & 0 deletions src/Detach.Tests/Tests/Numerics/RgbTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Detach.Numerics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Numerics;

namespace Detach.Tests.Tests.Numerics;

[TestClass]
public class RgbTests
{
[DataTestMethod]
[DataRow(0, 0, 0)]
[DataRow(1, 2, 3)]
[DataRow(5, 6, 7)]
[DataRow(255, 6, 255)]
[DataRow(255, 255, 255)]
public void RgbConversions(int r, int g, int b)
{
Rgb expectedRgb = new((byte)r, (byte)g, (byte)b);

Assert.AreEqual(expectedRgb, Rgb.FromVector4(new Vector4(r / 255f, g / 255f, b / 255f, 1)));
Assert.AreEqual(expectedRgb, Rgb.FromVector3(new Vector3(r / 255f, g / 255f, b / 255f)));
Assert.AreEqual(expectedRgb, Rgb.FromRgbInt(expectedRgb.ToRgbInt()));
}
}
2 changes: 1 addition & 1 deletion src/Detach/Detach.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<Copyright>Copyright © Noah Stolk</Copyright>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/NoahStolk/Detach</RepositoryUrl>
<Version>0.8.0</Version>
<Version>0.9.0</Version>
</PropertyGroup>

<ItemGroup Label="Static code analysis">
Expand Down
32 changes: 32 additions & 0 deletions src/Detach/Numerics/Colors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Detach.Numerics;

internal static class Colors
{
public static int GetPerceivedBrightness(byte r, byte g, byte b)
{
return (int)Math.Sqrt(r * r * 0.299 + g * g * 0.587 + b * b * 0.114);
}

public static int GetHue(byte r, byte g, byte b)
{
byte min = Math.Min(Math.Min(r, g), b);
byte max = Math.Max(Math.Max(r, g), b);

if (min == max)
return 0;

float hue;
if (max == r)
hue = (g - b) / (float)(max - min);
else if (max == g)
hue = 2f + (b - r) / (float)(max - min);
else
hue = 4f + (r - g) / (float)(max - min);

hue *= 60;
if (hue < 0)
hue += 360;

return (int)Math.Round(hue);
}
}
164 changes: 164 additions & 0 deletions src/Detach/Numerics/Rgb.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using Detach.Utils;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace Detach.Numerics;

public readonly record struct Rgb(byte R, byte G, byte B)
{
public Rgb(Rgba rgba)
: this(rgba.R, rgba.G, rgba.B)
{
}

public static Rgb White => new(byte.MaxValue, byte.MaxValue, byte.MaxValue);
public static Rgb Black => new(byte.MinValue, byte.MinValue, byte.MinValue);

public static Rgb Red => new(byte.MaxValue, byte.MinValue, byte.MinValue);
public static Rgb Green => new(byte.MinValue, byte.MaxValue, byte.MinValue);
public static Rgb Blue => new(byte.MinValue, byte.MinValue, byte.MaxValue);

public static Rgb Yellow => new(byte.MaxValue, byte.MaxValue, byte.MinValue);
public static Rgb Aqua => new(byte.MinValue, byte.MaxValue, byte.MaxValue);
public static Rgb Purple => new(byte.MaxValue, byte.MinValue, byte.MaxValue);

public static Rgb Orange => new(byte.MaxValue, byte.MaxValue / 2, byte.MinValue);

public static implicit operator Vector3(Rgb rgb)
{
return new Vector3(rgb.R / (float)byte.MaxValue, rgb.G / (float)byte.MaxValue, rgb.B / (float)byte.MaxValue);
}

public static implicit operator Vector4(Rgb rgb)
{
return new Vector4(rgb.R / (float)byte.MaxValue, rgb.G / (float)byte.MaxValue, rgb.B / (float)byte.MaxValue, 1);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb operator +(Rgb left, Rgb right)
{
return new Rgb((byte)(left.R + right.R), (byte)(left.G + right.G), (byte)(left.B + right.B));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb operator -(Rgb left, Rgb right)
{
return new Rgb((byte)(left.R - right.R), (byte)(left.G - right.G), (byte)(left.B - right.B));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb operator *(Rgb left, Rgb right)
{
return new Rgb((byte)(left.R * right.R), (byte)(left.G * right.G), (byte)(left.B * right.B));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rgb operator /(Rgb left, Rgb right)
{
return new Rgb((byte)(left.R / right.R), (byte)(left.G / right.G), (byte)(left.B / right.B));
}

public int GetPerceivedBrightness()
{
return Colors.GetPerceivedBrightness(R, G, B);
}

public Rgb Intensify(byte component)
{
byte r = (byte)Math.Min(byte.MaxValue, R + component);
byte g = (byte)Math.Min(byte.MaxValue, G + component);
byte b = (byte)Math.Min(byte.MaxValue, B + component);
return new Rgb(r, g, b);
}

public Rgb Desaturate(float f)
{
float r = R / (float)byte.MaxValue;
float g = G / (float)byte.MaxValue;
float b = B / (float)byte.MaxValue;

float l = 0.3f * r + 0.6f * g + 0.1f * b;
float newR = r + f * (l - r);
float newG = g + f * (l - g);
float newB = b + f * (l - b);

return new Rgb((byte)(newR * byte.MaxValue), (byte)(newG * byte.MaxValue), (byte)(newB * byte.MaxValue));
}

public Rgb Darken(float amount)
{
return new Rgb((byte)(R * (1 - amount)), (byte)(G * (1 - amount)), (byte)(B * (1 - amount)));
}

public int GetHue()
{
return Colors.GetHue(R, G, B);
}

public static Rgb Lerp(Rgb value1, Rgb value2, float amount)
{
float r = MathUtils.Lerp(value1.R, value2.R, amount);
float g = MathUtils.Lerp(value1.G, value2.G, amount);
float b = MathUtils.Lerp(value1.B, value2.B, amount);
return new Rgb((byte)r, (byte)g, (byte)b);
}

public static Rgb Invert(Rgb rgb)
{
return new Rgb((byte)(byte.MaxValue - rgb.R), (byte)(byte.MaxValue - rgb.G), (byte)(byte.MaxValue - rgb.B));
}

public static Rgb Gray(float value)
{
if (value is < 0 or > 1)
throw new ArgumentOutOfRangeException(nameof(value));

byte component = (byte)(value * byte.MaxValue);
return new Rgb(component, component, component);
}

public static Rgb FromHsv(float hue, float saturation, float value)
{
saturation = Math.Clamp(saturation, 0, 1);
value = Math.Clamp(value, 0, 1);

int hi = (int)MathF.Floor(hue / 60) % 6;
float f = hue / 60 - MathF.Floor(hue / 60);

value *= byte.MaxValue;
byte v = (byte)value;
byte p = (byte)(value * (1 - saturation));
byte q = (byte)(value * (1 - f * saturation));
byte t = (byte)(value * (1 - (1 - f) * saturation));

return hi switch
{
0 => new Rgb(v, t, p),
1 => new Rgb(q, v, p),
2 => new Rgb(p, v, t),
3 => new Rgb(p, q, v),
4 => new Rgb(t, p, v),
_ => new Rgb(v, p, q),
};
}

public static Rgb FromVector3(Vector3 vector)
{
return new Rgb((byte)(vector.X * byte.MaxValue), (byte)(vector.Y * byte.MaxValue), (byte)(vector.Z * byte.MaxValue));
}

public static Rgb FromVector4(Vector4 vector)
{
return new Rgb((byte)(vector.X * byte.MaxValue), (byte)(vector.Y * byte.MaxValue), (byte)(vector.Z * byte.MaxValue));
}

public int ToRgbInt()
{
return (R << 24) + (G << 16) + (B << 8);
}

public static Rgb FromRgbInt(int rgb)
{
return new Rgb((byte)(rgb >> 24), (byte)(rgb >> 16), (byte)(rgb >> 8));
}
}
60 changes: 9 additions & 51 deletions src/Detach/Numerics/Rgba.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ namespace Detach.Numerics;

public readonly record struct Rgba(byte R, byte G, byte B, byte A = byte.MaxValue)
{
public Rgba(Rgb rgb, byte a = byte.MaxValue)
: this(rgb.R, rgb.G, rgb.B, a)
{
}

public static Rgba Invisible => default;

public static Rgba White => new(byte.MaxValue, byte.MaxValue, byte.MaxValue);
Expand Down Expand Up @@ -60,7 +65,7 @@ public static implicit operator Vector4(Rgba rgba)

public int GetPerceivedBrightness()
{
return (int)Math.Sqrt(R * R * 0.299 + G * G * 0.587 + B * B * 0.114);
return Colors.GetPerceivedBrightness(R, G, B);
}

public Rgba Intensify(byte component)
Expand All @@ -74,16 +79,7 @@ public Rgba Intensify(byte component)

public Rgba Desaturate(float f)
{
float r = R / (float)byte.MaxValue;
float g = G / (float)byte.MaxValue;
float b = B / (float)byte.MaxValue;

float l = 0.3f * r + 0.6f * g + 0.1f * b;
float newR = r + f * (l - r);
float newG = g + f * (l - g);
float newB = b + f * (l - b);

return new Rgba((byte)(newR * byte.MaxValue), (byte)(newG * byte.MaxValue), (byte)(newB * byte.MaxValue), A);
return new Rgba(new Rgb(R, G, B).Desaturate(f), A);
}

public Rgba Darken(float amount)
Expand All @@ -93,25 +89,7 @@ public Rgba Darken(float amount)

public int GetHue()
{
byte min = Math.Min(Math.Min(R, G), B);
byte max = Math.Max(Math.Max(R, G), B);

if (min == max)
return 0;

float hue;
if (max == R)
hue = (G - B) / (float)(max - min);
else if (max == G)
hue = 2f + (B - R) / (float)(max - min);
else
hue = 4f + (R - G) / (float)(max - min);

hue *= 60;
if (hue < 0)
hue += 360;

return (int)Math.Round(hue);
return Colors.GetHue(R, G, B);
}

public static Rgba Lerp(Rgba value1, Rgba value2, float amount)
Expand Down Expand Up @@ -139,27 +117,7 @@ public static Rgba Gray(float value)

public static Rgba FromHsv(float hue, float saturation, float value)
{
saturation = Math.Clamp(saturation, 0, 1);
value = Math.Clamp(value, 0, 1);

int hi = (int)MathF.Floor(hue / 60) % 6;
float f = hue / 60 - MathF.Floor(hue / 60);

value *= byte.MaxValue;
byte v = (byte)value;
byte p = (byte)(value * (1 - saturation));
byte q = (byte)(value * (1 - f * saturation));
byte t = (byte)(value * (1 - (1 - f) * saturation));

return hi switch
{
0 => new Rgba(v, t, p),
1 => new Rgba(q, v, p),
2 => new Rgba(p, v, t),
3 => new Rgba(p, q, v),
4 => new Rgba(t, p, v),
_ => new Rgba(v, p, q),
};
return new Rgba(Rgb.FromHsv(hue, saturation, value));
}

public static Rgba FromVector3(Vector3 vector)
Expand Down
Loading