@ -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 < < 1 6 ) | ( rgba . A < < 2 4 ) ) ,
ColorHexFormat . Rgba = > ( uint ) ( ( rgba . A < < 0 ) | ( rgba . B < < 8 ) | ( rgba . G < < 1 6 ) | ( rgba . R < < 2 4 ) ) ,
_ = > 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 = 2 5 5 , 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 < < 1 6 ) | ( a < < 2 4 ) ) ; // 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 = 2 5 5 , 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 < < 2 4 ) | ( g < < 1 6 ) | ( 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' + 1 0 ) ;
return true ;
}
value = 0 ;
return false ;
}
}