WPF 简单实现颜色选择器

  • 框架使用.NET4
  • Visual Studio 2022;

实现代码

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逻辑 处理代码如下:

  • 首先嵌套循环,用于在图像的每个像素位置上进行操作色值。
  • heightwidth 是图像的高度和宽度。
  • 在外层循环中,变量 y 从 0 开始递增,直到小于 height
  • 在内层循环中,变量 x 从 0 开始递增,直到小于 width
  • 在每个像素位置上,通过计算 normalizedXnormalizedY,将 xy 的值归一化到 [0, 1] 范围内。
  • 使用 HSVToRGB 函数将归一化后的 normalizedXnormalizedY 和 1(表示最大亮度)转换为 RGB 值,并将结果存储在 rgb 变量中。
  • 计算像素在图像数据缓冲区中的偏移量 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]。
  • rgb 是输出参数,用于存储转换后的 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 值赋给输出参数 rgb

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.HorizontalChangee.VerticalChange 相加,得到新的位置。
  • 计算 Canvas 的右侧和底部边界。通过 canvas.ActualWidth - thumb.ActualWidthcanvas.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);
        }

6)新增 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);
            }
        }

效果图

到此这篇关于基于WPF开发简单的颜色选择器的文章就介绍到这了,更多相关WPF颜色选择器内容请搜索本网站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本网站!

您可能感兴趣的文章:

  • C#及WPF获取本机所有字体和颜色的方法
  • C# wpf简单颜色板的实现
  • C#中WPF颜色对话框控件的实现
  • 基于WPF实现用户头像选择器的示例代码