diff --git a/components/ColorAnalyzer/OpenSolution.bat b/components/ColorAnalyzer/OpenSolution.bat
new file mode 100644
index 000000000..814a56d4b
--- /dev/null
+++ b/components/ColorAnalyzer/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/ColorAnalyzer/samples/AccentAnalyzer.md b/components/ColorAnalyzer/samples/AccentAnalyzer.md
new file mode 100644
index 000000000..352ab7cb8
--- /dev/null
+++ b/components/ColorAnalyzer/samples/AccentAnalyzer.md
@@ -0,0 +1,19 @@
+---
+title: AccentAnalyzer Helper
+author: Avid29
+description: A tool for extracting colors from an image
+keywords: Accents, Color, Helpers
+dev_langs:
+ - csharp
+category: Helpers
+subcategory: Miscellaneous
+discussion-id: 254
+issue-id: 0
+icon: assets/icon.png
+---
+
+# AccentAnalyzer
+
+The AccentAnalyzer provides a pure XAML way to use the colors extracted from an image as a binding source for any `Color` property.
+
+> [!Sample AccentAnalyzerSample]
diff --git a/components/ColorAnalyzer/samples/AccentAnalyzerSample.xaml b/components/ColorAnalyzer/samples/AccentAnalyzerSample.xaml
new file mode 100644
index 000000000..3bcdb91fa
--- /dev/null
+++ b/components/ColorAnalyzer/samples/AccentAnalyzerSample.xaml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/ColorAnalyzer/samples/AccentAnalyzerSample.xaml.cs b/components/ColorAnalyzer/samples/AccentAnalyzerSample.xaml.cs
new file mode 100644
index 000000000..fca29f2c6
--- /dev/null
+++ b/components/ColorAnalyzer/samples/AccentAnalyzerSample.xaml.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace ColorAnalyzerExperiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSample(id: nameof(AccentAnalyzerSample), "AccentAnalyzer helper", description: $"A sample for showing how the accent analyzer can be used.")]
+public sealed partial class AccentAnalyzerSample : Page
+{
+ public AccentAnalyzerSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/ColorAnalyzer/samples/Assets/StockImages/Flowers.jpg b/components/ColorAnalyzer/samples/Assets/StockImages/Flowers.jpg
new file mode 100644
index 000000000..1ff3e4f34
Binary files /dev/null and b/components/ColorAnalyzer/samples/Assets/StockImages/Flowers.jpg differ
diff --git a/components/ColorAnalyzer/samples/Assets/StockImages/Headphones.jpg b/components/ColorAnalyzer/samples/Assets/StockImages/Headphones.jpg
new file mode 100644
index 000000000..ee6fc7e91
Binary files /dev/null and b/components/ColorAnalyzer/samples/Assets/StockImages/Headphones.jpg differ
diff --git a/components/ColorAnalyzer/samples/Assets/StockImages/Paint.jpg b/components/ColorAnalyzer/samples/Assets/StockImages/Paint.jpg
new file mode 100644
index 000000000..66cb4173f
Binary files /dev/null and b/components/ColorAnalyzer/samples/Assets/StockImages/Paint.jpg differ
diff --git a/components/ColorAnalyzer/samples/Assets/icon.png b/components/ColorAnalyzer/samples/Assets/icon.png
new file mode 100644
index 000000000..8435bcaa9
Binary files /dev/null and b/components/ColorAnalyzer/samples/Assets/icon.png differ
diff --git a/components/ColorAnalyzer/samples/ColorAnalyzer.Samples.csproj b/components/ColorAnalyzer/samples/ColorAnalyzer.Samples.csproj
new file mode 100644
index 000000000..57dfffe41
--- /dev/null
+++ b/components/ColorAnalyzer/samples/ColorAnalyzer.Samples.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+ ColorAnalyzer
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
diff --git a/components/ColorAnalyzer/samples/Dependencies.props b/components/ColorAnalyzer/samples/Dependencies.props
new file mode 100644
index 000000000..9c82a5c18
--- /dev/null
+++ b/components/ColorAnalyzer/samples/Dependencies.props
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/ColorAnalyzer/samples/ImageOptionsPane.xaml b/components/ColorAnalyzer/samples/ImageOptionsPane.xaml
new file mode 100644
index 000000000..44227d2c9
--- /dev/null
+++ b/components/ColorAnalyzer/samples/ImageOptionsPane.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/ColorAnalyzer/samples/ImageOptionsPane.xaml.cs b/components/ColorAnalyzer/samples/ImageOptionsPane.xaml.cs
new file mode 100644
index 000000000..f54f1eedc
--- /dev/null
+++ b/components/ColorAnalyzer/samples/ImageOptionsPane.xaml.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if !WINDOWS_UWP
+using Microsoft.UI.Xaml.Media.Imaging;
+#elif WINDOWS_UWP
+using Windows.UI.Xaml.Media.Imaging;
+#endif
+
+namespace ColorAnalyzerExperiment.Samples;
+
+[ToolkitSampleOptionsPane(nameof(AccentAnalyzerSample))]
+public partial class ImageOptionsPane : UserControl
+{
+ private AccentAnalyzerSample.XamlNamedPropertyRelay _sample;
+
+ public ImageOptionsPane(AccentAnalyzerSample sample)
+ {
+ this.InitializeComponent();
+
+ _sample = new AccentAnalyzerSample.XamlNamedPropertyRelay(sample);
+
+ string[] images = ["Flowers.jpg", "Headphones.jpg", "Paint.jpg"];
+ StockImages = images.Select(x => $"ms-appx:///Assets/StockImages/{x}").ToList();
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ SetImage(new Uri(UrlTextbox.Text));
+ }
+
+ public IList StockImages { get; }
+
+ private void GridView_ItemClick(object sender, ItemClickEventArgs e)
+ {
+ SetImage(new Uri((string)e.ClickedItem));
+ }
+
+ private void SetImage(Uri uri)
+ {
+ _sample.AccentedImage.Source = new BitmapImage(uri);
+ }
+}
diff --git a/components/ColorAnalyzer/src/AccentAnalyzer.Clustering.cs b/components/ColorAnalyzer/src/AccentAnalyzer.Clustering.cs
new file mode 100644
index 000000000..e0c4cb370
--- /dev/null
+++ b/components/ColorAnalyzer/src/AccentAnalyzer.Clustering.cs
@@ -0,0 +1,182 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+
+namespace CommunityToolkit.WinUI.Helpers;
+
+public partial class AccentAnalyzer
+{
+ private static Vector3[] KMeansCluster(Span points, int k, out int[] counts)
+ {
+ // Track the assigned cluster of each point
+ int[] clusterIds = new int[points.Length];
+
+ // Track the centroids of each cluster and its member count
+ Span centroids = stackalloc Vector3[k];
+ counts = new int[k];
+
+ // Split the points into arbitrary clusters
+ Split(k, clusterIds);
+
+ bool converged = false;
+ while (!converged)
+ {
+ // Assume we've converged. If we haven't, we'll assign converged
+ // to false when adjust the clusters
+ converged = true;
+
+ // Calculate/Recalculate centroids
+ CalculateCentroidsAndPrune(ref centroids, ref counts, points, clusterIds);
+
+ // Move each point's clusterId to the nearest cluster centroid
+ for (int i = 0; i < points.Length; i++)
+ {
+ var nearestIndex = FindNearestClusterIndex(points[i], centroids);
+
+ // The nearest cluster hasn't changed. Do nothing
+ if (clusterIds[i] == nearestIndex)
+ continue;
+
+ // Update the cluster id and note that we have not converged
+ clusterIds[i] = nearestIndex;
+ converged = false;
+ }
+ }
+
+ return centroids.ToArray();
+ }
+
+ ///
+ /// Assigns arbitrary clusterIds for each point
+ ///
+ private static void Split(int k, int[] clusterIds)
+ {
+ // Mathematically true random sampling
+#if NET6_0_OR_GREATER
+ var offset = Random.Shared.Next(k);
+#else
+ var rand = new Random();
+ var offset = rand.Next(k);
+#endif
+
+ // Assign each clusters id
+ for (int i = 0; i < clusterIds.Length; i++)
+ clusterIds[i] = (i + offset) % k;
+ }
+
+ ///
+ /// Calculates the centroid of each cluster, and prunes empty clusters.
+ ///
+ private static void CalculateCentroidsAndPrune(ref Span centroids, ref int[] counts, Span points, int[] clusterIds)
+ {
+ // Clear centroids and counts before recalculation
+ for (int i = 0; i < centroids.Length; i++)
+ {
+ centroids[i] = Vector3.Zero;
+ counts[i] = 0;
+ }
+
+ // Accumulate step in centroid calculation
+ for (int i = 0; i < clusterIds.Length; i++)
+ {
+ int id = clusterIds[i];
+ centroids[id] += points[i];
+ counts[id]++;
+ }
+
+ // Prune empty clusters
+ // All empty clusters are swapped to the end of the span
+ // then a slice is taken with only the remaining populated clusters
+ int pivot = counts.Length;
+ for (int i = 0; i < pivot;)
+ {
+ // Increment and continue if populated
+ if (counts[i] != 0)
+ {
+ i++;
+ continue;
+ }
+
+ // The item is not populated. Swap to end and move pivot
+ // NOTE: This is a one-way "swap". We're discarding the 0s anyways.
+ pivot--;
+ centroids[i] = centroids[pivot];
+ counts[i] = counts[pivot];
+ }
+
+ // Perform slice
+#if !WINDOWS_UWP
+ counts = counts[..pivot];
+ centroids = centroids[..pivot];
+#elif WINDOWS_UWP
+ Array.Resize(ref counts, pivot);
+ centroids = centroids.Slice(0, pivot);
+#endif
+
+ // Division step in centroid calculation
+ for (int i = 0; i < centroids.Length; i++)
+ centroids[i] /= counts[i];
+ }
+
+ ///
+ /// Finds the index of the centroid nearest the point
+ ///
+ private static int FindNearestClusterIndex(Vector3 point, Span centroids)
+ {
+ // Track the nearest centroid's distance and the index of that centroid
+ float nearestDistance = float.PositiveInfinity;
+ int nearestIndex = -1;
+
+ for (int j = 0; j < centroids.Length; j++)
+ {
+ // Compare the point to the jth centroid
+ float distance = Vector3.DistanceSquared(point, centroids[j]);
+
+ // Skip the cluster if further than the nearest seen cluster
+ if (nearestDistance < distance)
+ continue;
+
+ // This is the nearest cluster
+ // Update the distance and index
+ nearestDistance = distance;
+ nearestIndex = j;
+ }
+
+ return nearestIndex;
+ }
+
+ internal static float FindColorfulness(Vector3 color)
+ {
+ var rg = color.X - color.Y;
+ var yb = ((color.X + color.Y) / 2) - color.Z;
+ return 0.3f * new Vector2(rg, yb).Length();
+ }
+
+ internal static float FindColorfulness(Vector3[] colors)
+ {
+ // Isolate rg and yb
+ var rg = colors.Select(x => Math.Abs(x.X - x.Y));
+ var yb = colors.Select(x => Math.Abs(0.5f * (x.X + x.Y) - x.Z));
+
+ // Evaluate rg and yb mean and std
+ var rg_std = FindStandardDeviation(rg, out var rg_mean);
+ var yb_std = FindStandardDeviation(yb, out var yb_mean);
+
+ // Combine means and standard deviations
+ var std = new Vector2(rg_mean, yb_mean).Length();
+ var mean = new Vector2(rg_std, yb_std).Length();
+
+ // Return colorfulness
+ return std + (0.3f * mean);
+ }
+
+ private static float FindStandardDeviation(IEnumerable data, out float avg)
+ {
+ var average = data.Average();
+ avg = average;
+ var sumOfSquares = data.Select(x => (x - average) * (x - average)).Sum();
+ return (float)Math.Sqrt(sumOfSquares / data.Count());
+ }
+}
diff --git a/components/ColorAnalyzer/src/AccentAnalyzer.Properties.cs b/components/ColorAnalyzer/src/AccentAnalyzer.Properties.cs
new file mode 100644
index 000000000..1e48b16f9
--- /dev/null
+++ b/components/ColorAnalyzer/src/AccentAnalyzer.Properties.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows.Input;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Helpers;
+
+public partial class AccentAnalyzer
+{
+ ///
+ /// Gets the for the property.
+ ///
+ public static readonly DependencyProperty SourceProperty =
+ DependencyProperty.Register(nameof(Source), typeof(UIElement), typeof(AccentAnalyzer), new PropertyMetadata(null, OnSourceChanged));
+
+ ///
+ /// Gets the for the property.
+ ///
+ public static readonly DependencyProperty PrimaryAccentColorProperty =
+ DependencyProperty.Register(nameof(PrimaryAccentColor), typeof(Color), typeof(AccentAnalyzer), new PropertyMetadata(Colors.Transparent));
+
+ ///
+ /// Gets the for the property.
+ ///
+ public static readonly DependencyProperty SecondaryAccentColorProperty =
+ DependencyProperty.Register(nameof(SecondaryAccentColor), typeof(Color), typeof(AccentAnalyzer), new PropertyMetadata(Colors.Transparent));
+
+ ///
+ /// Gets the for the property.
+ ///
+ public static readonly DependencyProperty TertiaryAccentColorProperty =
+ DependencyProperty.Register(nameof(TertiaryAccentColor), typeof(Color), typeof(AccentAnalyzer), new PropertyMetadata(Colors.Transparent));
+
+ ///
+ /// Gets the for the property.
+ ///
+ public static readonly DependencyProperty BaseColorProperty =
+ DependencyProperty.Register(nameof(BaseColor), typeof(Color), typeof(AccentAnalyzer), new PropertyMetadata(Colors.Transparent));
+
+ ///
+ /// Gets the for the property.
+ ///
+ public static readonly DependencyProperty DominantColorProperty =
+ DependencyProperty.Register(nameof(DominantColor), typeof(Color), typeof(AccentAnalyzer), new PropertyMetadata(Colors.Transparent));
+
+ ///
+ /// Gets the for the property.
+ ///
+ public static readonly DependencyProperty ColorfulnessProperty =
+ DependencyProperty.Register(nameof(Colorfulness), typeof(float), typeof(AccentAnalyzer), new PropertyMetadata(0f));
+
+ ///
+ /// An event fired when the accent properties are updated.
+ ///
+ public event EventHandler? AccentsUpdated;
+
+ ///
+ /// Gets or sets the source for accent color analysis.
+ ///
+ public UIElement? Source
+ {
+ get => (UIElement)GetValue(SourceProperty);
+ set => SetValue(SourceProperty, value);
+ }
+
+ ///
+ /// Gets the primary accent color as extracted from the .
+ ///
+ ///
+ /// The most "colorful" found in the image.
+ ///
+ public Color PrimaryAccentColor
+ {
+ get => (Color)GetValue(PrimaryAccentColorProperty);
+ protected set => SetValue(PrimaryAccentColorProperty, value);
+ }
+
+ ///
+ /// Gets the secondary accent color as extracted from the .
+ ///
+ ///
+ /// The second most "colorful" color found in the image.
+ ///
+ public Color SecondaryAccentColor
+ {
+ get => (Color)GetValue(SecondaryAccentColorProperty);
+ protected set => SetValue(SecondaryAccentColorProperty, value);
+ }
+
+ ///
+ /// Gets the tertiary accent color as extracted from the .
+ ///
+ ///
+ /// The third most "colorful" color found in the image.
+ ///
+ public Color TertiaryAccentColor
+ {
+ get => (Color)GetValue(TertiaryAccentColorProperty);
+ protected set => SetValue(TertiaryAccentColorProperty, value);
+ }
+
+ ///
+ /// Gets the base color as extracted from the .
+ ///
+ ///
+ /// The least "colorful" color found in the image.
+ ///
+ public Color BaseColor
+ {
+ get => (Color)GetValue(BaseColorProperty);
+ protected set => SetValue(BaseColorProperty, value);
+ }
+
+ ///
+ /// Gets the dominant color as extracted from the .
+ ///
+ ///
+ /// The color that takes up the most of the image.
+ ///
+ public Color DominantColor
+ {
+ get => (Color)GetValue(DominantColorProperty);
+ protected set => SetValue(DominantColorProperty, value);
+ }
+
+ ///
+ /// Gets the "colorfulness" of the .
+ ///
+ ///
+ /// Colorfulness is defined by David Hasler and Sabine Susstrunk's paper on measuring colorfulness
+ /// .
+ ///
+ /// An image with colors of high saturation and value will have a high colorfulness (around 1),
+ /// meanwhile images that are mostly gray or white will have a low colorfulness (around 0).
+ ///
+ public float Colorfulness
+ {
+ get => (float)GetValue(ColorfulnessProperty);
+ private set => SetValue(ColorfulnessProperty, value);
+ }
+
+ ///
+ /// Gets the set of extracted on last update.
+ ///
+ public IReadOnlyList? AccentColors { get; private set; }
+
+ private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is not AccentAnalyzer analyzer)
+ return;
+
+ _ = analyzer.UpdateAccentAsync();
+ }
+}
diff --git a/components/ColorAnalyzer/src/AccentAnalyzer.cs b/components/ColorAnalyzer/src/AccentAnalyzer.cs
new file mode 100644
index 000000000..a58cf90b0
--- /dev/null
+++ b/components/ColorAnalyzer/src/AccentAnalyzer.cs
@@ -0,0 +1,192 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if !WINAPPSDK
+global using Windows.UI.Xaml.Media.Imaging;
+global using Windows.System;
+#else
+global using Microsoft.UI;
+global using Microsoft.UI.Dispatching;
+global using Microsoft.UI.Xaml.Media.Imaging;
+#endif
+
+using System.Numerics;
+using System.Windows.Input;
+
+namespace CommunityToolkit.WinUI.Helpers;
+
+///
+/// A resource that can be used to extract color palettes out of any UIElement.
+///
+public partial class AccentAnalyzer : DependencyObject
+{
+ ///
+ /// Initialize an instance of the class.
+ ///
+ public AccentAnalyzer()
+ {
+ }
+
+ ///
+ /// Update the accent
+ ///
+ public void UpdateAccent()
+ {
+ _ = UpdateAccentAsync();
+ }
+
+ private async Task UpdateAccentAsync()
+ {
+ const int sampleCount = 4096;
+ const int k = 8;
+
+ // Retreive pixel samples from source
+ var samples = await SampleSourcePixelColorsAsync(sampleCount);
+
+ // Failed to retreive pixel data. Cancel
+ if (samples.Length == 0)
+ return;
+
+ // Cluster samples in RGB floating-point color space
+ // With Euclidean Squared distance function
+ // The accumulate accent color infos
+ var clusters = KMeansCluster(samples, k, out var sizes);
+ var colorData = clusters
+ .Select((color, i) => new AccentColorInfo(color, (float)sizes[i] / samples.Length));
+
+ // Evaluate colorfulness
+ // TODO: Should this be weighted by cluster sizes?
+ var overallColorfulness = FindColorfulness(clusters);
+
+ // Select accent colors
+ SelectAccentColors(colorData, overallColorfulness);
+
+ // Update accent colors property
+ // Not a dependency property, so no need to update from the UI Thread
+#if !WINDOWS_UWP
+ AccentColors = [..colorData];
+#else
+ AccentColors = colorData.ToList();
+#endif
+
+ // Update the colorfulness and invoke accents updated event,
+ // both from the UI thread
+ DispatcherQueue.GetForCurrentThread().TryEnqueue(() =>
+ {
+ Colorfulness = overallColorfulness;
+ AccentsUpdated?.Invoke(this, EventArgs.Empty);
+ });
+ }
+
+ ///
+ /// This method takes the processed color information and selects the accent colors from it.
+ ///
+ ///
+ /// There is no guarentee that this method will be called from the UI Thread.
+ /// Dependency properties should be updated using a dispatcher.
+ ///
+ /// The analyzed accent color info from the image.
+ /// The overall colorfulness of the image.
+ protected virtual void SelectAccentColors(IEnumerable colorData, float imageColorfulness)
+ {
+ // Select accent colors
+ var accentColors = colorData
+ .OrderByDescending(x => x.Colorfulness)
+ .Select(x => x.Color);
+
+ // Get primary/secondary/tertiary accents
+ var primary = accentColors.First();
+ var secondary = accentColors.ElementAtOrDefault(1);
+ secondary = secondary != default ? secondary : primary;
+ var tertiary = accentColors.ElementAtOrDefault(2);
+ tertiary = tertiary != default ? tertiary : secondary;
+
+ // Get base color
+ var baseColor = accentColors.Last();
+
+ // Get dominant color by prominence
+#if NET6_0_OR_GREATER
+ var dominantColor = colorData
+ .MaxBy(x => x.Prominence).Color;
+#else
+ var dominantColor = colorData
+ .OrderByDescending((x) => x.Prominence)
+ .First().Color;
+#endif
+
+ // Batch update the dependency properties in the UI Thread
+ DispatcherQueue.GetForCurrentThread().TryEnqueue(() =>
+ {
+ PrimaryAccentColor = primary;
+ SecondaryAccentColor = secondary;
+ TertiaryAccentColor = tertiary;
+ BaseColor = baseColor;
+ DominantColor = dominantColor;
+ });
+ }
+
+ private async Task SampleSourcePixelColorsAsync(int sampleCount)
+ {
+ // Ensure the source is populated
+ if (Source is null)
+ return [];
+
+ // Grab actual size
+ // If actualSize is 0, replace with 1:1 aspect ratio
+ var sourceSize = Source.ActualSize;
+ sourceSize = sourceSize != Vector2.Zero ? sourceSize : Vector2.One;
+
+ // Calculate size of scaled rerender using the actual size
+ // scaled down to the sample count, maintaining aspect ration
+ var sourceArea = sourceSize.X * sourceSize.Y;
+ var sampleScale = MathF.Sqrt(sampleCount / sourceArea);
+ var sampleSize = sourceSize * sampleScale;
+
+ // Rerender the UIElement to a bitmap of about sampleCount pixels
+ // Note: RenderTargetBitmap is not supported with Uno Platform.
+ var bitmap = new RenderTargetBitmap();
+ await bitmap.RenderAsync(Source, (int)sampleSize.X, (int)sampleSize.Y);
+
+ // Create a stream from the bitmap
+ var pixels = await bitmap.GetPixelsAsync();
+ var pixelByteStream = pixels.AsStream();
+
+ // Something went wrong
+ if (pixelByteStream.Length == 0)
+ return [];
+
+ // Read the stream into a a color array
+ const int bytesPerPixel = 4;
+ var samples = new Vector3[(int)pixelByteStream.Length / bytesPerPixel];
+
+ // Iterate through the stream reading a pixel (4 bytes) at a time
+ // and storing them as a Vector3. Opacity info is dropped.
+ int colorIndex = 0;
+#if NET7_0_OR_GREATER
+ Span pixelBytes = stackalloc byte[bytesPerPixel];
+ while (pixelByteStream.Read(pixelBytes) == bytesPerPixel)
+#else
+ byte[] pixelBytes = new byte[bytesPerPixel];
+ while(pixelByteStream.Read(pixelBytes, 0, bytesPerPixel) == bytesPerPixel)
+#endif
+ {
+ // Skip fully transparent pixels
+ if (pixelBytes[3] == 0)
+ continue;
+
+ // Take the red, green, and blue channels to make a floating-point space color.
+ samples[colorIndex] = new Vector3(pixelBytes[2], pixelBytes[1], pixelBytes[0]) / byte.MaxValue;
+ colorIndex++;
+ }
+
+ // If we skipped any pixels, trim the span
+#if !WINDOWS_UWP
+ samples = samples[..colorIndex];
+#else
+ Array.Resize(ref samples, colorIndex);
+#endif
+
+ return samples;
+ }
+}
diff --git a/components/ColorAnalyzer/src/AccentColorInfo.cs b/components/ColorAnalyzer/src/AccentColorInfo.cs
new file mode 100644
index 000000000..a67f0ed8e
--- /dev/null
+++ b/components/ColorAnalyzer/src/AccentColorInfo.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Helpers;
+
+///
+/// A struct containing accent color info.
+///
+public readonly struct AccentColorInfo
+{
+ internal AccentColorInfo(Vector3 rgb, float prominence)
+ {
+ Colorfulness = AccentAnalyzer.FindColorfulness(rgb);
+
+ rgb *= byte.MaxValue;
+ Color = Color.FromArgb(byte.MaxValue, (byte)rgb.X, (byte)rgb.Y, (byte)rgb.Z);
+ Prominence = prominence;
+ }
+
+ ///
+ /// Gets the of the accent color.
+ ///
+ public Color Color { get; }
+
+ ///
+ /// Gets the colorfulness index of the accent color.
+ ///
+ ///
+ /// The exact definition of colorfulness is defined by David Hasler and Sabine Susstrunk's paper on measuring colorfulness
+ /// .
+ ///
+ /// Colors of high saturation and value will have a high colorfulness (around 1),
+ /// while colors that are mostly gray or white will have a low colorfulness (around 0).
+ ///
+ public float Colorfulness { get; }
+
+ ///
+ /// Gets the prominence of the accent color in the sampled image.
+ ///
+ public float Prominence { get; }
+}
diff --git a/components/ColorAnalyzer/src/CommunityToolkit.WinUI.ColorAnalyzer.csproj b/components/ColorAnalyzer/src/CommunityToolkit.WinUI.ColorAnalyzer.csproj
new file mode 100644
index 000000000..a80a1218c
--- /dev/null
+++ b/components/ColorAnalyzer/src/CommunityToolkit.WinUI.ColorAnalyzer.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+ ColorAnalyzer
+ This package contains the ColorAnalyzer.
+
+ CommunityToolkit.WinUI.ColorAnalyzerRns
+
+
+
+
+
diff --git a/components/ColorAnalyzer/src/Dependencies.props b/components/ColorAnalyzer/src/Dependencies.props
new file mode 100644
index 000000000..6bff73882
--- /dev/null
+++ b/components/ColorAnalyzer/src/Dependencies.props
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/ColorAnalyzer/src/MultiTarget.props b/components/ColorAnalyzer/src/MultiTarget.props
new file mode 100644
index 000000000..fff19e9d3
--- /dev/null
+++ b/components/ColorAnalyzer/src/MultiTarget.props
@@ -0,0 +1,10 @@
+
+
+
+
+ uwp;wasdk;
+
+
diff --git a/components/ColorAnalyzer/tests/ColorAnalyzer.Tests.projitems b/components/ColorAnalyzer/tests/ColorAnalyzer.Tests.projitems
new file mode 100644
index 000000000..412607c92
--- /dev/null
+++ b/components/ColorAnalyzer/tests/ColorAnalyzer.Tests.projitems
@@ -0,0 +1,14 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ FA8C1D4D-4CEF-490E-A369-0CCCFBAA8909
+
+
+ ColorAnalyzerTests
+
+
+
+
+
\ No newline at end of file
diff --git a/components/ColorAnalyzer/tests/ColorAnalyzer.Tests.shproj b/components/ColorAnalyzer/tests/ColorAnalyzer.Tests.shproj
new file mode 100644
index 000000000..bc9df735c
--- /dev/null
+++ b/components/ColorAnalyzer/tests/ColorAnalyzer.Tests.shproj
@@ -0,0 +1,13 @@
+
+
+
+ FA8C1D4D-4CEF-490E-A369-0CCCFBAA8909
+ 14.0
+
+
+
+
+
+
+
+
diff --git a/components/ColorAnalyzer/tests/ExampleAccentAnalyzerTestClass.cs b/components/ColorAnalyzer/tests/ExampleAccentAnalyzerTestClass.cs
new file mode 100644
index 000000000..9757153ca
--- /dev/null
+++ b/components/ColorAnalyzer/tests/ExampleAccentAnalyzerTestClass.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Tests;
+using CommunityToolkit.WinUI.Helpers;
+
+namespace AccentAnalyzerTests;
+
+[TestClass]
+public partial class ExampleAccentAnalyzerTestClass : VisualUITestBase
+{
+ // If you don't need access to UI objects directly or async code, use this pattern.
+ [TestMethod]
+ public void SimpleSynchronousExampleTest()
+ {
+ var assembly = typeof(AccentAnalyzer).Assembly;
+ var type = assembly.GetType(typeof(AccentAnalyzer).FullName ?? string.Empty);
+
+ Assert.IsNotNull(type, "Could not find AccentAnalyzer type.");
+ Assert.AreEqual(typeof(AccentAnalyzer), type, "Type of AccentAnalyzer does not match expected type.");
+ }
+}