最近个人项目需要用到ColorPicker,但是适用于WinRT和Win10的基本没用,所以只能自己造轮子了。
平台环境
- Windows 10
- Visual Studio 2015
思路
确定需求后,我查找了各方信息,发现PhotoShop的ColorPicker最符合我的需求,这里我们实现的仿PhotoShop HSB取色器,样式如下图。
确定目标后,则需要研究具体的调色原理了。我们都知道,程序使用的一般都是RGB颜色,而这里使用的则是HSB颜色。顾名思义,HSB分别是指色相(Hue)纯度(Saturation)明度(Brightness),这三个参数构成了HSB颜色,这比RGB颜色更易于选取,能够同时提供的颜色种类最多,对应HSV。色相可以通过色环来表示,HSB三个参数均和RGB保持着数学上的关系。详细信息可见于维基百科:https://en.wikipedia.org/wiki/HSL_and_HSV
这里计算流程为先计算色相,然后固定亮度,最后计算饱和度。首先对于色相,计算相对简单,分析色环,即可发现处于不同度数区间对应的RGB值也是有规律的。
具体来说假定HSB值为(H,100%,100%)条件下,RGB值对应关系如下:
H Color Value |
|
|
----------------------------- |
|
0-60 G 0->255 |
|
60-120 R 255->0 |
|
120-180 B 0->255 |
|
180-240 G 255->0 |
|
240-360 R 0->255 |
|
300-360 B 255->0 |
接下来根据饱和度S来进一步计算出RGB,此时假定条件为(H,S,100%),计算公式如下:
r"= r'+ (255 - r') * s
g"= g'+ (255 - g') * s
b"= b'+ (255 - b') * s
其中r',g',b'分别为第一步计算出的RGB结果。
最后一步,亮度L值与RGB关系最简单,RGB只需要分别乘以亮度即可。
实现代码
核心计算类CWColorService
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.UI; namespace CWColorPicker.Core { public class CWColorService { /// <summary> /// Convert HSB value to RGB value /// </summary> /// <param name="hsb">HSB value</param> /// <returns>RGB value</returns> public static int[] HSBToRGB(float[] hsb) { var rgb = new float[3]; var hValue = hsb[0]; /* Firstly, we need to calculate RGB value, when the HSB value is (h,100%,100%). H Color Value ---------------------------- 0-60 G 0->255 60-120 R 255->0 120-180 B 0->255 180-240 G 255->0 240-360 R 0->255 300-360 B 255->0 */ if (hValue <= 60) { rgb[0] = 255; rgb[1] = hValue / 60.0f * 255; } else if (hValue <= 120) { hValue -= 60; rgb[1] = 255; rgb[0] = (1 - hValue / 60.0f) * 255; } else if (hValue <= 180) { hValue -= 120; rgb[1] = 255; rgb[2] = hValue / 60.0f * 255; } else if (hValue <= 240) { rgb[2] = 255; hValue -= 180; rgb[1] = (1 - hValue / 60.0f) * 255; } else if (hValue <= 300) { rgb[2] = 255; hValue -= 240; rgb[0] = hValue / 60.0f * 255; } else { hValue -= 300; rgb[0] = 255; rgb[2] = (1 - hValue / 60.0f) * 255; } /* Secondly, acorrding to the value of staturation, we can calculate the rgb value, when the value of hsb is (h,s,100%) ------------------------- r"= r'+ (255 - r') * s g"= g'+ (255 - g') * s b"= b'+ (255 - b') * s */ for (int i = 0; i < 3; i++) { rgb[i] += (255 - rgb[i]) * hsb[1]; } var result = new int[3]; /* Finally, we need to calculate the real value of rgb, according to the value of brightness r = r" * br g = g" * br b = g" * br */ for (int i = 0; i < 3; i++) { rgb[i] *= hsb[2]; result[i] = (int)(rgb[i] + 0.5); } return result; } /// <summary> /// Convert RGB value to HSB value /// </summary> /// <param name="rgb">RGB Value</param> /// <returns></returns> public static float[] RGBToHSB(int[] rgb) { var result = new float[3]; return result; } /// <summary> /// get color from rgb value /// </summary> /// <param name="r"></param> /// <param name="g"></param> /// <param name="b"></param> /// <returns></returns> public static Color ColorFromRGB(int r,int g,int b) { var color = Color.FromArgb(255, (byte)r, (byte)g, (byte)b); return color; } public static Color ColorFromRGB(int[] rgb) { var color = ColorFromRGB(rgb[0], rgb[1], rgb[2]); return color; } } }
自定义Xaml控件
<UserControl x:Class="CWColorPicker.UI.CWColorPicker" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:CWColorPicker.UI" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignHeight="150" d:DesignWidth="150"> <Grid x:Name="ColorPanel"> <Image x:Name="ColorImage" Source="ms-appx:///Resource/color-pan.png" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" PointerPressed="ColorImage_PointerPressed" Margin="0"></Image> </Grid> </UserControl>
namespace CWColorPicker.UI { public sealed partial class CWColorPicker : UserControl { /// <summary> /// current selected color /// </summary> public Color Color { get { return (Color)GetValue(ColorProperty); } set { SetValue(ColorProperty, value); } } // Using a DependencyProperty as the backing store for Color. This enables animation, styling, binding, etc... public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(CWColorPicker), new PropertyMetadata(0)); /// <summary> /// current ponit in color picker /// </summary> public Point ColorPoint { get { return (Point)GetValue(ColorPointProperty); } set { SetValue(ColorPointProperty, value); } } // Using a DependencyProperty as the backing store for ColorPoint. This enables animation, styling, binding, etc... public static readonly DependencyProperty ColorPointProperty = DependencyProperty.Register("ColorPoint", typeof(Point), typeof(CWColorPicker), new PropertyMetadata(0)); /// <summary> /// ColorSelected Event /// </summary> public event EventHandler<CWColorSelectedArgs> ColorSelected; private void ColorChange(float[] hsb) { if (ColorSelected != null) { ColorSelected(this, new CWColorSelectedArgs(CWColorService.ColorFromRGB(CWColorService.HSBToRGB(hsb)))); } } private void ColorChange(Color color) { if (ColorSelected != null) { ColorSelected(this, new CWColorSelectedArgs(color)); } } public CWColorPicker() { this.InitializeComponent(); initPanelImage(); } /// <summary> /// load resource image from dll /// </summary> private async void initPanelImage() { var panel = new BitmapImage(); var imageStream = Assembly.Load(new AssemblyName("CWColorPicker")).GetManifestResourceStream("CWColorPicker.Resource.color-pan.png"); await panel.SetSourceAsync(imageStream.AsRandomAccessStream()); this.ColorImage.Source = panel; } /// <summary> /// calculate the color according to the touch point /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ColorImage_PointerPressed(object sender, PointerRoutedEventArgs e) { // Debug.WriteLine("pressed"); // Debug.WriteLine(e.GetCurrentPoint(this.ColorPanel).Position); var position = e.GetCurrentPoint(this.ColorImage).Position; var hsb = new float[3]; hsb[2] = 1.0f; hsb[0] = (float)(int)(position.X / this.ColorImage.ActualWidth * 360); hsb[1] = float.Parse((position.Y / this.ColorImage.ActualHeight).ToString("0.00")); this.Color = CWColorService.ColorFromRGB(CWColorService.HSBToRGB(hsb)); this.ColorPoint = position; ColorChange(this.Color); } } }
其他代码及完整项目可以在Github上获取,地址:https://github.com/ChangweiZhang/CWColorPicker
实现效果