From b100b5c2fe641d1883b93a7dd51a56715cab800a Mon Sep 17 00:00:00 2001 From: Syntriax Date: Fri, 2 May 2025 00:51:53 +0300 Subject: [PATCH] feat: added color primitives --- Engine.Core/Primitives/ColorHSV.cs | 186 ++++++++++++++++++ Engine.Core/Primitives/ColorRGB.cs | 163 +++++++++++++++ Engine.Core/Primitives/ColorRGBA.cs | 149 ++++++++++++++ .../Primitives/ColorHSVConverter.cs | 28 +++ .../Primitives/ColorRGBAConverter.cs | 28 +++ .../Primitives/ColorRGBConverter.cs | 28 +++ 6 files changed, 582 insertions(+) create mode 100644 Engine.Core/Primitives/ColorHSV.cs create mode 100644 Engine.Core/Primitives/ColorRGB.cs create mode 100644 Engine.Core/Primitives/ColorRGBA.cs create mode 100644 Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorHSVConverter.cs create mode 100644 Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBAConverter.cs create mode 100644 Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBConverter.cs diff --git a/Engine.Core/Primitives/ColorHSV.cs b/Engine.Core/Primitives/ColorHSV.cs new file mode 100644 index 0000000..716cc1b --- /dev/null +++ b/Engine.Core/Primitives/ColorHSV.cs @@ -0,0 +1,186 @@ +using System; + +namespace Syntriax.Engine.Core; + +/// +/// Represents an HSV color. +/// +/// Hue of the . +/// Saturation of the . +/// Value of the . +/// +/// Initializes a new instance of the struct with the specified values. +/// +[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")] +public readonly struct ColorHSV(float hue, float saturation, float value) +{ + /// + /// The Hue value of the . + /// + public readonly float Hue = hue; + + /// + /// The Saturation value of the . + /// + public readonly float Saturation = saturation; + + /// + /// The Value value of the . + /// + public readonly float Value = value; + + public static ColorHSV operator -(ColorHSV color) => new(1f - color.Hue, 1f - color.Saturation, 1f - color.Value); + public static ColorHSV operator +(ColorHSV left, ColorHSV right) => new(left.Hue + right.Hue, left.Saturation + right.Saturation, left.Value + right.Value); + public static ColorHSV operator -(ColorHSV left, ColorHSV right) => new(left.Hue - right.Hue, left.Saturation - right.Saturation, left.Value - right.Value); + public static ColorHSV operator *(ColorHSV left, ColorHSV right) => new(left.Hue * right.Hue, left.Saturation * right.Saturation, left.Value * right.Value); + public static ColorHSV operator *(ColorHSV color, float value) => new(color.Hue * value, color.Saturation * value, color.Value * value); + public static ColorHSV operator *(float value, ColorHSV color) => new(color.Hue * value, color.Saturation * value, color.Value * value); + public static ColorHSV operator /(ColorHSV color, float value) => new(color.Hue / value, color.Saturation / value, color.Value / value); + public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue.ApproximatelyEquals(right.Hue) && left.Saturation.ApproximatelyEquals(right.Saturation) && left.Value.ApproximatelyEquals(right.Value); + public static bool operator !=(ColorHSV left, ColorHSV right) => !left.Hue.ApproximatelyEquals(right.Hue) || !left.Saturation.ApproximatelyEquals(right.Saturation) || !left.Value.ApproximatelyEquals(right.Value); + + public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorRGB)rgba; + public static implicit operator ColorHSV(ColorRGB rgb) + { + float hue; + float saturation; + float value; + + float rd = rgb.R / 255f; + float gd = rgb.G / 255f; + float bd = rgb.B / 255f; + + float max = Math.Max(rd, Math.Max(gd, bd)); + float min = Math.Min(rd, Math.Min(gd, bd)); + float delta = max - min; + + if (delta.ApproximatelyEquals(0)) + hue = 0f; + else if (max.ApproximatelyEquals(rd)) + hue = 60f * ((gd - bd) / delta % 6f); + else if (max.ApproximatelyEquals(gd)) + hue = 60f * (((bd - rd) / delta) + 2f); + else + hue = 60f * (((rd - gd) / delta) + 4f); + + if (hue < 0f) + hue += 360f; + + saturation = max.ApproximatelyEquals(0f) ? 0f : delta / max; + value = max; + + return new(hue, saturation, value); + } + + /// + /// Inverts the given . + /// + /// The . + /// The inverted . + public static ColorHSV Invert(ColorHSV color) => -color; + + /// + /// Adds two s. + /// + /// The first . + /// The second . + /// The sum of the two s. + public static ColorHSV Add(ColorHSV left, ColorHSV right) => left + right; + + /// + /// Subtracts one from another. + /// + /// The to subtract from. + /// The to subtract. + /// The result of subtracting the second from the first. + public static ColorHSV Subtract(ColorHSV left, ColorHSV right) => left - right; + + /// + /// Multiplies a by a scalar value. + /// + /// The . + /// The scalar value. + /// The result of multiplying the by the scalar value. + public static ColorHSV Multiply(ColorHSV color, float value) => color * value; + + /// + /// Divides a by a scalar value. + /// + /// The . + /// The scalar value. + /// The result of dividing the by the scalar value. + public static ColorHSV Divide(ColorHSV color, float value) => color / value; + + /// + /// Calculates the from one point to another. + /// + /// The starting point. + /// The ending point. + /// The from the starting point to the ending point. + public static ColorHSV FromTo(ColorHSV from, ColorHSV to) => to - from; + + /// + /// Performs linear interpolation between two s. + /// + /// The starting (t = 0). + /// The ending (t = 1). + /// The interpolation parameter. + /// The interpolated . + public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t) => from + FromTo(from, to) * t; + + /// + /// Converts the to its string representation. + /// + /// A string representation of the . + public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})"; + + /// + /// Checks if two s are approximately equal within a specified epsilon range. + /// + /// The first . + /// The second . + /// The epsilon range. + /// if the s are approximately equal; otherwise, . + public static bool ApproximatelyEquals(ColorHSV left, ColorHSV right, float epsilon = float.Epsilon) + => left.Hue.ApproximatelyEquals(right.Hue, epsilon) && left.Saturation.ApproximatelyEquals(right.Saturation, epsilon) && left.Value.ApproximatelyEquals(right.Value, epsilon); + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare with the current . + /// if the specified object is equal to the current ; otherwise, . + public override bool Equals(object? obj) => obj is ColorHSV objVec && Hue.Equals(objVec.Hue) && Saturation.Equals(objVec.Saturation) && Value.Equals(objVec.Value); + + /// + /// Generates a hash code for the . + /// + /// A hash code for the . + public override int GetHashCode() => HashCode.Combine(Hue, Saturation, Value); +} + +/// +/// Provides extension methods for type. +/// +public static class ColorHSVExtensions +{ + /// + public static ColorHSV Add(this ColorHSV color, ColorHSV value) => ColorHSV.Add(color, value); + + /// + public static ColorHSV Subtract(this ColorHSV color, ColorHSV value) => ColorHSV.Subtract(color, value); + + /// + public static ColorHSV Multiply(this ColorHSV color, float value) => ColorHSV.Multiply(color, value); + + /// + public static ColorHSV Divide(this ColorHSV color, float value) => ColorHSV.Divide(color, value); + + /// + public static ColorHSV FromTo(this ColorHSV from, ColorHSV to) => ColorHSV.FromTo(from, to); + + /// + public static ColorHSV Lerp(this ColorHSV from, ColorHSV to, float t) => ColorHSV.Lerp(from, to, t); + + /// + public static bool ApproximatelyEquals(this ColorHSV left, ColorHSV right, float epsilon = float.Epsilon) => ColorHSV.ApproximatelyEquals(left, right, epsilon); +} diff --git a/Engine.Core/Primitives/ColorRGB.cs b/Engine.Core/Primitives/ColorRGB.cs new file mode 100644 index 0000000..099b6b9 --- /dev/null +++ b/Engine.Core/Primitives/ColorRGB.cs @@ -0,0 +1,163 @@ +using System; + +namespace Syntriax.Engine.Core; + +/// +/// Represents an RGB color. +/// +/// Red value of the . +/// Green value of the . +/// Blue value of the . +/// +/// Initializes a new instance of the struct with the specified values. +/// +[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")] +public readonly struct ColorRGB(byte r, byte g, byte b) +{ + /// + /// The Red value of the . + /// + public readonly byte R = r; + + /// + /// The Green value of the . + /// + public readonly byte G = g; + + /// + /// The Blue value of the . + /// + public readonly byte B = b; + + public static ColorRGB operator -(ColorRGB color) => new((byte)(255 - color.R), (byte)(255 - color.G), (byte)(255 - color.B)); + public static ColorRGB operator +(ColorRGB left, ColorRGB right) => new((byte)(left.R + right.R), (byte)(left.G + right.G), (byte)(left.B + right.B)); + public static ColorRGB operator -(ColorRGB left, ColorRGB right) => new((byte)(left.R - right.R), (byte)(left.G - right.G), (byte)(left.B - right.B)); + public static ColorRGB operator *(ColorRGB left, ColorRGB right) => new((byte)(left.R * right.R), (byte)(left.G * right.G), (byte)(left.B * right.B)); + public static ColorRGB operator *(ColorRGB color, float value) => new((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value)); + public static ColorRGB operator *(float value, ColorRGB color) => new((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value)); + public static ColorRGB operator /(ColorRGB color, float value) => new((byte)(color.R / value), (byte)(color.G / value), (byte)(color.B / value)); + public static bool operator ==(ColorRGB left, ColorRGB right) => left.R == right.R && left.G == right.G && left.B == right.B; + public static bool operator !=(ColorRGB left, ColorRGB right) => left.R != right.R || left.G != right.G || left.B != right.B; + + public static implicit operator ColorRGB(ColorRGBA rgba) => new(rgba.R, rgba.G, rgba.B); + public static implicit operator ColorRGB(ColorHSV hsv) + { + float c = hsv.Value * hsv.Saturation; + float x = c * (1 - Math.Abs(hsv.Hue / 60 % 2 - 1)); + float m = hsv.Value - c; + + float r1 = 0, g1 = 0, b1 = 0; + + if (hsv.Hue >= 0 && hsv.Hue < 60) { r1 = c; g1 = x; b1 = 0; } + else if (hsv.Hue >= 60 && hsv.Hue < 120) { r1 = x; g1 = c; b1 = 0; } + else if (hsv.Hue >= 120 && hsv.Hue < 180) { r1 = 0; g1 = c; b1 = x; } + else if (hsv.Hue >= 180 && hsv.Hue < 240) { r1 = 0; g1 = x; b1 = c; } + else if (hsv.Hue >= 240 && hsv.Hue < 300) { r1 = x; g1 = 0; b1 = c; } + else if (hsv.Hue >= 300 && hsv.Hue < 360) { r1 = c; g1 = 0; b1 = x; } + + byte r = (byte)Math.RoundToInt((r1 + m) * 255); + byte g = (byte)Math.RoundToInt((g1 + m) * 255); + byte b = (byte)Math.RoundToInt((b1 + m) * 255); + + return new(r, g, b); + } + + /// + /// Inverts the given . + /// + /// The . + /// The inverted . + public static ColorRGB Invert(ColorRGB color) => -color; + + /// + /// Adds two s. + /// + /// The first . + /// The second . + /// The sum of the two s. + public static ColorRGB Add(ColorRGB left, ColorRGB right) => left + right; + + /// + /// Subtracts one from another. + /// + /// The to subtract from. + /// The to subtract. + /// The result of subtracting the second from the first. + public static ColorRGB Subtract(ColorRGB left, ColorRGB right) => left - right; + + /// + /// Multiplies a by a scalar value. + /// + /// The . + /// The scalar value. + /// The result of multiplying the by the scalar value. + public static ColorRGB Multiply(ColorRGB color, float value) => color * value; + + /// + /// Divides a by a scalar value. + /// + /// The . + /// The scalar value. + /// The result of dividing the by the scalar value. + public static ColorRGB Divide(ColorRGB color, float value) => color / value; + + /// + /// Calculates the from one point to another. + /// + /// The starting point. + /// The ending point. + /// The from the starting point to the ending point. + public static ColorRGB FromTo(ColorRGB from, ColorRGB to) => to - from; + + /// + /// Performs linear interpolation between two s. + /// + /// The starting (t = 0). + /// The ending (t = 1). + /// The interpolation parameter. + /// The interpolated . + public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t) => from + FromTo(from, to) * t; + + /// + /// Converts the to its string representation. + /// + /// A string representation of the . + public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})"; + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare with the current . + /// if the specified object is equal to the current ; otherwise, . + public override bool Equals(object? obj) => obj is ColorRGB objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B); + + /// + /// Generates a hash code for the . + /// + /// A hash code for the . + public override int GetHashCode() => HashCode.Combine(R, G, B); +} + +/// +/// Provides extension methods for type. +/// +public static class ColorRGBExtensions +{ + /// + public static ColorRGB Add(this ColorRGB color, ColorRGB value) => ColorRGB.Add(color, value); + + /// + public static ColorRGB Subtract(this ColorRGB color, ColorRGB value) => ColorRGB.Subtract(color, value); + + /// + public static ColorRGB Multiply(this ColorRGB color, float value) => ColorRGB.Multiply(color, value); + + /// + public static ColorRGB Divide(this ColorRGB color, float value) => ColorRGB.Divide(color, value); + + /// + public static ColorRGB FromTo(this ColorRGB from, ColorRGB to) => ColorRGB.FromTo(from, to); + + /// + public static ColorRGB Lerp(this ColorRGB from, ColorRGB to, float t) => ColorRGB.Lerp(from, to, t); +} diff --git a/Engine.Core/Primitives/ColorRGBA.cs b/Engine.Core/Primitives/ColorRGBA.cs new file mode 100644 index 0000000..bc5b35d --- /dev/null +++ b/Engine.Core/Primitives/ColorRGBA.cs @@ -0,0 +1,149 @@ +using System; + +namespace Syntriax.Engine.Core; + +/// +/// Represents an RGBA color. +/// +/// Red value of the . +/// Green value of the . +/// Blue value of the . +/// Alpha value of the . +/// +/// Initializes a new instance of the struct with the specified values. +/// +[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")] +public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255) +{ + /// + /// The Red value of the . + /// + public readonly byte R = r; + + /// + /// The Green value of the . + /// + public readonly byte G = g; + + /// + /// The Blue value of the . + /// + public readonly byte B = b; + + /// + /// The Alpha value of the . + /// + public readonly byte A = a; + + public static ColorRGBA operator -(ColorRGBA color) => new((byte)(255 - color.R), (byte)(255 - color.G), (byte)(255 - color.B), (byte)(255 - color.A)); + public static ColorRGBA operator +(ColorRGBA left, ColorRGBA right) => new((byte)(left.R + right.R), (byte)(left.G + right.G), (byte)(left.B + right.B), (byte)(left.A + right.A)); + public static ColorRGBA operator -(ColorRGBA left, ColorRGBA right) => new((byte)(left.R - right.R), (byte)(left.G - right.G), (byte)(left.B - right.B), (byte)(left.A - right.A)); + public static ColorRGBA operator *(ColorRGBA left, ColorRGBA right) => new((byte)(left.R * right.R), (byte)(left.G * right.G), (byte)(left.B * right.B), (byte)(left.A * right.A)); + public static ColorRGBA operator *(ColorRGBA color, float value) => new((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value)); + public static ColorRGBA operator *(float value, ColorRGBA color) => new((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value)); + public static ColorRGBA operator /(ColorRGBA color, float value) => new((byte)(color.R / value), (byte)(color.G / value), (byte)(color.B / value), (byte)(color.A / value)); + public static bool operator ==(ColorRGBA left, ColorRGBA right) => left.R == right.R && left.G == right.G && left.B == right.B && left.A == right.A; + public static bool operator !=(ColorRGBA left, ColorRGBA right) => left.R != right.R || left.G != right.G || left.B != right.B || left.A != right.A; + + public static implicit operator ColorRGBA(ColorRGB rgb) => new(rgb.R, rgb.G, rgb.B, 255); + public static implicit operator ColorRGBA(ColorHSV hsv) => (ColorRGB)hsv; + + /// + /// Inverts the given . + /// + /// The . + /// The inverted . + public static ColorRGBA Invert(ColorRGBA color) => -color; + + /// + /// Adds two s. + /// + /// The first . + /// The second . + /// The sum of the two s. + public static ColorRGBA Add(ColorRGBA left, ColorRGBA right) => left + right; + + /// + /// Subtracts one from another. + /// + /// The to subtract from. + /// The to subtract. + /// The result of subtracting the second from the first. + public static ColorRGBA Subtract(ColorRGBA left, ColorRGBA right) => left - right; + + /// + /// Multiplies a by a scalar value. + /// + /// The . + /// The scalar value. + /// The result of multiplying the by the scalar value. + public static ColorRGBA Multiply(ColorRGBA color, float value) => color * value; + + /// + /// Divides a by a scalar value. + /// + /// The . + /// The scalar value. + /// The result of dividing the by the scalar value. + public static ColorRGBA Divide(ColorRGBA color, float value) => color / value; + + /// + /// Calculates the from one point to another. + /// + /// The starting point. + /// The ending point. + /// The from the starting point to the ending point. + public static ColorRGBA FromTo(ColorRGBA from, ColorRGBA to) => to - from; + + /// + /// Performs linear interpolation between two s. + /// + /// The starting (t = 0). + /// The ending (t = 1). + /// The interpolation parameter. + /// The interpolated . + public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t) => from + FromTo(from, to) * t; + + /// + /// Converts the to its string representation. + /// + /// A string representation of the . + public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})"; + + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare with the current . + /// if the specified object is equal to the current ; otherwise, . + public override bool Equals(object? obj) => obj is ColorRGBA objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B) && A.Equals(objVec.A); + + /// + /// Generates a hash code for the . + /// + /// A hash code for the . + public override int GetHashCode() => HashCode.Combine(R, G, B, A); +} + +/// +/// Provides extension methods for type. +/// +public static class ColorRGBAExtensions +{ + /// + public static ColorRGBA Add(this ColorRGBA color, ColorRGBA value) => ColorRGBA.Add(color, value); + + /// + public static ColorRGBA Subtract(this ColorRGBA color, ColorRGBA value) => ColorRGBA.Subtract(color, value); + + /// + public static ColorRGBA Multiply(this ColorRGBA color, float value) => ColorRGBA.Multiply(color, value); + + /// + public static ColorRGBA Divide(this ColorRGBA color, float value) => ColorRGBA.Divide(color, value); + + /// + public static ColorRGBA FromTo(this ColorRGBA from, ColorRGBA to) => ColorRGBA.FromTo(from, to); + + /// + public static ColorRGBA Lerp(this ColorRGBA from, ColorRGBA to, float t) => ColorRGBA.Lerp(from, to, t); +} diff --git a/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorHSVConverter.cs b/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorHSVConverter.cs new file mode 100644 index 0000000..b5bb4ca --- /dev/null +++ b/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorHSVConverter.cs @@ -0,0 +1,28 @@ +using System; + +using Syntriax.Engine.Core; + +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Syntriax.Engine.Serializers.Yaml; + +public class ColorHSVConverter : EngineTypeYamlSerializerBase +{ + private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSV).Length + 1; + + public override ColorHSV Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + string value = parser.Consume().Value; + string insideParenthesis = value[SUBSTRING_START_LENGTH..^1]; + string[] values = insideParenthesis.Split(", "); + return new ColorHSV(float.Parse(values[0]), float.Parse(values[1]), float.Parse(values[2])); + } + + public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + ColorHSV hsv = (ColorHSV)value!; + emitter.Emit(new Scalar($"{nameof(ColorHSV)}({hsv.Hue}, {hsv.Saturation}, {hsv.Value})")); + } +} diff --git a/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBAConverter.cs b/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBAConverter.cs new file mode 100644 index 0000000..8243a0b --- /dev/null +++ b/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBAConverter.cs @@ -0,0 +1,28 @@ +using System; + +using Syntriax.Engine.Core; + +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Syntriax.Engine.Serializers.Yaml; + +public class ColorRGBAConverter : EngineTypeYamlSerializerBase +{ + private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGBA).Length + 1; + + public override ColorRGBA Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + string value = parser.Consume().Value; + string insideParenthesis = value[SUBSTRING_START_LENGTH..^1]; + string[] values = insideParenthesis.Split(", "); + return new ColorRGBA(byte.Parse(values[0]), byte.Parse(values[1]), byte.Parse(values[2]), byte.Parse(values[3])); + } + + public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + ColorRGBA rgb = (ColorRGBA)value!; + emitter.Emit(new Scalar($"{nameof(ColorRGBA)}({rgb.R}, {rgb.G}, {rgb.B}, {rgb.A})")); + } +} diff --git a/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBConverter.cs b/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBConverter.cs new file mode 100644 index 0000000..809db2a --- /dev/null +++ b/Engine.Serializers/Engine.Serializers.Yaml/Converters/Primitives/ColorRGBConverter.cs @@ -0,0 +1,28 @@ +using System; + +using Syntriax.Engine.Core; + +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Syntriax.Engine.Serializers.Yaml; + +public class ColorRGBConverter : EngineTypeYamlSerializerBase +{ + private static readonly int SUBSTRING_START_LENGTH = nameof(ColorRGB).Length + 1; + + public override ColorRGB Read(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + string value = parser.Consume().Value; + string insideParenthesis = value[SUBSTRING_START_LENGTH..^1]; + string[] values = insideParenthesis.Split(", "); + return new ColorRGB(byte.Parse(values[0]), byte.Parse(values[1]), byte.Parse(values[2])); + } + + public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + ColorRGB rgb = (ColorRGB)value!; + emitter.Emit(new Scalar($"{nameof(ColorRGB)}({rgb.R}, {rgb.G}, {rgb.B})")); + } +}