Skip to content

Commit c72fa2b

Browse files
authored
Merge pull request #767 from Avid29/color-sources
Add ColorSource abstraction for the origin of a ColorAnalyzer pixel stream
2 parents 430392c + 3e0e427 commit c72fa2b

File tree

14 files changed

+266
-45
lines changed

14 files changed

+266
-45
lines changed

components/ColorAnalyzer/samples/ColorPaletteSampler/AccentColorSample.xaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- 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. -->
1+
<!-- 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. -->
22
<local:ColorPaletteSamplerToolkitSampleBase x:Class="ColorAnalyzerExperiment.Samples.AccentColorSample"
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -11,8 +11,10 @@
1111
mc:Ignorable="d">
1212

1313
<local:ColorPaletteSamplerToolkitSampleBase.Resources>
14-
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler"
15-
Source="{x:Bind SampledImage}">
14+
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler">
15+
<helpers:ColorPaletteSampler.Source>
16+
<helpers:UIColorSource Source="{x:Bind SampledImage}" />
17+
</helpers:ColorPaletteSampler.Source>
1618
<helpers:AccentColorPaletteSelector x:Name="AccentPalette"
1719
MinColorCount="3" />
1820
</helpers:ColorPaletteSampler>

components/ColorAnalyzer/samples/ColorPaletteSampler/BaseColorSample.xaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- 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. -->
1+
<!-- 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. -->
22
<local:ColorPaletteSamplerToolkitSampleBase x:Class="ColorAnalyzerExperiment.Samples.BaseColorSample"
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -11,8 +11,10 @@
1111
mc:Ignorable="d">
1212

1313
<local:ColorPaletteSamplerToolkitSampleBase.Resources>
14-
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler"
15-
Source="{x:Bind SampledImage}">
14+
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler">
15+
<helpers:ColorPaletteSampler.Source>
16+
<helpers:UIColorSource Source="{x:Bind SampledImage}" />
17+
</helpers:ColorPaletteSampler.Source>
1618
<helpers:BaseColorPaletteSelector x:Name="BaseColorPalette"
1719
MinColorCount="3" />
1820
</helpers:ColorPaletteSampler>

components/ColorAnalyzer/samples/ColorPaletteSampler/ColorPaletteSamplerToolkitSample.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,19 @@ public abstract partial class ColorPaletteSamplerToolkitSampleBase : Page
99
public static readonly DependencyProperty SelectedImageProperty =
1010
DependencyProperty.Register(nameof(SelectedImage), typeof(ImageSource), typeof(ColorPaletteSamplerToolkitSampleBase), new PropertyMetadata(null));
1111

12+
public static readonly DependencyProperty SelectedImageUrlProperty =
13+
DependencyProperty.Register(nameof(SelectedImageUrl), typeof(string), typeof(ColorPaletteSamplerToolkitSampleBase), new PropertyMetadata(null));
14+
1215
public ColorPaletteSamplerToolkitSampleBase()
1316
{
1417
}
1518

19+
public string? SelectedImageUrl
20+
{
21+
get => (string?)GetValue(SelectedImageUrlProperty);
22+
set => SetValue(SelectedImageUrlProperty, value);
23+
}
24+
1625
public ImageSource SelectedImage
1726
{
1827
get => (ImageSource)GetValue(SelectedImageProperty);

components/ColorAnalyzer/samples/ColorPaletteSampler/ColorWeightSample.xaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- 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. -->
1+
<!-- 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. -->
22
<local:ColorPaletteSamplerToolkitSampleBase x:Class="ColorAnalyzerExperiment.Samples.ColorWeightSample"
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -11,8 +11,10 @@
1111
mc:Ignorable="d">
1212

1313
<local:ColorPaletteSamplerToolkitSampleBase.Resources>
14-
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler"
15-
Source="{x:Bind SampledImage}">
14+
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler">
15+
<helpers:ColorPaletteSampler.Source>
16+
<helpers:UIColorSource Source="{x:Bind SampledImage}" />
17+
</helpers:ColorPaletteSampler.Source>
1618
<helpers:ColorWeightPaletteSelector x:Name="ColorWeightPalette"
1719
MinColorCount="5" />
1820
</helpers:ColorPaletteSampler>

components/ColorAnalyzer/samples/ColorPaletteSampler/ImageOptionsPane.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ private void GridView_ItemClick(object sender, ItemClickEventArgs e)
4343

4444
private void SetImage(Uri uri)
4545
{
46+
_sample.SelectedImageUrl = uri.AbsoluteUri;
4647
_sample.SelectedImage = new BitmapImage(uri);
4748
}
4849
}

components/ColorAnalyzer/samples/ColorPaletteSampler/MultiplePaletteSelectorSample.xaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- 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. -->
1+
<!-- 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. -->
22
<local:ColorPaletteSamplerToolkitSampleBase x:Class="ColorAnalyzerExperiment.Samples.MultiplePaletteSelectorSample"
33
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
44
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -11,8 +11,10 @@
1111
mc:Ignorable="d">
1212

1313
<local:ColorPaletteSamplerToolkitSampleBase.Resources>
14-
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler"
15-
Source="{x:Bind SampledImage}">
14+
<helpers:ColorPaletteSampler x:Name="ColorPaletteSampler">
15+
<helpers:ColorPaletteSampler.Source>
16+
<helpers:UIColorSource Source="{x:Bind SampledImage}" />
17+
</helpers:ColorPaletteSampler.Source>
1618
<helpers:AccentColorPaletteSelector x:Name="AccentPalette"
1719
MinColorCount="1" />
1820
<helpers:BaseColorPaletteSelector x:Name="BaseColorPalette"

components/ColorAnalyzer/src/ColorPaletteSampler/ColorPaletteSampler.Properties.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ public partial class ColorPaletteSampler
1010
/// Gets the <see cref="DependencyProperty"/> for the <see cref="Source"/> property.
1111
/// </summary>
1212
public static readonly DependencyProperty SourceProperty =
13-
DependencyProperty.Register(nameof(Source), typeof(UIElement), typeof(ColorPaletteSampler), new PropertyMetadata(null, OnSourceChanged));
13+
DependencyProperty.Register(nameof(Source), typeof(ColorSource), typeof(ColorPaletteSampler), new PropertyMetadata(null, OnSourceChanged));
1414

1515
/// <summary>
1616
/// An event fired when the <see cref="Palette"/> and <see cref="PaletteSelectors"/> are updated.
1717
/// </summary>
1818
public event EventHandler? PaletteUpdated;
1919

2020
/// <summary>
21-
/// Gets or sets the <see cref="UIElement"/> source sampled for a color palette.
21+
/// Gets or sets the <see cref="ColorSource"/> for the color palette.
2222
/// </summary>
23-
public UIElement? Source
23+
public ColorSource? Source
2424
{
25-
get => (UIElement)GetValue(SourceProperty);
25+
get => (ColorSource)GetValue(SourceProperty);
2626
set => SetValue(SourceProperty, value);
2727
}
2828

@@ -45,6 +45,21 @@ private static void OnSourceChanged(DependencyObject d, DependencyPropertyChange
4545
if (d is not ColorPaletteSampler analyzer)
4646
return;
4747

48+
if (e.OldValue is ColorSource oldSource)
49+
{
50+
oldSource.SourceUpdated -= analyzer.OnSourceUpdated;
51+
}
52+
53+
if (e.NewValue is ColorSource newSource)
54+
{
55+
newSource.SourceUpdated += analyzer.OnSourceUpdated;
56+
}
57+
4858
_ = analyzer.UpdatePaletteAsync();
4959
}
60+
61+
private void OnSourceUpdated(object? sender, EventArgs e)
62+
{
63+
_ = UpdatePaletteAsync();
64+
}
5065
}

components/ColorAnalyzer/src/ColorPaletteSampler/ColorPaletteSampler.cs

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
#endif
1313

1414
using System.Numerics;
15-
using System.Windows.Input;
16-
using Windows.UI;
1715

1816
namespace CommunityToolkit.WinUI.Helpers;
1917

@@ -95,41 +93,24 @@ public async Task UpdatePaletteAsync()
9593

9694
private async Task<Vector3[]> SampleSourcePixelColorsAsync(int sampleCount)
9795
{
98-
// Ensure the source is populated
9996
if (Source is null)
10097
return [];
10198

102-
// Grab actual size
103-
// If actualSize is 0, replace with 1:1 aspect ratio
104-
var sourceSize = Source.ActualSize;
105-
sourceSize = sourceSize != Vector2.Zero ? sourceSize : Vector2.One;
106-
107-
// Calculate size of scaled rerender using the actual size
108-
// scaled down to the sample count, maintaining aspect ration
109-
var sourceArea = sourceSize.X * sourceSize.Y;
110-
var sampleScale = MathF.Sqrt(sampleCount / sourceArea);
111-
var sampleSize = sourceSize * sampleScale;
112-
113-
// Rerender the UIElement to a bitmap of about sampleCount pixels
114-
// Note: RenderTargetBitmap is not supported with Uno Platform.
115-
var bitmap = new RenderTargetBitmap();
116-
await bitmap.RenderAsync(Source, (int)sampleSize.X, (int)sampleSize.Y);
117-
118-
// Create a stream from the bitmap
119-
var pixels = await bitmap.GetPixelsAsync();
120-
var pixelByteStream = pixels.AsStream();
99+
var pixelByteStream = await Source.GetPixelDataAsync(sampleCount);
121100

122101
// Something went wrong
123-
if (pixelByteStream.Length == 0)
102+
if (pixelByteStream is null || pixelByteStream.Length == 0)
124103
return [];
125104

126105
// Read the stream into a a color array
127106
const int bytesPerPixel = 4;
128-
var samples = new Vector3[(int)pixelByteStream.Length / bytesPerPixel];
107+
var samples = new Vector3[sampleCount];
129108

130109
// Iterate through the stream reading a pixel (4 bytes) at a time
131110
// and storing them as a Vector3. Opacity info is dropped.
132111
int colorIndex = 0;
112+
var step = (pixelByteStream.Length / sampleCount);
113+
step -= step % 4;
133114
#if NET7_0_OR_GREATER
134115
Span<byte> pixelBytes = stackalloc byte[bytesPerPixel];
135116
while (pixelByteStream.Read(pixelBytes) == bytesPerPixel)
@@ -145,6 +126,9 @@ private async Task<Vector3[]> SampleSourcePixelColorsAsync(int sampleCount)
145126
// Take the red, green, and blue channels to make a floating-point space color.
146127
samples[colorIndex] = new Vector3(pixelBytes[2], pixelBytes[1], pixelBytes[0]) / byte.MaxValue;
147128
colorIndex++;
129+
130+
// Advance by step amount
131+
pixelByteStream.Position += step;
148132
}
149133

150134
// If we skipped any pixels, trim the span
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.WinUI.Helpers;
6+
7+
/// <summary>
8+
/// A base class for a color data source in the <see cref="ColorPaletteSampler"/>.
9+
/// </summary>
10+
public abstract partial class ColorSource : DependencyObject
11+
{
12+
/// <summary>
13+
/// An event invoked when the source pixels changed.
14+
/// </summary>
15+
public event EventHandler? SourceUpdated;
16+
17+
/// <summary>
18+
/// Retreives the pixels from the source as a stream
19+
/// </summary>
20+
/// <param name="requestedSamples">The number of samples requested by the <see cref="ColorPaletteSampler"/>.</param>
21+
/// <returns>A stream of pixels in rgba format.</returns>
22+
public abstract Task<Stream?> GetPixelDataAsync(int requestedSamples);
23+
24+
/// <summary>
25+
/// Invokes the <see cref="SourceUpdated"/> event.
26+
/// </summary>
27+
protected void InvokeSourceUpdated() => SourceUpdated?.Invoke(this, EventArgs.Empty);
28+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Windows.Graphics.Imaging;
6+
7+
namespace CommunityToolkit.WinUI.Helpers;
8+
9+
/// <summary>
10+
/// A <see cref="ColorSource"/> that uses a <see cref="Stream"/> directly as a source.
11+
/// </summary>
12+
public class StreamColorSource : ColorSource
13+
{
14+
/// <summary>
15+
/// Gets the <see cref="DependencyProperty"/> for the <see cref="Source"/> property.
16+
/// </summary>
17+
public static readonly DependencyProperty SourceProperty =
18+
DependencyProperty.Register(nameof(Source), typeof(UIElement), typeof(StreamColorSource), new PropertyMetadata(null, OnSourceChanged));
19+
20+
/// <summary>
21+
/// Gets or sets the <see cref="UIElement"/> source sampled for a color palette.
22+
/// </summary>
23+
public Stream? Source
24+
{
25+
get => (Stream)GetValue(SourceProperty);
26+
set => SetValue(SourceProperty, value);
27+
}
28+
29+
/// <inheritdoc/>
30+
public override async Task<Stream?> GetPixelDataAsync(int requestedSamples)
31+
{
32+
#if !HAS_UNO
33+
var decoder = await BitmapDecoder.CreateAsync(Source.AsRandomAccessStream());
34+
var pixelData = await decoder.GetPixelDataAsync();
35+
var bytes = pixelData.DetachPixelData();
36+
return new MemoryStream(bytes);
37+
#else
38+
// NOTE: This assumes raw pixel data.
39+
// TODO: Uses some form of image processing
40+
return Source;
41+
#endif
42+
}
43+
44+
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
45+
{
46+
if (d is not StreamColorSource source)
47+
return;
48+
49+
source.InvokeSourceUpdated();
50+
}
51+
}

0 commit comments

Comments
 (0)