项目源码:在终章发布
索引
- 取色板
- PS取色板
- Unity编辑器取色板
- TextureShop取色板
- 界面设计
- H取色区域
- SV取色区域
- HSV联合取色逻辑
- H取色区域Shader
- SV取色区域Shader
- ColorPicker
- H取色区域检测输入
- SV取色区域检测输入
- 更新取色面板
取色板
取色板
,也既是颜色选择器,方便我们人眼选择颜色的工具。
在二维图像处理程序中,这应该是最最基本的工具之一,那么在TextureShop
中,我们首先要实现的就是它。
PS取色板
我们先来看看PS
的取色板(较复杂,同时支持RGB
、HSV
、Lab
颜色空间):
Unity编辑器取色板
再来看看Unity编辑器
的取色板(相对简单,也支持RGB
、HSV
颜色空间):
TextureShop取色板
界面设计
回到我们的TextureShop
,取色板的界面经过设计后呈如下模样:
众所周知,设计取色板
的初衷就是为了方便人眼选择颜色,而针对人眼相对友好的颜色空间
为HSV
,所以我们必须要在界面上展现出颜色的HSV
值,并使得用户能够根据HSV
值选择颜色。
H取色区域
通过了解HSV
颜色空间(了解HSV颜色空间),得知HSV
中H
值为色相
,也即是决定了颜色的最终种类
(是红还是绿),所以取色板
面板中,竖形选取区域即为H取色区域
:
H取色区域(竖形选取区域):
1.只能竖
方向取值(图中白色横条代表了取值所在位置);
2.取值类型为H
值;
3.H
取值范围:上 - 下 (1 - 0);
SV取色区域
H
为色相,当色相相同的情况下:
S
值决定了该颜色的饱和度,从完全不饱和(灰白色)到完全饱和(正常颜色)之间,取值范围为0-1;V
值决定了该颜色的亮度,从完全不亮(黑色)到完全亮(白色)之间,取值范围为0-1;
那么在取色板
面板中,矩形选取区域即可做为SV取色区域
(因为他在横竖方向上分别可取2个值):
SV取色区域(矩形选取区域):
1.只能横、竖
方向取值(图中白色圆点代表了取值所在位置);
2.取值类型为S、V
值;
3.S
取值范围:左 - 右 (0 - 1);
4.V
取值范围:上 - 下 (1 - 0);
HSV联合取色逻辑
综上所述,H
值既是取色的首选,用户通过在H取色区域
选定一个颜色种类后,再在SV取色区域
选取(也可称为调节)该颜色的饱和度、亮度,达到微调颜色的目的。
也即是:
H取色区域:选取颜色种类。
SV取色区域:微调颜色饱和度、亮度。
事实上,所有的取色板基于HSV
空间的取色逻辑几乎都是这样实现的,回看PS
的取色板:
右边竖形选取区域即为H取色区域
,左边矩形选取区域即为SV取色区域
。
在H取色区域
选取了红色(H=0)
后;
在SV取色区域
即可调节红色
的饱和度、亮度;
越左边饱和度越低,越接近
灰白色
;
越右边饱和度越高,越接近原色
(这里为红色
);
越上边亮度越高,颜色越亮
;
越下边亮度越低,颜色越暗
;
H取色区域Shader
事实上,搞清楚了实现原理之后,后面的编码部分就极其简单了,比方H取色区域
的渲染Shader,只需要取uv
坐标的纵坐标
为H
值,然后输出颜色即可:
Shader "UI/TextureShop/ColorPickerH"
{
......
//片元处理方法
fixed4 frag(FragData IN) : SV_Target
{
//直接取uv坐标的纵坐标作为H值
half h = IN.texcoord.y;
//使:S=1(原色),V=1(最亮)
//将此HSV颜色值转换为RGB颜色值
half3 rgb = HSVToRGB(half3(h, 1, 1));
half4 color = half4(rgb.r, rgb.g, rgb.b, 1);
//输出颜色
return color;
}
......
}
完成后效果如下:
SV取色区域Shader
SV取色区域
同理,只需要取uv
坐标的横坐标
为S
值,取uv
坐标的纵坐标
为V
值,然后输出颜色即可。
至于H
值,SV取色区域
需要外部传入当前的H
值,也即是H取色区域
当前的取值需要传给SV取色区域
:
Shader "UI/TextureShop/ColorPickerSV"
{
Properties
{
//可供外部传入的H值
[HideInInspector] _H("H", float) = 0
}
......
//片元处理方法
fixed4 frag(FragData IN) : SV_Target
{
//获取外部传入的H值
half h = _H;
//直接取uv坐标的横坐标作为S值
half s = IN.texcoord.x;
//直接取uv坐标的纵坐标作为V值
half v = IN.texcoord.y;
//将此HSV颜色值转换为RGB颜色值
half3 rgb = HSVToRGB(half3(h, s, v));
half4 color = half4(rgb.r, rgb.g, rgb.b, 1);
//输出颜色
return color;
}
......
}
完成后效果如下:
到此,取色板
的核心逻辑也就差不多了然于胸了,接下来是C#部分。
ColorPicker
编写一个类ColorPicker
,用于完成整个取色板
的逻辑驱动,主要包含如下步骤:
1.检测用户在
H取色区域
的输入,获取用户选取的H值;
2.将H值传入SV取色区域
;
3.检测用户在SV取色区域
的输入,获取用户选取的S、V值;
4.将H、S、V值实时显示在取色面板中;
5.将H、S、V值转换为R、G、B值后,实时显示在取色面板中;
6.在当前区域
显示用户当前选取的最终颜色;
7.当用户手动输入了H、S、V值或R、G、B值后,同步更新H取色区域
及SV取色区域
中的选取图标位置;
8.当用户点击确认按钮
,取色完毕,返回用户选取的最终颜色。
H取色区域检测输入
H取色区域
的检测输入我使用了ICanvasRaycastFilter
接口来实现,定义了一个类PickBoard
来实现该接口:
/// <summary>
/// 拾取板
/// </summary>
[DisallowMultipleComponent]
public sealed class PickBoard : MonoBehaviour, ICanvasRaycastFilter
{
/// <summary>
/// 图片背景
/// </summary>
public Image Board;
/// <summary>
/// 拾取点(H取色时的白色横条,SV取色时的白色圆点)
/// </summary>
public RectTransform Point;
/// <summary>
/// 是否忽略拾取点的X坐标(H取色时应当忽略)
/// </summary>
public bool IsIgnoreX = false;
/// <summary>
/// 拾取事件(每次取色时触发)
/// </summary>
public UnityEvent<Vector2> PickEvent;
private RectTransform _rectTransform;
/// <summary>
/// 平面布局位置组件
/// </summary>
private RectTransform Rect
{
get
{
if (_rectTransform == null)
{
_rectTransform = GetComponent<RectTransform>();
}
return _rectTransform;
}
}
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (Input.GetMouseButton(0))
{
Vector2 ui = Utility.ScreenPointToUGUIPoint(sp, eventCamera, Rect);
if (IsIgnoreX) ui.x = 0;
Point.anchoredPosition = ui;
Vector2 uv = Utility.ScreenPointToUVPoint(sp, eventCamera, Rect);
PickEvent?.Invoke(uv);
}
return true;
}
/// <summary>
/// 设置拾取的UV坐标(用户手动输入HSV值时,更新选取图标位置)
/// </summary>
/// <param name="uv">UV坐标</param>
public void SetUV(Vector2 uv)
{
float x = (uv.x - Rect.pivot.x) * Rect.sizeDelta.x;
float y = (uv.y - Rect.pivot.y) * Rect.sizeDelta.y;
Vector2 ui = new Vector2(x, y);
if (IsIgnoreX) ui.x = 0;
Point.anchoredPosition = ui;
}
}
SV取色区域检测输入
SV取色区域
的检测输入同上。
更新取色面板
回到ColorPicker
类中,我准备将剩下的逻辑都统称为更新取色面板
(将H值传入SV取色区域、显示HSV值、显示RGB值、显示当前颜色、更新取色区域的图标位置等),当且仅当取色面板为脏状态
时,才会在下一帧触发更新取色面板
的行为:
/// <summary>
/// 取色板
/// </summary>
[DisallowMultipleComponent]
public sealed class ColorPicker : MonoBehaviour
{
//是否为脏状态
private bool _isDirty = false;
//是否以HSV值为主,否则以RGB值为主
private bool _isUseHSV = true;
private void Update()
{
if (_isDirty)
{
RefreshCurrentColor();
_isDirty = false;
}
}
/// <summary>
/// 更新取色面板
/// </summary>
private void RefreshCurrentColor()
{
//以HSV为主(用户输入了HSV值),将HSV转换后,覆盖RGB值
if (_isUseHSV)
{
Current = Color.HSVToRGB(H, S, V);
R = (int)(Current.r * 255);
G = (int)(Current.g * 255);
B = (int)(Current.b * 255);
A = (int)(Current.a * 255);
}
//以RGB为主(用户输入了RGB值),将RGB转换后,覆盖HSV值
else
{
Current = new Color(R / 255f, G / 255f, B / 255f, A / 255f);
Color.RGBToHSV(Current, out float h, out float s, out float v);
H = h;
S = s;
V = v;
}
//关闭UI控件的输入检测
_isMonitor = false;
//同步更新H取色区域的选取图标位置
PickBoard_H.SetUV(new Vector2(0, H));
//同步更新SV取色区域的选取图标位置
PickBoard_SV.SetUV(new Vector2(S, V));
//将H值传入SV取色区域
PickBoard_SV.Board.material.SetFloat("_H", H);
//将HSV、RGBA值更新到UI控件
InputField_H.text = H.ToString("F2");
InputField_S.text = S.ToString("F2");
InputField_V.text = V.ToString("F2");
InputField_R.text = R.ToString();
InputField_G.text = G.ToString();
InputField_B.text = B.ToString();
InputField_A.text = A.ToString();
//更新当前选取的颜色
Img_CurrentColor.color = Current;
//重新开启UI控件的输入检测
_isMonitor = true;
}
}
那么接下来,需要定义HSV、RGBA的UI控件
及属性值
:
/// <summary>
/// 取色板
/// </summary>
[DisallowMultipleComponent]
public sealed class ColorPicker : MonoBehaviour
{
/// <summary>
/// 拾取板H
/// </summary>
public PickBoard PickBoard_H;
/// <summary>
/// 拾取板SV
/// </summary>
public PickBoard PickBoard_SV;
/// <summary>
/// 输入框H
/// </summary>
public InputField InputField_H;
/// <summary>
/// 输入框S
/// </summary>
public InputField InputField_S;
/// <summary>
/// 输入框V
/// </summary>
public InputField InputField_V;
/// <summary>
/// 输入框R
/// </summary>
public InputField InputField_R;
/// <summary>
/// 输入框G
/// </summary>
public InputField InputField_G;
/// <summary>
/// 输入框B
/// </summary>
public InputField InputField_B;
/// <summary>
/// 输入框A
/// </summary>
public InputField InputField_A;
/// <summary>
/// 当前拾取的颜色显示框
/// </summary>
public Image Img_CurrentColor;
private float _h = 0;
private int _r = 0;
/// <summary>
/// HSV分量H
/// </summary>
public float H
{
get
{
return _h;
}
set
{
//限制H值在0-1之间
_h = Mathf.Clamp01(value);
//H值一旦改变,取色板切换为脏状态
_isDirty = true;
//且表明在更新取色板时,以HSV值为主
_isUseHSV = true;
}
}
//S、V属性同理......
/// <summary>
/// RGB分量R
/// </summary>
public int R
{
get
{
return _r;
}
set
{
//限制R值在0-1之间
_r = Mathf.Clamp(value, 0, 255);
//R值一旦改变,取色板切换为脏状态
_isDirty = true;
//且表明在更新取色板时,以RGB值为主
_isUseHSV = false;
}
}
//G、B、A属性同理......
}
到此,TextureShop
的简易取色板
便完成了,我们来看看最终效果: