1. HSV 1.1 HSV的定義 HSV都是一種將RGB色彩模型中的點在圓柱坐標系中的表示法,這種表示法試圖做到比RGB基於笛卡爾坐標系的幾何結構更加直觀。HSV即色相、飽和度、明度(英語:Hue, Saturation, Value),又稱HSB,其中B即英語:Brightness。 色相(H) ...
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%。
1.2 HSV與RGB
HSV在數學上定義為在RGB空間中的顏色的R, G和B的坐標的變換。
1.2.1 從RGB到HSL或HSV的轉換
(r, g, b)分別是一個顏色的紅、綠和藍坐標,它們的值是在0到1之間的實數。設max等價於r, g和b中的最大者。設min等於這些值中的最小者:
1.2.2 從HSV到RGB的轉換
給定在HSV中 (h, s, v)值定義的一個顏色,帶有如上的h,和分別表示飽和度和明度的s和v變化於0到1之間,在RGB空間中對應的 (r, g, b)三原色可以計算為(R,G,B變化於0到1之間):
對於每個顏色向量 (r, g, b),
1.3 HSV的應用
HSV模型通常用於電腦圖形應用中。在用戶必須選擇一個顏色應用於特定圖形元素各種應用環境中,經常使用HSV 色輪。
另外,由於HSV對用戶來說是一種直觀的顏色模型,所以常用於調整圖片,下圖為Paint.Net中調整圖片:
下圖為UWPCommunityToolkit中通過Saturation調整圖片:
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();
有個小問題,即使不仔細看也能看到圓形的邊緣鋸齒很嚴重。當然可以在上面的代碼裡加入高斯模糊的演算法處理這些鋸齒,但畢竟這篇文章不打算討論到這麼深入。可以簡單地使用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.
這樣看起來就好很多了。
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