文章目录
什么是HSV/HSB/HSL
首先理解 HSV/HSB/HSL 都是什么
我理解:
- HSV : Hue, Saturation, Value
- HSB : Hue, Saturation, Brightness
- HSL : Hue, Saturation, Lightness(或是Luminance)
Hue : 色相
Saturation: 饱和度
其中的Value、Brightness、Lightness都是用于控制亮度用的
三者同一个意思,就是叫法不一
下面使用HSV的方式来介绍
看一张图:
下面是我看到这张图,然后对HSV的理解
Hue 色相
从上图可以看出:Hue就是颜色的周期
逆时针一圈可以看出过渡规律:
R~RG~G~GB~B~RB~R
后面~R可以可以省略,因为可以倒回开头的R
如下:
R~RG~G~GB~B~RB~R
⇒
R~RG~G~GB~B~RB
(红~黄~绿~浅蓝~蓝~粉紫(倒回~红))
而这些过渡,对应的是:Hue的0~360的6个区间,如下伪代码,对应的是:Hue 与 RGB 的映射
// jave.lin 2019.09.03
// Hue to RGB
oneRegion = 360f/6; // 或是1f/6
region1 = onRegion;
region2 = onRegion * 2;
region3 = onRegion * 3;
region4 = onRegion * 4;
region5 = onRegion * 5;
region6 = onRegion * 6;
if (position >= region1 && position < region2) {
// R~RG
} else if (position >= region2 && position < region3) {
// RG~G
} else if (position >= region3 && position < region4) {
// G~GB
} else if (position >= region4 && position < region5) {
// GB~B
} else if (position >= region5 && position < region6) {
// B~RB
}
Saturation饱和度
从上图可以看出来,就是颜色鲜艳度,就是对应白色到Hue色之间的过渡关系,Saturation越大越偏向于Hue色,否则偏向于白色
也可以理解为颜色的鲜艳度,饱和度高,越鲜艳,饱和度底,越灰白
对应伪代码就是:
// jave.lin 2019.09.03
// Saturation理解
float t = 0f~1f; // 或是0~100,用于控制饱和度强度
Color whiteColor = Color.white;
Color hueColor = ...; // 就是先从上面的hue中确定色相
Color FinalColor = Lerp(whiteColor, hueColor, t); // Lerp(a,b,t) => a*(1-t)+b*t,下面用到的Lerp都是一样的
Value 亮度
从上图可以看出,Value就是控制亮度的,Value值越小,越偏向黑色,否则偏向Hue色相应用Saturation后的颜色
伪代码:
// jave.lin 2019.09.03
// Value 理解
float t = 0f~1f; // 或是0~100,用于控制亮度强度
Color saturateColor = Lerp(whiteColor, hueColor, saturateT); // 上面Saturate的值
Color blackColor = Color.black;
Color FinalColor = Lerp(blackColor, saturateColor, t);
互转公式
除了上面我们自己总结的规律,下面是引用了别的博主的内容,参考:Unity Shader-后处理:简单的颜色调整(亮度,饱和度,对比度)
RGB to HSV 公式
HSV to RGB 公式
你也可以自己去编写相关的逻辑。
然后我最近有在学习Unity,与UnityShader相关的内容,里面有部分shader代码是有应用这些公式的,源码如下:
RGB to HSV & HSV to RGB
这个是没有考虑分量大小直接转换的:
#if defined(_COLORCOLOR_ON)
half3 RGBtoHSV(half3 arg1)
{
half4 K = half4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
half4 P = lerp(half4(arg1.bg, K.wz), half4(arg1.gb, K.xy), step(arg1.b, arg1.g));
half4 Q = lerp(half4(P.xyw, arg1.r), half4(arg1.r, P.yzx), step(P.x, arg1.r));
half D = Q.x - min(Q.w, Q.y);
half E = 1e-10;
return half3(abs(Q.z + (Q.w - Q.y) / (6.0 * D + E)), D / (Q.x + E), Q.x);
}
half3 HSVtoRGB(half3 arg1)
{
half4 K = half4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
half3 P = abs(frac(arg1.xxx + K.xyz) * 6.0 - K.www);
return arg1.z * lerp(K.xxx, saturate(P - K.xxx), arg1.y);
}
#endif
RGB to HSV ver1.0
这种是有考虑RGB分量那个比较高的分支处理。
half3 UnityMeta_RGBToHSVHelper(float offset, half dominantColor, half colorone, half colortwo)
{
half H, S, V;
V = dominantColor;
if (V != 0.0)
{
half small = 0.0;
if (colorone > colortwo)
small = colortwo;
else
small = colorone;
half diff = V - small;
if (diff != 0)
{
S = diff / V;
H = offset + ((colorone - colortwo)/diff);
}
else
{
S = 0;
H = offset + (colorone - colortwo);
}
H /= 6.0;
if (H < 6.0)
{
H += 1.0;
}
}
else
{
S = 0;
H = 0;
}
return half3(H, S, V);
}
half3 UnityMeta_RGBToHSV(half3 rgbColor)
{
// when blue is highest valued
if((rgbColor.b > rgbColor.g) && (rgbColor.b > rgbColor.r))
return UnityMeta_RGBToHSVHelper(4.0, rgbColor.b, rgbColor.r, rgbColor.g);
//when green is highest valued
else if(rgbColor.g > rgbColor.r)
return UnityMeta_RGBToHSVHelper(2.0, rgbColor.g, rgbColor.b, rgbColor.r);
//when red is highest valued
else
return UnityMeta_RGBToHSVHelper(0.0, rgbColor.r, rgbColor.g, rgbColor.b);
}
RGB2HSV & HSV2RGB
//================================================================
// HSV HELPERS
// source: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
float3 rgb2hsv(float3 c)
{
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
float3 hsv2rgb(float3 c)
{
c.g = max(c.g, 0.0); //make sure that saturation value is positive
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
}
使用C# WinForm写了个Demo
下面是使用了最初我们总结的规律逻辑来写的(没使用公式)。
主要以:Graphics类、与Bitmap来实现, Graphics.DrawImage(Image image);
圈圈、线条都是直接Graphics.DrawLine, Graphics.DrawEllipse
主要核心代码:
// t : [0~1]对应:0~360
// 返回的颜色
private void GetH(float t, out byte r, out byte g, out byte b)
{
r = 0; g = 0; b = 0;
const float t1 = 1f / 6;
const float t2 = 1f / 6 * 2;
const float t3 = 1f / 6 * 3;
const float t4 = 1f / 6 * 4;
const float t5 = 1f / 6 * 5;
const float t6 = 1f / 6 * 6;
const float invOne = 6f;
t = t < 0 ? 0 : t;
t = t > 1 ? 1 : t;
if (t >= 0 && t < t1)
{
// R ~ RG
r = 255;
g = (byte)(t * invOne * 255);
b = 0;
}
else if (t >= t1 && t < t2)
{
// RG ~ G
r = (byte)((1 - (t - t1) * invOne) * 255);
g = 255;
b = 0;
}
else if (t >= t2 && t < t3)
{
// G ~ GB
r = 0;
g = 255;
b = (byte)((t - t2) * invOne * 255);
}
else if (t >= t3 && t < t4)
{
// GB ~ B
r = 0;
g = (byte)((1 - (t - t3) * invOne) * 255);
b = 255;
}
else if (t >= t4 && t < t5)
{
// B ~ RB
r = (byte)((t - t4) * invOne * 255);
g = 0;
b = 255;
}
else if (t >= t5 && t <= t6)
{
// RB ~ R
r = 255;
g = 0;
b = (byte)((1 - (t - t5) * invOne) * 255);
}
}
private void GetSV(
// tr,tg,tb 目标颜色
byte tr, byte tg, byte tb,
// st 饱和度插值系数
// vt 亮度插值系数
float st, float vt,
// 输出的 rgb 颜色
out byte r, out byte g, out byte b)
{
float stt = 1 - st;
float vtt = 1 - vt;
// 饱和度
tb = Lerp(255, tb, st, stt);
tg = Lerp(255, tg, st, stt);
tr = Lerp(255, tr, st, stt);
// 亮度
b = Lerp(tb, 0, vt, vtt);
g = Lerp(tg, 0, vt, vtt);
r = Lerp(tr, 0, vt, vtt);
}
Project
Test_HSV_HSB_HSL_色相_饱和度_亮度.zip