diff --git a/Directory.packages.props b/Directory.packages.props
index 8c3acea706..8e48c9cc90 100644
--- a/Directory.packages.props
+++ b/Directory.packages.props
@@ -32,5 +32,6 @@
+
diff --git a/src/MaterialDesign3.MaterialColorUtilities/DynamicColor/DynamicColor.cs b/src/MaterialDesign3.MaterialColorUtilities/DynamicColor/DynamicColor.cs
index a8ee399555..3696a20296 100644
--- a/src/MaterialDesign3.MaterialColorUtilities/DynamicColor/DynamicColor.cs
+++ b/src/MaterialDesign3.MaterialColorUtilities/DynamicColor/DynamicColor.cs
@@ -140,6 +140,9 @@ public int GetArgb(DynamicScheme scheme)
return (argb & 0x00ffffff) | (alpha << 24);
}
+ public System.Windows.Media.Color GetColor(DynamicScheme scheme)
+ => ColorUtils.ColorFromArgb(GetArgb(scheme));
+
public Hct GetHct(DynamicScheme scheme)
{
if (hctCache.TryGetValue(scheme, out var cached))
@@ -320,4 +323,4 @@ private void ValidateExtendedColor(SpecVersion specVersion, DynamicColor extende
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/MaterialDesign3.MaterialColorUtilities/Scheme/DynamicSchemeFactory.cs b/src/MaterialDesign3.MaterialColorUtilities/Scheme/DynamicSchemeFactory.cs
new file mode 100644
index 0000000000..05423137a7
--- /dev/null
+++ b/src/MaterialDesign3.MaterialColorUtilities/Scheme/DynamicSchemeFactory.cs
@@ -0,0 +1,71 @@
+using System.Windows.Media;
+
+namespace MaterialColorUtilities;
+
+///
+/// Factory for creating a dynamic color scheme.
+///
+public static class DynamicSchemeFactory
+{
+ ///
+ /// Factory method for creating a dynamic color scheme.
+ ///
+ ///
+ /// The colors are optional. If any of them are null,
+ /// the color will be automatically generated based on the source color.
+ ///
+ public static DynamicScheme Create(
+ Color sourceColor,
+ Variant variant,
+ bool isDark,
+ double contrastLevel,
+ Platform platform,
+ SpecVersion specVersion,
+ Color? primary,
+ Color? secondary,
+ Color? tertiary,
+ Color? neutral,
+ Color? neutralVariant,
+ Color? error)
+ {
+ var sourceColorHct = Hct.FromInt(ColorUtils.ArgbFromColor(sourceColor));
+
+ TonalPalette primaryPalette = primary == null
+ ? ColorSpecs.Get(specVersion).GetPrimaryPalette(variant, sourceColorHct, isDark, platform, contrastLevel)
+ : ColorSpecs.Get(specVersion).GetPrimaryPalette(variant, Hct.FromInt(ColorUtils.ArgbFromColor(primary.Value)), isDark, platform, contrastLevel);
+
+ TonalPalette secondaryPalette = secondary == null
+ ? ColorSpecs.Get(specVersion).GetSecondaryPalette(variant, sourceColorHct, isDark, platform, contrastLevel)
+ : ColorSpecs.Get(specVersion).GetSecondaryPalette(variant, Hct.FromInt(ColorUtils.ArgbFromColor(secondary.Value)), isDark, platform, contrastLevel);
+
+ TonalPalette tertiaryPalette = tertiary == null
+ ? ColorSpecs.Get(specVersion).GetTertiaryPalette(variant, sourceColorHct, isDark, platform, contrastLevel)
+ : ColorSpecs.Get(specVersion).GetTertiaryPalette(variant, Hct.FromInt(ColorUtils.ArgbFromColor(tertiary.Value)), isDark, platform, contrastLevel);
+
+ TonalPalette neutralPalette = neutral == null
+ ? ColorSpecs.Get(specVersion).GetNeutralPalette(variant, sourceColorHct, isDark, platform, contrastLevel)
+ : ColorSpecs.Get(specVersion).GetNeutralPalette(variant, Hct.FromInt(ColorUtils.ArgbFromColor(neutral.Value)), isDark, platform, contrastLevel);
+
+ TonalPalette neutralVariantPalette = neutralVariant == null
+ ? ColorSpecs.Get(specVersion).GetNeutralVariantPalette(variant, sourceColorHct, isDark, platform, contrastLevel)
+ : ColorSpecs.Get(specVersion).GetNeutralVariantPalette(variant, Hct.FromInt(ColorUtils.ArgbFromColor(neutralVariant.Value)), isDark, platform, contrastLevel);
+
+ TonalPalette? errorPalette = error == null
+ ? ColorSpecs.Get(specVersion).GetErrorPalette(variant, sourceColorHct, isDark, platform, contrastLevel)
+ : ColorSpecs.Get(specVersion).GetErrorPalette(variant, Hct.FromInt(ColorUtils.ArgbFromColor(error.Value)), isDark, platform, contrastLevel);
+
+ return new DynamicScheme(
+ sourceColorHct,
+ variant,
+ isDark,
+ contrastLevel,
+ platform,
+ specVersion,
+ primaryPalette,
+ secondaryPalette,
+ tertiaryPalette,
+ neutralPalette,
+ neutralVariantPalette,
+ errorPalette);
+ }
+}
diff --git a/src/MaterialDesign3.MaterialColorUtilities/Utils/ColorUtils.cs b/src/MaterialDesign3.MaterialColorUtilities/Utils/ColorUtils.cs
index 287ad54d61..db6a6ba588 100644
--- a/src/MaterialDesign3.MaterialColorUtilities/Utils/ColorUtils.cs
+++ b/src/MaterialDesign3.MaterialColorUtilities/Utils/ColorUtils.cs
@@ -26,6 +26,22 @@ public static class ColorUtils
///
public static int ArgbFromRgb(int red, int green, int blue) => (255 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | (blue & 255);
+ ///
+ /// Converts a color in ARGB format to a .
+ ///
+ public static System.Windows.Media.Color ColorFromArgb(int argb) =>
+ System.Windows.Media.Color.FromArgb(
+ (byte)AlphaFromArgb(argb),
+ (byte)RedFromArgb(argb),
+ (byte)GreenFromArgb(argb),
+ (byte)BlueFromArgb(argb));
+
+ ///
+ /// Converts a to ARGB format.
+ ///
+ public static int ArgbFromColor(System.Windows.Media.Color color) =>
+ (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B;
+
///
/// Converts a color from linear RGB components to ARGB format.
///
diff --git a/tests/MaterialColorUtilities.Tests/ColorUtilsTests.cs b/tests/MaterialColorUtilities.Tests/ColorUtilsTests.cs
index b4e07e43b3..b5bb28a3b0 100644
--- a/tests/MaterialColorUtilities.Tests/ColorUtilsTests.cs
+++ b/tests/MaterialColorUtilities.Tests/ColorUtilsTests.cs
@@ -1,4 +1,6 @@
-namespace MaterialColorUtilities.Tests;
+using System.Windows.Media;
+
+namespace MaterialColorUtilities.Tests;
public sealed class ColorUtilsTests
{
@@ -264,4 +266,41 @@ public async Task Linearize_Delinearize_RoundTrip()
await Assert.That(converted).IsEqualTo(c);
}
}
+
+ public static IEnumerable> TestColors()
+ {
+ yield return () => (unchecked((int)0xFFFF0000), Colors.Red);
+ yield return () => (unchecked((int)0xFF00FF00), Colors.Lime);
+ yield return () => (unchecked((int)0xFF0000FF), Colors.Blue);
+ yield return () => (unchecked((int)0xFFFF00FF), Colors.Magenta);
+ yield return () => (unchecked((int)0xFFFFFF00), Colors.Yellow);
+ yield return () => (unchecked((int)0xFF00FFFF), Colors.Cyan);
+ yield return () => (unchecked((int)0xFFFFFFFF), Colors.White);
+ yield return () => (unchecked((int)0xFF000000), Colors.Black);
+ yield return () => (unchecked((int)0x00FFFFFF), Colors.Transparent);
+ }
+
+ [Test]
+ [DisplayName("colorFromArgb returns known Colors")]
+ [MethodDataSource(nameof(TestColors))]
+ public async Task ColorFromArgb_KnownColors(int argb, Color color)
+ {
+ var converted = ColorUtils.ColorFromArgb(argb);
+
+ string result = $"{converted.A:x}{converted.R:x}{converted.G:x}{converted.B:X}";
+ string expected = $"{color.A:x}{color.R:x}{color.G:x}{color.B:X}";
+
+ await Assert.That(result).IsEqualTo(expected);
+ }
+
+ [Test]
+ [DisplayName("argbFromColor returns known ints")]
+ [MethodDataSource(nameof(TestColors))]
+ public async Task ArgbFromColor_KnownColors(int argb, Color color)
+ {
+ string result = ColorUtils.ArgbFromColor(color).ToString("X");
+ string expected = argb.ToString("X");
+
+ await Assert.That(result).IsEqualTo(expected);
+ }
}
diff --git a/tests/MaterialColorUtilities.Tests/DynamicSchemeTests.cs b/tests/MaterialColorUtilities.Tests/DynamicSchemeTests.cs
index abeac6397e..aa4399b291 100644
--- a/tests/MaterialColorUtilities.Tests/DynamicSchemeTests.cs
+++ b/tests/MaterialColorUtilities.Tests/DynamicSchemeTests.cs
@@ -1,4 +1,4 @@
-using System.Runtime.Serialization;
+using System.Windows.Media;
namespace MaterialColorUtilities.Tests;
@@ -65,4 +65,107 @@ public async Task RotationGreaterThan360_Wraps()
// 43 + 480 = 523 -> 163 after sanitize/wrap
await Assert.That(hue).IsEqualTo(163.0).Within(1.0);
}
+
+ ///
+ /// Shows how te crate a theme from a primary color.
+ ///
+ [Test]
+ public async Task CreateThemeFromColor()
+ {
+ var mdc = new MaterialDynamicColors();
+ var primaryColorHct = Hct.FromInt(ColorUtils.ArgbFromColor(Color.FromArgb(0xff, 0x6a, 0x9c, 0x59)));
+ var scheme = new SchemeContent(
+ primaryColorHct,
+ isDark: true,
+ contrastLevel : 0.5,
+ SpecVersion.Spec2025,
+ Platform.Phone);
+
+ scheme.ShouldSatisfyAllConditions(
+ // Main Palettes
+ () => mdc.PrimaryPaletteKeyColor.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x52, 0x83, 0x43)),
+ () => mdc.SecondaryPaletteKeyColor.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x68, 0x7D, 0x5E)),
+ () => mdc.TertiaryPaletteKeyColor.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x2B, 0x9F, 0x94)),
+ () => mdc.NeutralPaletteKeyColor.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x75, 0x78, 0x71)),
+ () => mdc.NeutralVariantPaletteKeyColor.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x72, 0x79, 0x6C)),
+ () => mdc.ErrorPaletteKeyColor.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xDE, 0x37, 0x30)),
+
+ // Surfaces [S]
+ () => mdc.Background.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x11, 0x14, 0x0F)),
+ () => mdc.OnBackground.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xE1, 0xE3, 0xDB)),
+ () => mdc.Surface.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x11, 0x14, 0x0F)),
+ () => mdc.SurfaceDim.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x11, 0x14, 0x0F)),
+ () => mdc.SurfaceBright.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x42, 0x45, 0x3F)),
+ () => mdc.SurfaceContainerLowest.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x06, 0x08, 0x05)),
+ () => mdc.SurfaceContainerLow.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x1B, 0x1E, 0x19)),
+ () => mdc.SurfaceContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x26, 0x29, 0x23)),
+ () => mdc.SurfaceContainerHigh.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x30, 0x33, 0x2E)),
+ () => mdc.SurfaceContainerHighest.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x3C, 0x3F, 0x39)),
+ () => mdc.OnSurface.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF)),
+ () => mdc.SurfaceVariant.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x42, 0x49, 0x3E)),
+ () => mdc.OnSurfaceVariant.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xD8, 0xDF, 0xCF)),
+ () => mdc.Outline.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xAD, 0xB4, 0xA6)),
+ () => mdc.OutlineVariant.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x8B, 0x93, 0x85)),
+ () => mdc.InverseSurface.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xE1, 0xE3, 0xDB)),
+ () => mdc.InverseOnSurface.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x28, 0x2B, 0x25)),
+ () => mdc.Shadow.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x00, 0x00)),
+ () => mdc.Scrim.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x00, 0x00)),
+ () => mdc.SurfaceTint.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x9F, 0xD5, 0x8B)),
+
+ // Primaries [P]
+ () => mdc.Primary.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xB5, 0xEB, 0x9F)),
+ () => mdc.PrimaryDim?.GetColor(scheme).ShouldBe(Color.FromArgb(0x00, 0x00, 0x00, 0x00)),
+ () => mdc.OnPrimary.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x04, 0x2D, 0x00)),
+ () => mdc.PrimaryContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x6B, 0x9D, 0x5A)),
+ () => mdc.OnPrimaryContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x00, 0x00)),
+ () => mdc.PrimaryFixed.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xBB, 0xF1, 0xA5)),
+ () => mdc.PrimaryFixedDim.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x9F, 0xD5, 0x8B)),
+ () => mdc.OnPrimaryFixed.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x01, 0x16, 0x00)),
+ () => mdc.OnPrimaryFixedVariant.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x10, 0x3F, 0x06)),
+ () => mdc.InversePrimary.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x24, 0x52, 0x18)),
+
+ // Secondaries [Q]
+ () => mdc.Secondary.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xCC, 0xE3, 0xBF)),
+ () => mdc.SecondaryDim?.GetColor(scheme).ShouldBe(Color.FromArgb(0x00, 0x00, 0x00, 0x00)),
+ () => mdc.OnSecondary.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x18, 0x2A, 0x12)),
+ () => mdc.SecondaryContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x81, 0x97, 0x77)),
+ () => mdc.OnSecondaryContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x00, 0x00)),
+
+ // Secondary Fixed [QF]
+ () => mdc.SecondaryFixed.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xD2, 0xE9, 0xC5)),
+ () => mdc.SecondaryFixedDim.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xB7, 0xCD, 0xAA)),
+ () => mdc.OnSecondaryFixed.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x05, 0x15, 0x02)),
+ () => mdc.OnSecondaryFixedVariant.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x28, 0x3B, 0x22)),
+
+ // Tertiaries [T]
+ () => mdc.Tertiary.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x85, 0xEE, 0xE1)),
+ () => mdc.TertiaryDim?.GetColor(scheme).ShouldBe(Color.FromArgb(0x00, 0x00, 0x00, 0x00)),
+ () => mdc.OnTertiary.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x2B, 0x27)),
+ () => mdc.TertiaryContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x2D, 0xA1, 0x96)),
+ () => mdc.OnTertiaryContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x00, 0x00)),
+
+ // Tertiary Fixed [TF]
+ () => mdc.TertiaryFixed.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x8B, 0xF5, 0xE8)),
+ () => mdc.TertiaryFixedDim.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x6E, 0xD8, 0xCC)),
+ () => mdc.OnTertiaryFixed.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x15, 0x12)),
+ () => mdc.OnTertiaryFixedVariant.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x3E, 0x38)),
+
+ // Errors [E]
+ () => mdc.Error.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xFF, 0xD2, 0xCC)),
+ () => mdc.ErrorDim?.GetColor(scheme).ShouldBe(Color.FromArgb(0x00, 0x00, 0x00, 0x00)),
+ () => mdc.OnError.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x54, 0x00, 0x03)),
+ () => mdc.ErrorContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xFF, 0x54, 0x49)),
+ () => mdc.OnErrorContainer.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x00, 0x00, 0x00)),
+
+ // Android-only
+ () => mdc.ControlActivated.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x23, 0x51, 0x17)),
+ () => mdc.ControlNormal.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0xC2, 0xC9, 0xBA)),
+ () => mdc.ControlHighlight.GetColor(scheme).ShouldBe(Color.FromArgb(0x33, 0xFF, 0xFF, 0xFF)),
+ () => mdc.TextPrimaryInverse.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x19, 0x1C, 0x17)),
+ () => mdc.TextSecondaryAndTertiaryInverse.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x42, 0x49, 0x3E)),
+ () => mdc.TextPrimaryInverseDisableOnly.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x19, 0x1C, 0x17)),
+ () => mdc.TextSecondaryAndTertiaryInverseDisabled.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x19, 0x1C, 0x17)),
+ () => mdc.TextHintInverse.GetColor(scheme).ShouldBe(Color.FromArgb(0xFF, 0x19, 0x1C, 0x17)));
+ await Task.CompletedTask;
+ }
}
diff --git a/tests/MaterialColorUtilities.Tests/MaterialColorUtilities.Tests.csproj b/tests/MaterialColorUtilities.Tests/MaterialColorUtilities.Tests.csproj
index 34faae9454..2b8d7795ea 100644
--- a/tests/MaterialColorUtilities.Tests/MaterialColorUtilities.Tests.csproj
+++ b/tests/MaterialColorUtilities.Tests/MaterialColorUtilities.Tests.csproj
@@ -1,7 +1,7 @@
- net8.0-windows
+ net8.0-windows
MaterialColorUtilities.Tests
MaterialColorUtilities.Tests
Exe
@@ -13,6 +13,7 @@
false
true
+
@@ -25,10 +26,12 @@
+
+