Unity自定义UI组件(八) 颜色拾取器(上)

前言

unity中我们在选取颜色时unity会自动弹出颜色拾取器,通过操作选取自己想要的颜色,但是这个组建在我们自己的应用中无法使用,我们可以通过Windows的组建,但是unity 中调用总是有很多的问题,所以博主就自己开发了一个颜色拾取器,方便使用。我们将完美还原Unity自带颜色拾取器的全部功能,内容较多,分为两篇博客讲解

组件特点

  • 无需任何asset
  • 导入代码即可生成
  • 调用接口方便

实现效果

  • 普通状态
    Normal

  • 其他调色板模式
    Brightness

  • HSV模式
    HSV

  • 截图模式
    截图模式

主要内容

通过博主之前七篇UI组件的讲解,这里不再对基础的代码进行讲解,我们讲解一下其中的重点内容。其中的吸管图标,滑动条的图标和按钮图标都是直接利用代码绘制,详细见代码注解。

    1. 颜色原理与颜色模式
    1. 渐变色堆叠,实现多种调色板
    1. 实现放大部分区域,吸取颜色

详细讲解

1.颜色原理与颜色模式

1.1 色彩原理:

Unity中颜色由R、G、B、A(红、绿、蓝、透明)四个通道组成,所有的颜色都是通过这四个通道混合而成。

1.2 颜色模式

颜色模式是将颜色表现为数学形式的模式,分为RGB模式,CMYK模式(用于打印),HSV模式,灰度模式,位图模式等,这里我们以颜色拾取器设计的RGB和HSV来讲解一下。

1.2.1 RGB模式
  • RGB就是常说的三原色,R代表Red(红色),G代表Green(绿色),B代表Blue(蓝色)
  • 自然界中肉眼所能看到的任何色彩都可以由这三种色彩混合叠加而成,因此也称为加色模式
  • 计算机定义颜色时R、G、 B三种成分的取值范围是0-255,0表示没有刺激量,255表示刺激量达最大值
  • R、G、B均为255时就合成了白光,R、G、B均为0时就形成了黑色
  • 在显示屏上显示颜色定义时,往往采用这种模式,图像如用于电视、幻灯片、网络、多媒体,一般使用RGB模
1.2.2 HSV模式
  • HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间
  • 这个模型中颜色的参数分别是:色调(H),饱和度(S),明度(V)
  • 色调H

    用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,品红为300°

  • 饱和度S

    饱和度S表示颜色接近光谱色的程度。一种颜色,可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。饱和度高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和

  • 明度V

    明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)

以上内容做一个了解,在Unity中我们这里理解,RGB模式是R、G、B三个基本色来构成,而HSV是由H 色调、S 饱和度、V 明度(亮度)来组合构成。

1.3 调色板模式

因为颜色总是由三个分量组成,所有在二维的空间中,我们用两个分量组成的面和另一个分量来决定的,所以我们可以按照这个单独的分量来命名

  • Hue模式
  • Brightness模式
  • Saturation模式
  • Red模式
  • Green模式
  • Blue模式

2.渐变色堆叠,实现多种调色板

关于颜色渐变的内容我们在上一篇Unity自定义UI组件 渐变工具中具体讲解过,这里我们只讨论如何实现多种颜色的叠加

2.1 类型一

[效果图一]

2.1.1 实现原理
  • 1.利用ColoredTape调出七色渐变图
    • a. 设置TapeDirection为Horizontal方向
    • b. 设置Colors尺寸为7,依次七种颜色值

    [效果图1.2]
  • 2.再新建一个ColoredTape给七色渐变图蒙版
    • a. 设置TapeDirection为Vertical方向
    • b. 设置Colors尺寸为2,传入两种颜色值

    [效果图1.5]
2.1.2 拾取颜色原理

通过颜色的堆叠我们已经得出了以上的效果,但是我们得通过鼠标移动获取到混合后的颜色值,我们首先分别获取鼠标点击后两个ColoredTape的两个颜色值,然后将两个颜色值进行混合,其中就两个核心问题,一根据位置获取ColoredTape上某点的颜色值,二根据将两种颜色混合

  • 获取ColoredTape某点颜色值
    根据ColoredTape分为几个梯度,然后根据位置获取到这个点前后两个关键色的值,然后根据位置再对这两个颜色值进行混合
///
/// 获取色带上某点的颜色值
///
public virtual Color GetColor( Vector2 position )
{
    int colorCount = m_Colors.Count;
    switch (TapeDirection)
    {
        // 水平色带
        case E_DrawDirection.Horizontal:
            var perX = RectSize.x / ( ( colorCount - 1 ) * 2 );
            var doubelPer = perX * 2;
            var lenght0 = position.x + RectSize.x / 2.0f;
            int index0 = (int)( lenght0 / doubelPer );
            var temp0 = lenght0 % doubelPer / doubelPer;
            if (lenght0.Equals(RectSize.x))
            {
                index0--;
                temp0++;
            }
            // 获取离该点最近的两个颜色值,然后混合
            Color start = m_Colors[index0];
            Color end = m_Colors[index0 + 1];
            return start * ( 1 - temp0 ) + end * temp0;
        // 垂直色带
        case E_DrawDirection.Vertical:
            var perY = RectSize.y / ( ( colorCount - 1 ) * 2 );
            var doublePer = perY * 2;
            var lenght1 = position.y + RectSize.y / 2.0f;
            var index1 = (int)( lenght1 / doublePer );
            var temp1 = lenght1 % doublePer / doublePer;
            if ( lenght1.Equals(RectSize.y) )
            {
                index1--;
                temp1++;
            }
            / 获取离该点最近的两个颜色值,然后混合
            Color start1 = m_Colors[index1];
            Color end1 = m_Colors[index1 + 1];
            return start1 * (1 - temp1) + end1 * temp1;
        default:
            return Color.white;
    }
}
  • 两种颜色混合
    两个ColoredTape通过调用以上方法获得两个颜色值,然后对这两种颜色进行混合
private Color mixedTwoColoredTapeColor( Vector2 noniusPos )
{
    Color first = m_firstLayerCT.GetColor(noniusPos);
    // m_secondLayerCT是蒙版色带
    Color second = m_secondLayerCT.GetColor(noniusPos);
    float alpha = second.a;
    float index = 1 - alpha;
    // 利用透明度为比例值将两种颜色混色
    var color = new Color(second.r * index , second.g * index , second.b * index) + 
        new Color(first.r * alpha , first.g * alpha , first.b * alpha);
    color.a = Color.a;
    return color;
}

我们调用mexedTwoColoredTapeColor获取该种混合模式下某点的颜色,下面我们讲解另外一种类型

2.2 类型二
![
](https://img-blog.csdn.net/20170704195431102?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMjk1NzkxMzc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
2.2.1 实现原理

与渐变色带不一样的是,该种情况不能通过两个色带混合获得,所以编写一个继承至ColoredTape 的MultiColoredTape类型实现,我们只需要将图片的四个顶点设置为不同的颜色即可

public class MultiColoredTape : MainColorTape
{
    public Color TopLeft = Color.yellow;
    public Color TopRight = Color.white;
    public Color BottomLeft = Color.red;
    public Color BottomRight = Color.magenta;

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();
        RectSize = GetPixelAdjustedRect().size;
        var halfX = RectSize.x / 2.0f;
        var halfY = RectSize.y / 2.0f;
        // 每个顶点设置一种颜色
        UIVertex topLeft = GetUIVertex(new Vector2(-halfX , halfY) , TopLeft);
        UIVertex topRight = GetUIVertex(new Vector2(halfX , halfY) , TopRight);
        UIVertex bottomLeft = GetUIVertex(new Vector2(-halfX , -halfY) , BottomLeft);
        UIVertex bottomRight = GetUIVertex(new Vector2(halfX , -halfY) , BottomRight);
        vh.AddUIVertexQuad(new UIVertex[] { topLeft , topRight , bottomRight , bottomLeft });
        if ( Outline ) DrawOutline(vh);
    }
}

在面板中简单设置即可获得所要的效果

2.2.2 拾取颜色原理

这种颜色的混合让获取某一点色值的逻辑稍微变得复杂一点,总体分为两步

  • a. 计算四个点与该点的权重,权重通过计算距离比例(以图片的长宽为基础值)
// 获取权重 , 距离顶点超过边长的权重为0
private float getsacle( Vector2 position , Vector2 index )
{
    float lenght = Vector3.Distance(position , index);
    if ( lenght > RectSize.x )
        return 0;
    return 1 - lenght / RectSize.x;
}
  • b. 然后根据权重值将四个顶点色进行混合
public override Color GetColor( Vector2 position )
{
    float halfLenght = RectSize.x / 2.0f;
    var topleft = ( getsacle(position , new Vector2(-halfLenght , halfLenght)) ) * TopLeft;
    var topright = ( getsacle(position , new Vector2(halfLenght , halfLenght)) ) * TopRight;
    var bottomleft = ( getsacle(position , new Vector2(-halfLenght , -halfLenght)) ) * BottomLeft;
    var bottomright = ( getsacle(position , new Vector2(halfLenght , -halfLenght)) ) * BottomRight;
    // 根据权重值计算混合色
    var color = topleft + topright + bottomleft + bottomright;
    return color;
}

3.实现放大部分区域,吸取颜色

[效果图]

要实现这个效果,有几个核心的问题,如下:

  • 1.如何实现网格划分
  • 2.如何获取一个像素的颜色
  • 3.如何实现图像显示在网格中

接下来我们就挨个解决以上问题:

3.1 网格划分

其实在Unity自定义UI组件(二)函数图篇(下)中我们已经讲过过如何绘制网格,获取每个线段两个端点的位置,然后将其绘制成一条直线,通过循环我们就可以得到网格图,详细见代码

protected override void OnPopulateMesh(VertexHelper vh)
{
    vh.Clear();
    var rectSize = GetPixelAdjustedRect().size;
    var width = rectSize.x;
    var height = rectSize.y;
    var perWidth = width / XAxisCount;
    var perHeight = height / YAxisCount;

    Vector2 origin = new Vector2(-rectSize.x / 2.0f , -rectSize.y / 2.0f);
    //x axis 绘制x轴
    for ( int i = 0 ; i <= YAxisCount ; i++ )
    {
        Vector2 startPos = origin + new Vector2(0 , i * perHeight);
        Vector2 endPos = startPos + new Vector2(width , 0);
        vh.AddUIVertexQuad(GetQuad(startPos , endPos , Color , LineWidth));
    }
    //y axis 绘制y轴
    for ( int i = 0 ; i <= XAxisCount ; i++ )
    {
        Vector2 startPos = origin + new Vector2(i * perWidth , 0);
        Vector2 endPos = startPos + new Vector2(0 , height);
        vh.AddUIVertexQuad(GetQuad(startPos , endPos , Color , LineWidth));
    }
    // 将中心一格设置为红色 
    var x = (int)( XAxisCount / 2 );
    var y = (int)( YAxisCount / 2 );
    var bottomLeft = origin + new Vector2(perWidth * x , perHeight * y);
    var bottomRight = bottomLeft + new Vector2(perWidth , 0);
    var topLeft = bottomLeft + new Vector2(0 , perHeight);
    var topRight = topLeft + new Vector2(perWidth , 0);
    vh.AddUIVertexQuad(GetQuad(topLeft , topRight , FocusColor , FocuslineWidth));
    vh.AddUIVertexQuad(GetQuad(bottomLeft , bottomRight , FocusColor , FocuslineWidth));
    vh.AddUIVertexQuad(GetQuad(topLeft , bottomLeft , FocusColor , FocuslineWidth));
    vh.AddUIVertexQuad(GetQuad(topRight , bottomRight , FocusColor , FocuslineWidth));
}
3.2 获取像素颜色

Unity提供了一个方法UnityEngine.Texture2D.GetPixel(x,y),利用这个方法我们可以获取一个纹理上某个点的颜色值。

protected virtual void Update()
{   
    //吸取颜色
    if (WorkState == E_WorkState.Sucker)
    {
        // 截图显示在网格中,每帧刷新
        StartCoroutine(ScreenShot());
        if ( Input.GetMouseButtonDown(0) )
        {
            // 获取m_screenImage.sprite 纹理的中心像素的颜色
            // 获取的像素是网格的中心像素
            Color = m_screenImage.sprite.texture.GetPixel(m_imageMesh.XAxisCount / 2 + 1 , m_imageMesh.YAxisCount / 2 + 1);
            SetNoniusPositionByColor();
            WorkState = E_WorkState.Normal;
        }
    }
}

知道了如何获取图像上某一点像素颜色之后,我们来看看如何截图并显示

3.3 获取截图并在网格中显示

我们将Image组建和网格组建(IamgeMesh)放置在同一层级,并让后网格组建的Slibing大于Image组建,我们给Image赋值即可实现在网格中显示图片的效果。

// 按网格像素截屏并显示
private IEnumerator ScreenShot( )
{
    // 获取截图的尺寸 x , y 
    var xCount = m_imageMesh.XAxisCount;
    var yCount = m_imageMesh.YAxisCount;
    // 新建纹理,参数为尺寸,格式,是否带有mipmap
    m_texture = new Texture2D(xCount , yCount , TextureFormat.RGB24 , false);
    // 等待帧结束
    yield return new WaitForEndOfFrame();
    // 读取Rect范围内的像素并存入纹理中
    m_texture.ReadPixels(new Rect((int)Input.mousePosition.x - (int)( xCount / 2 ) ,
        (int)Input.mousePosition.y - (int)( yCount / 2 ) , xCount , yCount) , 0 , 0);
    // 实际应用纹理
    m_texture.Apply();
    // 赋值给Image组建
    m_screenImage.sprite = Sprite.Create(m_texture , new Rect(0 , 0 , xCount , yCount) , Vector2.zero);
}

通过以上重点讲解大家应该对颜色拾取器中核心的问题有所掌握,通过以上学习大家可以创建一个基础的颜色拾取器,但是如果要实现调色板不同的模式和不同颜色模式需要对颜色属性有个深度的了解,下一篇我们继续完善颜色拾取器,以完美复刻Unity自带颜色拾取器。下一篇我们重点讲解一下内容:

  • 如何实现不同模式的调色板
  • 如何实现不同的颜色模式
  • 如何在自己的工程中使用颜色拾取器工具
  • 如何使用颜色拾取器的接口

后续拓展

  • Unity中只提供这样一种颜色拾取器,用过Photoshop的同学知道ps中的颜色拾取器与unity中是不一样的,或者是一些视频剪辑软件中的颜色拾取器是环形的,我们通过这篇的学习,可以自己拓展出更多类型的颜色拾取器,有时间我也会奉上我的代码,以供学习参考
  • 在Untiy UGUI中绘制图形图像接口还是过于繁琐,必须自己写一个类继承Graphic或其子类,重新OnPopulateMesh来绘制图形,我们可以简化这一步骤以实现QT中的QPainter和QPen类似的功能,提供绘制同行更加简单的API,接下来,我们将实现这个功能

Unity自定义UI组件系列

Unity框架解读系列

分享地址

猜你喜欢

转载自blog.csdn.net/qq_29579137/article/details/74356097