且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

[UWP]使用Writeable?Bitmap创建HSV色轮

更新时间:2022-09-28 20:59:16

原文:[UWP]使用Writeable?Bitmap创建HSV色轮

1. HSV

1.1 HSV的定义

HSV都是一种将RGB色彩模型中的点在圆柱坐标系中的表示法,这种表示法试图做到比RGB基于笛卡尔坐标系的几何结构更加直观。HSV即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。

  • 色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等,取值0-360。红色是0,绿色是120,蓝色为240。
  • 饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。
  • 明度(V),数值越低越接近黑色。,取0-100%。

[UWP]使用Writeable?Bitmap创建HSV色轮

1.2 HSV与RGB

HSV在数学上定义为在RGB空间中的颜色的R, G和B的坐标的变换。
[UWP]使用Writeable?Bitmap创建HSV色轮

1.2.1 从RGB到HSL或HSV的转换

(r, g, b)分别是一个颜色的红、绿和蓝坐标,它们的值是在0到1之间的实数。设max等价于r, g和b中的最大者。设min等于这些值中的最小者:
[UWP]使用Writeable?Bitmap创建HSV色轮

[UWP]使用Writeable?Bitmap创建HSV色轮

1.2.2 从HSV到RGB的转换

给定在HSV中 (h, s, v)值定义的一个颜色,带有如上的h,和分别表示饱和度和明度的s和v变化于0到1之间,在RGB空间中对应的 (r, g, b)三原色可以计算为(R,G,B变化于0到1之间):

[UWP]使用Writeable?Bitmap创建HSV色轮

对于每个颜色向量 (r, g, b),

[UWP]使用Writeable?Bitmap创建HSV色轮

1.3 HSV的应用

HSV模型通常用于计算机图形应用中。在用户必须选择一个颜色应用于特定图形元素各种应用环境中,经常使用HSV 色轮。
[UWP]使用Writeable?Bitmap创建HSV色轮

另外,由于HSV对用户来说是一种直观的颜色模型,所以常用于调整图片,下图为Paint.Net中调整图片:

[UWP]使用Writeable?Bitmap创建HSV色轮
[UWP]使用Writeable?Bitmap创建HSV色轮

下图为UWPCommunityToolkit中通过Saturation调整图片:
[UWP]使用Writeable?Bitmap创建HSV色轮

1.4 HSV与色轮

很多设计方面的书籍都有介绍使用色轮为UI配色,由于篇幅较大这里就不在论述了,具体可以参考以下链接:网页设计中怎么配色

2. WriteableBitmap

WriteableBitmap 提供可写入并可更新的 BitmapSource。也就是说, 你可动态更改图像,然后重新呈现更新的图像。使用WinRTXamlToolkit可以轻松完成这个操作,代码如下:

var diameter = 100;
var source = new WriteableBitmap(diameter, diameter);
var pixels = source.PixelBuffer.GetPixels();
for (var i = 0; i < diameter * diameter; i++)
{
    var color = Color.FromArgb(255, 255, 255, 255);
    pixels.Bytes[i * 4] = color.B;
    pixels.Bytes[i * 4 + 1] = color.G;
    pixels.Bytes[i * 4 + 2] = color.R;
    pixels.Bytes[i * 4 + 3] = color.A;
}
pixels.UpdateFromBytes();
source.Invalidate();
_imageElement.Source = source;

上面的代码将一个尺寸在100*100的WriteableBitmap中所有像素都设为白色,然后设置为图片的Source。在这里像素数据的格式为BitmapPixelFormat.Bgra8,即用四个Byte分别表示颜色的RGRA(通常颜色表示成ARGB,如#FFFF0000即Alpha:255,Red:255,Green:0,Blue:0)。

还可以将WriteableBitmap保存成文件,同样使用WinRTXamlToolkit实现:

await source.SaveAsync(KnownFolders.PicturesLibrary, "Wheel.png");

3. 使用WriteableBitmap创建HSV色轮

前面介绍了Hsv色轮,也介绍了如何使用WriteableBitmap,那么用WriteableBitmap实现一个HSV色轮是一件很简单的事,只需要计算每个像素点距离中心点的角度(Hue)和距离(Saturation)得出HsvColor,再转换成ArgbColor填入WriteableBitmap就实现了。具体代码如下:

var diameter = width < height ? width : height;
var radius = diameter / 2;
var source = new WriteableBitmap(diameter, diameter);
var pixels = source.PixelBuffer.GetPixels();
var array = new double[diameter, diameter];
for (var i = 0; i < diameter * diameter; i++)
{
    var x = i % diameter;
    var y = i / diameter;
    var distance = Math.Sqrt(Math.Pow(radius - x, 2) + Math.Pow(radius - y, 2));
    var saturation = distance / radius;
    array[x, y] = saturation;
    if (saturation >= 1)
    {
        pixels.Bytes[i * 4] = 0;
        pixels.Bytes[i * 4 + 1] = 0;
        pixels.Bytes[i * 4 + 2] = 0;
        pixels.Bytes[i * 4 + 3] = 0;
    }
    else
    {
        var distanceOfX = x - radius;
        var distanceOfY = y - radius;

        var theta = Math.Atan2(distanceOfY, distanceOfX);

        if (theta < 0)
            theta += 2 * Math.PI;

        var hue = theta / (Math.PI * 2) * 360.0;
        var color = ColorHelper.FromHsv(hue, saturation, 1);
        pixels.Bytes[i * 4] = color.B;
        pixels.Bytes[i * 4 + 1] = color.G;
        pixels.Bytes[i * 4 + 2] = color.R;
        pixels.Bytes[i * 4 + 3] = 255;
    }
}
pixels.UpdateFromBytes();
source.Invalidate();

[UWP]使用Writeable?Bitmap创建HSV色轮

有个小问题,即使不仔细看也能看到圆形的边缘锯齿很严重。当然可以在上面的代码里加入高斯模糊的算法处理这些锯齿,但毕竟这篇文章不打算讨论到这么深入。可以简单地使用WriteableBitmapEx对整个WriteableBitmap进行高斯模糊:

source.Convolute(WriteableBitmapExtensions.KernelGaussianBlur5x5);

The WriteableBitmapEx library is a collection of extension methods for the WriteableBitmap. The WriteableBitmap class is available for all XAML flavors including Windows Phone, WPF, WinRT Windows Store XAML, (Windows 10) UWP and Silverlight.

这样看起来就好很多了。
[UWP]使用Writeable?Bitmap创建HSV色轮

4. HSV转RGB的陷阱

上面代码中RGB和HSV互换使用了UWPCommunityToolkit中的ColorHelper,ColorHelper的介绍是这样的:

The Colors Helper lets users convert colors from text names, HTML hex, HSV, or HSL to Windows UI Colors (and back again of course).

但是这里有个陷阱。以下代码将一个RGB color转换成HSV color,再转换回RGB color,看起来没什么问题:

var color = Color.FromArgb(255, 255, 20, 200);
var hsv = ColorHelper.ToHsv(color);
Debug.WriteLine(string.Format("H:{0}  S:{1}  V:{2}", hsv.H,hsv.S , hsv.V));
color = ColorHelper.FromHsv(hsv.H, hsv.S , hsv.V );
Debug.WriteLine(string.Format("R:{0}  G:{1}  B:{2}", color.R, color.G, color.B));

但是看输出就能发现转回来的RBG color改变了:

H:314.042553191489  S:0.92156862745098  V:1
R:255  G:19  B:199

造成这个问题的原因在于RGB能表示的颜色范围有限,只是256 * 256 * 256=16777216种颜色。而HSV如果使用int值,只能表示360 * 100 * 100=3600000种颜色,如果用double则几乎有无数种组合,这样两种颜色模型间就不匹配了。这种情况下只能折衷一下限制HSV的精度了,改成下面的代码能解决上面的问题:

color = ColorHelper.FromHsv(Math.Round(hsv.H), Math.Round(hsv.S, 2), Math.Round(hsv.V, 2));

5. 参考

HSL and HSV - Wikipedia
WriteableBitmap Class

6. 源码

HsvColorWheel for UWP