@ -2,6 +2,7 @@
using System.Globalization ;
using Avalonia.Data.Converters ;
using Avalonia.Media ;
using Avalonia.Utilities ;
namespace Avalonia.Controls.Converters
{
@ -10,6 +11,11 @@ namespace Avalonia.Controls.Converters
/// </summary>
public class ColorToHexConverter : IValueConverter
{
/// <summary>
/// Gets or sets the position of a color's alpha component relative to all other components.
/// </summary>
public AlphaComponentPosition AlphaPosition { get ; set ; } = AlphaComponentPosition . Leading ;
/// <inheritdoc/>
public object? Convert (
object? value ,
@ -42,16 +48,7 @@ namespace Avalonia.Controls.Converters
return AvaloniaProperty . UnsetValue ;
}
string hexColor = color . ToUint32 ( ) . ToString ( "x8" , CultureInfo . InvariantCulture ) . ToUpperInvariant ( ) ;
if ( includeSymbol = = false )
{
// TODO: When .net standard 2.0 is dropped, replace the below line
//hexColor = hexColor.Replace("#", string.Empty, StringComparison.Ordinal);
hexColor = hexColor . Replace ( "#" , string . Empty ) ;
}
return hexColor ;
return ToHexString ( color , AlphaPosition , includeSymbol ) ;
}
/// <inheritdoc/>
@ -62,21 +59,159 @@ namespace Avalonia.Controls.Converters
CultureInfo culture )
{
string hexValue = value ? . ToString ( ) ? ? string . Empty ;
return ParseHexString ( hexValue , AlphaPosition ) ? ? AvaloniaProperty . UnsetValue ;
}
/// <summary>
/// Converts the given color to its hex color value string representation.
/// </summary>
/// <param name="color">The color to represent as a hex value string.</param>
/// <param name="alphaPosition">The output position of the alpha component.</param>
/// <param name="includeSymbol">Whether the hex symbol '#' will be added.</param>
/// <returns>The input color converted to its hex value string.</returns>
public static string ToHexString (
Color color ,
AlphaComponentPosition alphaPosition ,
bool includeSymbol = false )
{
uint intColor ;
if ( alphaPosition = = AlphaComponentPosition . Trailing )
{
intColor = ( ( uint ) color . R < < 2 4 ) | ( ( uint ) color . G < < 1 6 ) | ( ( uint ) color . B < < 8 ) | ( uint ) color . A ;
}
else
{
// Default is Leading alpha
intColor = ( ( uint ) color . A < < 2 4 ) | ( ( uint ) color . R < < 1 6 ) | ( ( uint ) color . G < < 8 ) | ( uint ) color . B ;
}
if ( Color . TryParse ( hexValue , out Color color ) )
string hexColor = intColor . ToString ( "x8" , CultureInfo . InvariantCulture ) . ToUpperInvariant ( ) ;
if ( includeSymbol )
{
hexColor = '#' + hexColor ;
}
return hexColor ;
}
/// <summary>
/// Parses a hex color value string into a new <see cref="Color"/>.
/// </summary>
/// <param name="hexColor">The hex color string to parse.</param>
/// <param name="alphaPosition">The input position of the alpha component.</param>
/// <returns>The parsed <see cref="Color"/>; otherwise, null.</returns>
public static Color ? ParseHexString (
string hexColor ,
AlphaComponentPosition alphaPosition )
{
hexColor = hexColor . Trim ( ) ;
if ( ! hexColor . StartsWith ( "#" , StringComparison . Ordinal ) )
{
hexColor = "#" + hexColor ;
}
if ( TryParseHexFormat ( hexColor . AsSpan ( ) , alphaPosition , out Color color ) )
{
return color ;
}
else if ( hexValue . StartsWith ( "#" , StringComparison . Ordinal ) = = false & &
Color . TryParse ( "#" + hexValue , out Color color2 ) )
return null ;
}
/// <summary>
/// Parses the given span of characters representing a hex color value into a new <see cref="Color"/>.
/// </summary>
/// <remarks>
/// This is based on the Color.TryParseHexFormat() method.
/// It is copied because it needs to be extended to handle alpha position.
/// However, the alpha position enum is only available in the controls namespace with the ColorPicker control.
/// </remarks>
private static bool TryParseHexFormat (
ReadOnlySpan < char > s ,
AlphaComponentPosition alphaPosition ,
out Color color )
{
static bool TryParseCore ( ReadOnlySpan < char > input , AlphaComponentPosition alphaPosition , ref Color color )
{
return color2 ;
var alphaComponent = 0 u ;
if ( input . Length = = 6 )
{
if ( alphaPosition = = AlphaComponentPosition . Trailing )
{
alphaComponent = 0x000000FF ;
}
else
{
alphaComponent = 0xFF000000 ;
}
}
else if ( input . Length ! = 8 )
{
return false ;
}
if ( ! input . TryParseUInt ( NumberStyles . HexNumber , CultureInfo . InvariantCulture , out var parsed ) )
{
return false ;
}
if ( alphaComponent ! = 0 )
{
if ( alphaPosition = = AlphaComponentPosition . Trailing )
{
parsed = ( parsed < < 8 ) | alphaComponent ;
}
else
{
parsed = parsed | alphaComponent ;
}
}
if ( alphaPosition = = AlphaComponentPosition . Trailing )
{
// #RRGGBBAA
color = new Color (
a : ( byte ) ( parsed & 0xFF ) ,
r : ( byte ) ( ( parsed > > 2 4 ) & 0xFF ) ,
g : ( byte ) ( ( parsed > > 1 6 ) & 0xFF ) ,
b : ( byte ) ( ( parsed > > 8 ) & 0xFF ) ) ;
}
else
{
// #AARRGGBB
color = new Color (
a : ( byte ) ( ( parsed > > 2 4 ) & 0xFF ) ,
r : ( byte ) ( ( parsed > > 1 6 ) & 0xFF ) ,
g : ( byte ) ( ( parsed > > 8 ) & 0xFF ) ,
b : ( byte ) ( parsed & 0xFF ) ) ;
}
return true ;
}
else
color = default ;
ReadOnlySpan < char > input = s . Slice ( 1 ) ;
// Handle shorthand cases like #FFF (RGB) or #FFFF (ARGB).
if ( input . Length = = 3 | | input . Length = = 4 )
{
// Invalid hex color value provided
return AvaloniaProperty . UnsetValue ;
var extendedLength = 2 * input . Length ;
Span < char > extended = stackalloc char [ extendedLength ] ;
for ( int i = 0 ; i < input . Length ; i + + )
{
extended [ 2 * i + 0 ] = input [ i ] ;
extended [ 2 * i + 1 ] = input [ i ] ;
}
return TryParseCore ( extended , alphaPosition , ref color ) ;
}
return TryParseCore ( input , alphaPosition , ref color ) ;
}
}
}