WPF 简单实现颜色选择器
控件名:ColorPicker
作 者:WPFDevelopersOrg - 驚鏵
原文链接[1]:https://github.com/yanjinhuagood/ColorPickerSample
框架使用
.NET4
;Visual Studio 2022
;
![5b18ace2a54a746deaf9b3219df36488.png](https://img-blog.csdnimg.cn/img_convert/5b18ace2a54a746deaf9b3219df36488.png)
1)新增 xaml
代码如下:
定义一个
WriteableBitmap
用于记录颜色缓冲值。在
XAML
中定义Canvas
设置背景为一张图像。在
Canvas
中添加Thumb
是一个可拖动的控件,用于实现交互中的拖动后获取颜色。
<Canvas x:Name="canvas" MouseLeftButtonDown="canvas_MouseLeftButtonDown">
<Canvas.Background>
<ImageBrush ImageSource="{Binding Bitmap}" />
</Canvas.Background>
<Thumb
x:Name="thumb"
Canvas.Left="0"
Canvas.Top="0"
Width="20"
Height="20"
Background="Transparent"
BorderBrush="Black"
BorderThickness="2"
DragDelta="Thumb_DragDelta">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10"
SnapsToDevicePixels="True" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Canvas>
2)新增 Loaded逻辑
处理代码如下:
首先嵌套循环,用于在图像的每个像素位置上进行操作色值。
height
和width
是图像的高度和宽度。在外层循环中,变量
y
从 0 开始递增,直到小于height
。在内层循环中,变量
x
从 0 开始递增,直到小于width
。在每个像素位置上,通过计算
normalizedX
和normalizedY
,将x
和y
的值归一化到 [0, 1] 范围内。使用
HSVToRGB
函数将归一化后的normalizedX
、normalizedY
和 1(表示最大亮度)转换为 RGB 值,并将结果存储在r
、g
和b
变量中。计算像素在图像数据缓冲区中的偏移量
pixelOffset
,其中stride
是每行像素占用的字节数。使用
Marshal.WriteByte
方法将 RGB 值写入图像数据缓冲区中的相应位置,同时设置 Alpha 通道为 0xFF(完全不透明)。
IntPtr backBuffer = Bitmap.BackBuffer;
int stride = Bitmap.BackBufferStride;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
byte r, g, b;
double normalizedX = (double)x / (width - 1);
double normalizedY = (double)y / (height - 1);
HSVToRGB(normalizedX, normalizedY, 1, out r, out g, out b);
int pixelOffset = y * stride + x * 4;
Marshal.WriteByte(backBuffer, pixelOffset + 0, b);
Marshal.WriteByte(backBuffer, pixelOffset + 1, g);
Marshal.WriteByte(backBuffer, pixelOffset + 2, r);
Marshal.WriteByte(backBuffer, pixelOffset + 3, 0xFF);
}
}
3)新增 HSVToRGB
方法代码如下:
HSVToRGB 方法返回 r 、g、b
h
是色相值,取值范围为 [0, 1]。s
是饱和度值,取值范围为 [0, 1]。v
是亮度值,取值范围为 [0, 1]。r
、g
和b
是输出参数,用于存储转换后的 RGB 值。在函数内部,根据 HSV 转换公式进行计算。如果饱和度
s
为 0,则表示灰度色调,此时将 RGB 的三个分量都设置为亮度v
的值,并乘以 255 转换为字节表示。如果饱和度
s
不为 0,则根据色相h
的值确定所处的色相区间,并根据公式计算出对应的 RGB 值。具体步骤如下:将色相
h
乘以 6,得到一个扩展的色相值hue
。取
hue
的整数部分作为索引i
,表示所处的色相区间。计算
hue
的小数部分f
。根据公式计算出对应的 RGB 值,其中
p
是亮度v
与饱和度s
的乘积,q
是亮度v
与饱和度s
以及f
的乘积,t
是亮度v
与饱和度s
以及(1.0 - f)
的乘积。根据索引
i
的值,将计算得到的 RGB 值赋给输出参数r
、g
和b
。
private static void HSVToRGB(double h, double s, double v, out byte r, out byte g, out byte b)
{
if (s == 0)
{
r = g = b = (byte)(v * 255);
}
else
{
double hue = h * 6.0;
int i = (int)Math.Floor(hue);
double f = hue - i;
double p = v * (1.0 - s);
double q = v * (1.0 - (s * f));
double t = v * (1.0 - (s * (1.0 - f)));
switch (i)
{
case 0:
r = (byte)(v * 255);
g = (byte)(t * 255);
b = (byte)(p * 255);
break;
case 1:
r = (byte)(q * 255);
g = (byte)(v * 255);
b = (byte)(p * 255);
break;
case 2:
r = (byte)(p * 255);
g = (byte)(v * 255);
b = (byte)(t * 255);
break;
case 3:
r = (byte)(p * 255);
g = (byte)(q * 255);
b = (byte)(v * 255);
break;
case 4:
r = (byte)(t * 255);
g = (byte)(p * 255);
b = (byte)(v * 255);
break;
default:
r = (byte)(v * 255);
g = (byte)(p * 255);
b = (byte)(q * 255);
break;
}
}
}
4)新增 Thumb_DragDelta
代码如下:
在事件处理程序中,首先获取拖动的
Thumb
控件,并计算出新的左侧和顶部位置。通过Canvas.GetLeft(thumb)
和Canvas.GetTop(thumb)
方法获取当前Thumb
控件在Canvas
中的左侧和顶部位置,然后将其与拖动的变化量e.HorizontalChange
和e.VerticalChange
相加,得到新的位置。计算
Canvas
的右侧和底部边界。通过canvas.ActualWidth - thumb.ActualWidth
和canvas.ActualHeight - thumb.ActualHeight
计算出Canvas
的右侧和底部边界位置。对新的左侧和顶部位置进行边界检查。如果新的左侧位置小于 0,则将其设置为 0,以保证
Thumb
控件不会超出Canvas
的左侧边界。如果新的左侧位置大于Canvas
的右侧边界位置canvasRight
,则将其设置为canvasRight
,以确保Thumb
控件不会超出Canvas
的右侧边界。类似地,对新的顶部位置进行边界检查。通过
Canvas.SetLeft(thumb, newLeft)
和Canvas.SetTop(thumb, newTop)
将Thumb
控件的位置更新为新的左侧和顶部位置。调用
GetAreaColor()
方法来获取更新后的区域颜色。
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
var thumb = (Thumb)sender;
double newLeft = Canvas.GetLeft(thumb) + e.HorizontalChange;
double newTop = Canvas.GetTop(thumb) + e.VerticalChange;
double canvasRight = canvas.ActualWidth - thumb.ActualWidth;
double canvasBottom = canvas.ActualHeight - thumb.ActualHeight;
if (newLeft < 0)
newLeft = 0;
else if (newLeft > canvasRight)
newLeft = canvasRight;
if (newTop < 0)
newTop = 0;
else if (newTop > canvasBottom)
newTop = canvasBottom;
Canvas.SetLeft(thumb, newLeft);
Canvas.SetTop(thumb, newTop);
GetAreaColor();
}
5)新增 canvas_MouseLeftButtonDown
代码如下:
实现鼠标左键按下时,将 Thumb 控件移动到鼠标点击位置,并进行边界限制。同时,还获取了鼠标点击位置的颜色信息
private void canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var canvasPosition = e.GetPosition(canvas);
double newLeft = canvasPosition.X - thumb.ActualWidth / 2;
double newTop = canvasPosition.Y - thumb.ActualHeight / 2;
double canvasRight = canvas.ActualWidth - thumb.ActualWidth;
double canvasBottom = canvas.ActualHeight - thumb.ActualHeight;
if (newLeft < 0)
newLeft = 0;
else if (newLeft > canvasRight)
newLeft = canvasRight;
if (newTop < 0)
newTop = 0;
else if (newTop > canvasBottom)
newTop = canvasBottom;
Canvas.SetLeft(thumb, newLeft);
Canvas.SetTop(thumb, newTop);
var thumbPosition = e.GetPosition(canvas);
GetAreaColor(thumbPosition);
}
5)新增 GetAreaColor
代码如下:
Thumb
控件的中心点坐标转换为相对于Canvas
的坐标。计算每行像素数据所占的字节数。
创建一个字节数组,用于存储位图的像素数据。
将位图的像素数据复制到字节数组中。
计算要访问的像素在字节数组中的索引位置。
Color.FromArgb
取其Alpha
、红色、绿色和蓝色通道的值。在这段代码中,它被用于构造一个Color
对象,表示位图中特定像素的颜色。pixels[pixelIndex + 3]
表示字节数组中的第pixelIndex + 3
个元素,即 Alpha 通道的值。pixels[pixelIndex + 2]
表示字节数组中的第pixelIndex + 2
个元素,即红色通道的值。pixels[pixelIndex + 1]
表示字节数组中的第pixelIndex + 1
个元素,即绿色通道的值。pixels[pixelIndex]
表示字节数组中的第pixelIndex
个元素,即蓝色通道的值。
void GetAreaColor(Point? thumbPosition = null)
{
thumbPosition = thumbPosition == null ? thumbPosition = thumb.TranslatePoint(new Point(thumb.ActualWidth / 2, thumb.ActualHeight / 2), canvas) : thumbPosition;
int xCoordinate = (int)thumbPosition?.X;
int yCoordinate = (int)thumbPosition?.Y;
if (xCoordinate >= 0 && xCoordinate < Bitmap.PixelWidth && yCoordinate >= 0 && yCoordinate < Bitmap.PixelHeight)
{
int stride = Bitmap.PixelWidth * (Bitmap.Format.BitsPerPixel / 8);
byte[] pixels = new byte[Bitmap.PixelHeight * stride];
Bitmap.CopyPixels(new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight), pixels, stride, 0);
int pixelIndex = (yCoordinate * stride) + (xCoordinate * (Bitmap.Format.BitsPerPixel / 8));
Color color = Color.FromArgb(pixels[pixelIndex + 3], pixels[pixelIndex + 2], pixels[pixelIndex + 1], pixels[pixelIndex]);
MyBtn.Background = new SolidColorBrush(color);
}
}
![891b487e90b8a1b4013fae7d0e0494f3.gif](https://img-blog.csdnimg.cn/img_convert/891b487e90b8a1b4013fae7d0e0494f3.gif)
码云[2]
参考资料
[1]
原文链接: https://github.com/yanjinhuagood/ColorPickerSample
[2]码云: https://gitee.com/yanjinhua/ColorPickerSample