Skip to content
Draft
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
70 changes: 70 additions & 0 deletions src/Avalonia.Base/Media/Color.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace Avalonia.Media
public
#endif
readonly struct Color : IEquatable<Color>
#if !BUILDTASK
, IFormattable
#endif
{
private const double byteToDouble = 1.0 / 255;

Expand Down Expand Up @@ -461,6 +464,73 @@ internal void ToString(System.Text.StringBuilder builder)
}
}

#if !BUILDTASK
/// <summary>
/// Returns a formatted string representation of the color.
/// </summary>
/// <param name="format">
/// The format specifier. All color types support all specifiers via auto-conversion.
/// Uppercase includes alpha, lowercase excludes it. <c>%</c> suffix = percent mode:
/// <list type="bullet">
/// <item><c>null</c> or <c>""</c> — Default (known name or <c>#aarrggbb</c>)</item>
/// <item><c>"X"</c> — XAML hex with alpha: <c>#AARRGGBB</c></item>
/// <item><c>"x"</c> — Hex without alpha: <c>#RRGGBB</c></item>
/// <item><c>"H"</c> — HTML hex with alpha: <c>#RRGGBBAA</c></item>
/// <item><c>"R"</c> — CSS rgba absolute: <c>rgba(r, g, b, a)</c></item>
/// <item><c>"r"</c> — CSS rgb absolute: <c>rgb(r, g, b)</c></item>
/// <item><c>"R%"</c> — CSS rgba percent: <c>rgba(r%, g%, b%, a%)</c></item>
/// <item><c>"r%"</c> — CSS rgb percent: <c>rgb(r%, g%, b%)</c></item>
/// <item><c>"L"/"l"/"L%"/"l%"</c> — CSS hsl/hsla (auto-converts to HSL)</item>
/// <item><c>"V"/"v"/"V%"/"v%"</c> — CSS hsv/hsva (auto-converts to HSV)</item>
/// </list>
/// <para><c>"C"</c> and <c>"A"</c> are reserved for future complex format strings.</para>
/// </param>
/// <param name="formatProvider">Ignored. Color formatting is culture-invariant.</param>
/// <returns>The formatted string representation.</returns>
/// <exception cref="FormatException">Thrown when <paramref name="format"/> is not recognized.</exception>
public string ToString(string? format, IFormatProvider? formatProvider)
{
if (string.IsNullOrEmpty(format))
{
return ToString();
}

return format switch
{
"X" => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
"x" => $"#{R:X2}{G:X2}{B:X2}",
"H" => $"#{R:X2}{G:X2}{B:X2}{A:X2}",

"R" => string.Format(CultureInfo.InvariantCulture, "rgba({0}, {1}, {2}, {3:F2})", R, G, B, A * byteToDouble),
"r" => string.Format(CultureInfo.InvariantCulture, "rgb({0}, {1}, {2})", R, G, B),
"R%" => FormatRgbaPercent(),
"r%" => FormatRgbPercent(),

"L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider),
"V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider),

_ => throw new FormatException($"Format string '{format}' is not supported.")
};
}

private string FormatRgbaPercent()
{
int rPct = (int)Math.Round(R * byteToDouble * 100.0);
int gPct = (int)Math.Round(G * byteToDouble * 100.0);
int bPct = (int)Math.Round(B * byteToDouble * 100.0);
int aPct = (int)Math.Round(A * byteToDouble * 100.0);
return string.Format(CultureInfo.InvariantCulture, "rgba({0}%, {1}%, {2}%, {3}%)", rPct, gPct, bPct, aPct);
}

private string FormatRgbPercent()
{
int rPct = (int)Math.Round(R * byteToDouble * 100.0);
int gPct = (int)Math.Round(G * byteToDouble * 100.0);
int bPct = (int)Math.Round(B * byteToDouble * 100.0);
return string.Format(CultureInfo.InvariantCulture, "rgb({0}%, {1}%, {2}%)", rPct, gPct, bPct);
}
#endif

/// <summary>
/// Returns the integer representation of the color.
/// </summary>
Expand Down
85 changes: 84 additions & 1 deletion src/Avalonia.Base/Media/HslColor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ namespace Avalonia.Media
public
#endif
readonly struct HslColor : IEquatable<HslColor>
#if !BUILDTASK
, IFormattable
#endif
{
/// <summary>
/// Initializes a new instance of the <see cref="HslColor"/> struct.
Expand Down Expand Up @@ -177,6 +180,86 @@ public HsvColor ToHsv()
return HslColor.ToHsv(H, S, L, A);
}

#if !BUILDTASK
/// <summary>
/// Returns a formatted string representation of the HSL color.
/// </summary>
/// <param name="format">
/// The format specifier. All color types support all specifiers via auto-conversion.
/// Uppercase includes alpha, lowercase excludes it. <c>%</c> suffix = percent mode.
/// <list type="bullet">
/// <item><c>null</c> or <c>""</c> — Default (full-precision <c>hsla(h, s, l, a)</c>)</item>
/// <item><c>"X"</c> — XAML hex: <c>#AARRGGBB</c></item>
/// <item><c>"x"</c> — Hex: <c>#RRGGBB</c></item>
/// <item><c>"H"</c> — HTML hex: <c>#RRGGBBAA</c></item>
/// <item><c>"R"/"r"/"R%"/"r%"</c> — CSS rgb/rgba (auto-converts to RGB)</item>
/// <item><c>"L"</c> — CSS hsla: <c>hsla(h, s%, l%, a)</c></item>
/// <item><c>"l"</c> — CSS hsl: <c>hsl(h, s%, l%)</c></item>
/// <item><c>"L%"</c> — All percent: <c>hsla(h%, s%, l%, a%)</c></item>
/// <item><c>"l%"</c> — All percent: <c>hsl(h%, s%, l%)</c></item>
/// <item><c>"V"/"v"/"V%"/"v%"</c> — CSS hsv/hsva (auto-converts to HSV)</item>
/// </list>
/// <para><c>"C"</c> and <c>"A"</c> are reserved for future complex format strings.</para>
/// </param>
/// <param name="formatProvider">Ignored. Color formatting is culture-invariant.</param>
/// <returns>The formatted string representation.</returns>
/// <exception cref="FormatException">Thrown when <paramref name="format"/> is not recognized.</exception>
public string ToString(string? format, IFormatProvider? formatProvider)
{
if (string.IsNullOrEmpty(format))
{
return ToString();
}

return format switch
{
"X" or "x" or "H" or "R" or "r" or "R%" or "r%" => ToRgb().ToString(format, formatProvider),

"L" => FormatHsla(),
"l" => FormatHsl(),
"L%" => FormatHslaPercent(),
"l%" => FormatHslPercent(),

"V" or "v" or "V%" or "v%" => ToHsv().ToString(format, formatProvider),

_ => throw new FormatException($"Format string '{format}' is not supported.")
};
}

private string FormatHsla()
{
int hDeg = (int)Math.Round(H);
int sPct = (int)Math.Round(S * 100.0);
int lPct = (int)Math.Round(L * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsla({0}, {1}%, {2}%, {3:F2})", hDeg, sPct, lPct, A);
}

private string FormatHsl()
{
int hDeg = (int)Math.Round(H);
int sPct = (int)Math.Round(S * 100.0);
int lPct = (int)Math.Round(L * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsl({0}, {1}%, {2}%)", hDeg, sPct, lPct);
}

private string FormatHslaPercent()
{
int hPct = (int)Math.Round(H / 360.0 * 100.0);
int sPct = (int)Math.Round(S * 100.0);
int lPct = (int)Math.Round(L * 100.0);
int aPct = (int)Math.Round(A * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsla({0}%, {1}%, {2}%, {3}%)", hPct, sPct, lPct, aPct);
}

private string FormatHslPercent()
{
int hPct = (int)Math.Round(H / 360.0 * 100.0);
int sPct = (int)Math.Round(S * 100.0);
int lPct = (int)Math.Round(L * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsl({0}%, {1}%, {2}%)", hPct, sPct, lPct);
}
#endif

/// <inheritdoc/>
public override string ToString()
{
Expand All @@ -193,7 +276,7 @@ public override string ToString()
// hsla(230, 1.0, 0.5, 1.0)
//

sb.Append("hsva(");
sb.Append("hsla(");
sb.Append(H.ToString(CultureInfo.InvariantCulture));
sb.Append(", ");
sb.Append(S.ToString(CultureInfo.InvariantCulture));
Expand Down
83 changes: 83 additions & 0 deletions src/Avalonia.Base/Media/HsvColor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ namespace Avalonia.Media
public
#endif
readonly struct HsvColor : IEquatable<HsvColor>
#if !BUILDTASK
, IFormattable
#endif
{
/// <summary>
/// Initializes a new instance of the <see cref="HsvColor"/> struct.
Expand Down Expand Up @@ -207,6 +210,86 @@ public HslColor ToHsl()
return HsvColor.ToHsl(H, S, V, A);
}

#if !BUILDTASK
/// <summary>
/// Returns a formatted string representation of the HSV color.
/// </summary>
/// <param name="format">
/// The format specifier. All color types support all specifiers via auto-conversion.
/// Uppercase includes alpha, lowercase excludes it. <c>%</c> suffix = percent mode.
/// <list type="bullet">
/// <item><c>null</c> or <c>""</c> — Default (full-precision <c>hsva(h, s, v, a)</c>)</item>
/// <item><c>"X"</c> — XAML hex: <c>#AARRGGBB</c></item>
/// <item><c>"x"</c> — Hex: <c>#RRGGBB</c></item>
/// <item><c>"H"</c> — HTML hex: <c>#RRGGBBAA</c></item>
/// <item><c>"R"/"r"/"R%"/"r%"</c> — CSS rgb/rgba (auto-converts to RGB)</item>
/// <item><c>"L"/"l"/"L%"/"l%"</c> — CSS hsl/hsla (auto-converts to HSL)</item>
/// <item><c>"V"</c> — CSS hsva: <c>hsva(h, s%, v%, a)</c></item>
/// <item><c>"v"</c> — CSS hsv: <c>hsv(h, s%, v%)</c></item>
/// <item><c>"V%"</c> — All percent: <c>hsva(h%, s%, v%, a%)</c></item>
/// <item><c>"v%"</c> — All percent: <c>hsv(h%, s%, v%)</c></item>
/// </list>
/// <para><c>"C"</c> and <c>"A"</c> are reserved for future complex format strings.</para>
/// </param>
/// <param name="formatProvider">Ignored. Color formatting is culture-invariant.</param>
/// <returns>The formatted string representation.</returns>
/// <exception cref="FormatException">Thrown when <paramref name="format"/> is not recognized.</exception>
public string ToString(string? format, IFormatProvider? formatProvider)
{
if (string.IsNullOrEmpty(format))
{
return ToString();
}

return format switch
{
"X" or "x" or "H" or "R" or "r" or "R%" or "r%" => ToRgb().ToString(format, formatProvider),

"L" or "l" or "L%" or "l%" => ToHsl().ToString(format, formatProvider),

"V" => FormatHsva(),
"v" => FormatHsv(),
"V%" => FormatHsvaPercent(),
"v%" => FormatHsvPercent(),

_ => throw new FormatException($"Format string '{format}' is not supported.")
};
}

private string FormatHsva()
{
int hDeg = (int)Math.Round(H);
int sPct = (int)Math.Round(S * 100.0);
int vPct = (int)Math.Round(V * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsva({0}, {1}%, {2}%, {3:F2})", hDeg, sPct, vPct, A);
}

private string FormatHsv()
{
int hDeg = (int)Math.Round(H);
int sPct = (int)Math.Round(S * 100.0);
int vPct = (int)Math.Round(V * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsv({0}, {1}%, {2}%)", hDeg, sPct, vPct);
}

private string FormatHsvaPercent()
{
int hPct = (int)Math.Round(H / 360.0 * 100.0);
int sPct = (int)Math.Round(S * 100.0);
int vPct = (int)Math.Round(V * 100.0);
int aPct = (int)Math.Round(A * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsva({0}%, {1}%, {2}%, {3}%)", hPct, sPct, vPct, aPct);
}

private string FormatHsvPercent()
{
int hPct = (int)Math.Round(H / 360.0 * 100.0);
int sPct = (int)Math.Round(S * 100.0);
int vPct = (int)Math.Round(V * 100.0);
return string.Format(CultureInfo.InvariantCulture, "hsv({0}%, {1}%, {2}%)", hPct, sPct, vPct);
}
#endif

/// <inheritdoc/>
public override string ToString()
{
Expand Down
Loading