|
|
|
@ -1,6 +1,7 @@ |
|
|
|
// Copyright (c) Six Labors.
|
|
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
|
|
|
|
using System.Globalization; |
|
|
|
using System.Numerics; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using SixLabors.ImageSharp.PixelFormats; |
|
|
|
@ -126,66 +127,91 @@ public readonly partial struct Color : IEquatable<Color> |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new instance of the <see cref="Color"/> struct
|
|
|
|
/// from the given hexadecimal string.
|
|
|
|
/// Gets a <see cref="Color"/> from the given hexadecimal string.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="hex">
|
|
|
|
/// The hexadecimal representation of the combined color components arranged
|
|
|
|
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
|
|
|
|
/// The hexadecimal representation of the combined color components.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="format">
|
|
|
|
/// The format of the hexadecimal string to parse, if applicable. Defaults to <see cref="ColorHexFormat.Rgba"/>.
|
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="Color"/>.
|
|
|
|
/// The <see cref="Color"/> equivalent of the hexadecimal input.
|
|
|
|
/// </returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Color ParseHex(string hex) |
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
/// Thrown when the <paramref name="hex"/> is not in the correct format.
|
|
|
|
/// </exception>
|
|
|
|
public static Color ParseHex(string hex, ColorHexFormat format = ColorHexFormat.Rgba) |
|
|
|
{ |
|
|
|
Rgba32 rgba = Rgba32.ParseHex(hex); |
|
|
|
return FromPixel(rgba); |
|
|
|
Guard.NotNull(hex, nameof(hex)); |
|
|
|
|
|
|
|
if (!TryParseHex(hex, out Color color, format)) |
|
|
|
{ |
|
|
|
throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); |
|
|
|
} |
|
|
|
|
|
|
|
return color; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Attempts to creates a new instance of the <see cref="Color"/> struct
|
|
|
|
/// from the given hexadecimal string.
|
|
|
|
/// Gets a <see cref="Color"/> from the given hexadecimal string.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="hex">
|
|
|
|
/// The hexadecimal representation of the combined color components arranged
|
|
|
|
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
|
|
|
|
/// The hexadecimal representation of the combined color components.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="result">
|
|
|
|
/// When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal input.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="format">
|
|
|
|
/// The format of the hexadecimal string to parse, if applicable. Defaults to <see cref="ColorHexFormat.Rgba"/>.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="result">When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal input.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="bool"/>.
|
|
|
|
/// <see langword="true"/> if the parsing was successful; otherwise, <see langword="false"/>.
|
|
|
|
/// </returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static bool TryParseHex(string hex, out Color result) |
|
|
|
public static bool TryParseHex(string hex, out Color result, ColorHexFormat format = ColorHexFormat.Rgba) |
|
|
|
{ |
|
|
|
result = default; |
|
|
|
|
|
|
|
if (Rgba32.TryParseHex(hex, out Rgba32 rgba)) |
|
|
|
if (format == ColorHexFormat.Argb) |
|
|
|
{ |
|
|
|
result = FromPixel(rgba); |
|
|
|
return true; |
|
|
|
if (TryParseArgbHex(hex, out Argb32 argb)) |
|
|
|
{ |
|
|
|
result = FromPixel(argb); |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (format == ColorHexFormat.Rgba) |
|
|
|
{ |
|
|
|
if (TryParseRgbaHex(hex, out Rgba32 rgba)) |
|
|
|
{ |
|
|
|
result = FromPixel(rgba); |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new instance of the <see cref="Color"/> struct
|
|
|
|
/// from the given input string.
|
|
|
|
/// Gets a <see cref="Color"/> from the given input string.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="input">
|
|
|
|
/// The name of the color or the hexadecimal representation of the combined color components arranged
|
|
|
|
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
|
|
|
|
/// The name of the color or the hexadecimal representation of the combined color components.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="format">
|
|
|
|
/// The format of the hexadecimal string to parse, if applicable. Defaults to <see cref="ColorHexFormat.Rgba"/>.
|
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="Color"/>.
|
|
|
|
/// The <see cref="Color"/> equivalent of the input string.
|
|
|
|
/// </returns>
|
|
|
|
/// <exception cref="ArgumentException">Input string is not in the correct format.</exception>
|
|
|
|
public static Color Parse(string input) |
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
/// Thrown when the <paramref name="input"/> is not in the correct format.
|
|
|
|
/// </exception>
|
|
|
|
public static Color Parse(string input, ColorHexFormat format = ColorHexFormat.Rgba) |
|
|
|
{ |
|
|
|
Guard.NotNull(input, nameof(input)); |
|
|
|
|
|
|
|
if (!TryParse(input, out Color color)) |
|
|
|
if (!TryParse(input, out Color color, format)) |
|
|
|
{ |
|
|
|
throw new ArgumentException("Input string is not in the correct format.", nameof(input)); |
|
|
|
} |
|
|
|
@ -194,18 +220,21 @@ public readonly partial struct Color : IEquatable<Color> |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Attempts to creates a new instance of the <see cref="Color"/> struct
|
|
|
|
/// from the given input string.
|
|
|
|
/// Tries to create a new instance of the <see cref="Color"/> struct from the given input string.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="input">
|
|
|
|
/// The name of the color or the hexadecimal representation of the combined color components arranged
|
|
|
|
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
|
|
|
|
/// The name of the color or the hexadecimal representation of the combined color components.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="result">
|
|
|
|
/// When this method returns, contains the <see cref="Color"/> equivalent of the input string.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="format">
|
|
|
|
/// The format of the hexadecimal string to parse, if applicable. Defaults to <see cref="ColorHexFormat.Rgba"/>.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="result">When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal input.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="bool"/>.
|
|
|
|
/// <see langword="true"/> if the parsing was successful; otherwise, <see langword="false"/>.
|
|
|
|
/// </returns>
|
|
|
|
public static bool TryParse(string input, out Color result) |
|
|
|
public static bool TryParse(string input, out Color result, ColorHexFormat format = ColorHexFormat.Rgba) |
|
|
|
{ |
|
|
|
result = default; |
|
|
|
|
|
|
|
@ -219,7 +248,13 @@ public readonly partial struct Color : IEquatable<Color> |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
return TryParseHex(input, out result); |
|
|
|
result = default; |
|
|
|
if (string.IsNullOrWhiteSpace(input)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return TryParseHex(input, out result, format); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -227,6 +262,7 @@ public readonly partial struct Color : IEquatable<Color> |
|
|
|
/// </summary>
|
|
|
|
/// <param name="alpha">The new value of alpha [0..1].</param>
|
|
|
|
/// <returns>The color having it's alpha channel altered.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public Color WithAlpha(float alpha) |
|
|
|
{ |
|
|
|
Vector4 v = this.ToScaledVector4(); |
|
|
|
@ -235,22 +271,32 @@ public readonly partial struct Color : IEquatable<Color> |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the hexadecimal representation of the color instance in rrggbbaa form.
|
|
|
|
/// Gets the hexadecimal string representation of the color instance.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="format">
|
|
|
|
/// The format of the hexadecimal string to return. Defaults to <see cref="ColorHexFormat.Rgba"/>.
|
|
|
|
/// </param>
|
|
|
|
/// <returns>A hexadecimal string representation of the value.</returns>
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="format"/> is not supported.</exception>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public string ToHex() |
|
|
|
public string ToHex(ColorHexFormat format = ColorHexFormat.Rgba) |
|
|
|
{ |
|
|
|
if (this.boxedHighPrecisionPixel is not null) |
|
|
|
Rgba32 rgba = (this.boxedHighPrecisionPixel is not null) |
|
|
|
? this.boxedHighPrecisionPixel.ToRgba32() |
|
|
|
: Rgba32.FromScaledVector4(this.data); |
|
|
|
|
|
|
|
uint hexOrder = format switch |
|
|
|
{ |
|
|
|
return this.boxedHighPrecisionPixel.ToRgba32().ToHex(); |
|
|
|
} |
|
|
|
ColorHexFormat.Argb => (uint)((rgba.B << 0) | (rgba.G << 8) | (rgba.R << 16) | (rgba.A << 24)), |
|
|
|
ColorHexFormat.Rgba => (uint)((rgba.A << 0) | (rgba.B << 8) | (rgba.G << 16) | (rgba.R << 24)), |
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(format), format, "Unsupported color hex format.") |
|
|
|
}; |
|
|
|
|
|
|
|
return Rgba32.FromScaledVector4(this.data).ToHex(); |
|
|
|
return hexOrder.ToString("X8", CultureInfo.InvariantCulture); |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public override string ToString() => this.ToHex(); |
|
|
|
public override string ToString() => this.ToHex(ColorHexFormat.Rgba); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Converts the color instance to a specified <typeparamref name="TPixel"/> type.
|
|
|
|
@ -336,4 +382,241 @@ public readonly partial struct Color : IEquatable<Color> |
|
|
|
|
|
|
|
return this.boxedHighPrecisionPixel.GetHashCode(); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the hexadecimal string representation of the color instance in the format RRGGBBAA.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="hex">
|
|
|
|
/// The hexadecimal representation of the combined color components.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="result">
|
|
|
|
/// When this method returns, contains the <see cref="Rgba32"/> equivalent of the hexadecimal input.
|
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
|
|
|
/// <see langword="true"/> if the parsing was successful; otherwise, <see langword="false"/>.
|
|
|
|
/// </returns>
|
|
|
|
private static bool TryParseRgbaHex(string? hex, out Rgba32 result) |
|
|
|
{ |
|
|
|
result = default; |
|
|
|
|
|
|
|
if (!TryConvertToRgbaUInt32(hex, out uint packedValue)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
result = Unsafe.As<uint, Rgba32>(ref packedValue); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the hexadecimal string representation of the color instance in the format AARRGGBB.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="hex">
|
|
|
|
/// The hexadecimal representation of the combined color components.
|
|
|
|
/// </param>
|
|
|
|
/// <param name="result">
|
|
|
|
/// When this method returns, contains the <see cref="Argb32"/> equivalent of the hexadecimal input.
|
|
|
|
/// </param>
|
|
|
|
/// <returns>
|
|
|
|
/// <see langword="true"/> if the parsing was successful; otherwise, <see langword="false"/>.
|
|
|
|
/// </returns>
|
|
|
|
private static bool TryParseArgbHex(string? hex, out Argb32 result) |
|
|
|
{ |
|
|
|
result = default; |
|
|
|
|
|
|
|
if (!TryConvertToArgbUInt32(hex, out uint packedValue)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
result = Unsafe.As<uint, Argb32>(ref packedValue); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
private static bool TryConvertToRgbaUInt32(string? value, out uint result) |
|
|
|
{ |
|
|
|
result = default; |
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(value)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
ReadOnlySpan<char> hex = value.AsSpan(); |
|
|
|
|
|
|
|
if (hex[0] == '#') |
|
|
|
{ |
|
|
|
hex = hex[1..]; |
|
|
|
} |
|
|
|
|
|
|
|
byte a = 255, r, g, b; |
|
|
|
|
|
|
|
switch (hex.Length) |
|
|
|
{ |
|
|
|
case 8: |
|
|
|
if (!TryParseByte(hex[0], hex[1], out r) || |
|
|
|
!TryParseByte(hex[2], hex[3], out g) || |
|
|
|
!TryParseByte(hex[4], hex[5], out b) || |
|
|
|
!TryParseByte(hex[6], hex[7], out a)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 6: |
|
|
|
if (!TryParseByte(hex[0], hex[1], out r) || |
|
|
|
!TryParseByte(hex[2], hex[3], out g) || |
|
|
|
!TryParseByte(hex[4], hex[5], out b)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 4: |
|
|
|
if (!TryExpand(hex[0], out r) || |
|
|
|
!TryExpand(hex[1], out g) || |
|
|
|
!TryExpand(hex[2], out b) || |
|
|
|
!TryExpand(hex[3], out a)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 3: |
|
|
|
if (!TryExpand(hex[0], out r) || |
|
|
|
!TryExpand(hex[1], out g) || |
|
|
|
!TryExpand(hex[2], out b)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
result = (uint)(r | (g << 8) | (b << 16) | (a << 24)); // RGBA layout
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
private static bool TryConvertToArgbUInt32(string? value, out uint result) |
|
|
|
{ |
|
|
|
result = default; |
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(value)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
ReadOnlySpan<char> hex = value.AsSpan(); |
|
|
|
|
|
|
|
if (hex[0] == '#') |
|
|
|
{ |
|
|
|
hex = hex[1..]; |
|
|
|
} |
|
|
|
|
|
|
|
byte a = 255, r, g, b; |
|
|
|
|
|
|
|
switch (hex.Length) |
|
|
|
{ |
|
|
|
case 8: |
|
|
|
if (!TryParseByte(hex[0], hex[1], out a) || |
|
|
|
!TryParseByte(hex[2], hex[3], out r) || |
|
|
|
!TryParseByte(hex[4], hex[5], out g) || |
|
|
|
!TryParseByte(hex[6], hex[7], out b)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 6: |
|
|
|
if (!TryParseByte(hex[0], hex[1], out r) || |
|
|
|
!TryParseByte(hex[2], hex[3], out g) || |
|
|
|
!TryParseByte(hex[4], hex[5], out b)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 4: |
|
|
|
if (!TryExpand(hex[0], out a) || |
|
|
|
!TryExpand(hex[1], out r) || |
|
|
|
!TryExpand(hex[2], out g) || |
|
|
|
!TryExpand(hex[3], out b)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 3: |
|
|
|
if (!TryExpand(hex[0], out r) || |
|
|
|
!TryExpand(hex[1], out g) || |
|
|
|
!TryExpand(hex[2], out b)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
result = (uint)((b << 24) | (g << 16) | (r << 8) | a); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private static bool TryParseByte(char hi, char lo, out byte value) |
|
|
|
{ |
|
|
|
if (TryConvertHexCharToByte(hi, out byte high) && TryConvertHexCharToByte(lo, out byte low)) |
|
|
|
{ |
|
|
|
value = (byte)((high << 4) | low); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
value = 0; |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private static bool TryExpand(char c, out byte value) |
|
|
|
{ |
|
|
|
if (TryConvertHexCharToByte(c, out byte nibble)) |
|
|
|
{ |
|
|
|
value = (byte)((nibble << 4) | nibble); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
value = 0; |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
private static bool TryConvertHexCharToByte(char c, out byte value) |
|
|
|
{ |
|
|
|
if ((uint)(c - '0') <= 9) |
|
|
|
{ |
|
|
|
value = (byte)(c - '0'); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
char lower = (char)(c | 0x20); // Normalize to lowercase
|
|
|
|
|
|
|
|
if ((uint)(lower - 'a') <= 5) |
|
|
|
{ |
|
|
|
value = (byte)(lower - 'a' + 10); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
value = 0; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|