65.9K
CodeProject 正在变化。 阅读更多。
Home

配色方案选择器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (49投票s)

2015年6月24日

CPOL

11分钟阅读

viewsIcon

47213

downloadIcon

1219

“世界上的色彩日复一日地改变着!” - 《悲惨世界》

引言

本文并非关于代码(稍后会有代码,但重要的是解释)。我将尝试阐述软件开发的另一个方面——设计……不是软件设计,而是 UI/UX 设计。我不会涵盖 UI/UX 的全部主题(如大小、位置、形状等),只关注为您的网站选择颜色……

注意:如果您问,为什么是网站而不是桌面(原生)应用程序,我的回答是……当你创建一个桌面(原生)应用程序时,你不会玩弄颜色,而是使用操作系统提供的颜色主题——由最终用户选择,因为你想让他们感到舒适,就像在家一样。

当你在一家大公司工作时,你永远不会触及这个话题,设计师会给你确切的 UI/UX 参数,包括颜色。但是!在小公司或你独自工作的情况下,了解如何选择能够和谐搭配的颜色非常重要……

 

这两个网站都充满了色彩,但恰当的选择和搭配它们是关键!

背景

我的哥哥是一位专业的平面设计师,他设计过多种语言的书籍(版式和封面)。多年来,他完成了数千个设计,他的观点是“美在于观者的眼中”。在我与他一起工作的这些年里,他教会了我许多他的小秘诀,但很明显,虽然我能够监督排版(现在大部分由计算机完成),但我完全没有能力处理颜色。

这时,我亲爱的哥哥透露,有数学方法可以用来选择颜色,而且这些方法甚至被设计师使用……我将解释这些方法,但在此之前,先简单介绍一下常用的不同颜色模型……

颜色模型

颜色模型是将现实世界的颜色抽象为由通常包含 3 或 4 个值的元组。

RGB 模型

人类视网膜(眼睛)包含三种视锥细胞(传感器),它们可以感知(看到)不同的波长,即长(**红**色)、中(**绿**色)和短(**蓝**色)。这种结构——早在电子设备时代之前就已为人所知——被用作 RGB 颜色模型的基础,该模型通过添加这三个分量来创建颜色,就像人眼一样。该模型被用于我们今天知道的所有屏幕设备——所有你的 PC、平板电脑和智能手机屏幕。你之所以熟悉它,是因为所有主要(以及大多数次要)的语言/框架都支持这种颜色模型……

然而,RGB 模型的工作方式并非人类理解颜色。例如,名为洋红色的颜色由 R:255、G:0、B:255 组成。通过增加绿色,人们应该期望看到更绿色的颜色,然而,随着绿色的不断增加,颜色却变得越来越像白色……

RGB 模型中的洋红色条有点奇怪。
红色和蓝色固定为 255,绿色从 0 到 255(从左到右)

 

RGB 模型表示为一个立方体。
(基于 SharkD - Wikimedia Commons 的作品)

HSV 模型

该颜色模型创建于 20 世纪 70 年代,专门为计算机图形的需求而设计。该模型仍然以加法方式使用 3 个分量,但重新组织了 RGB 颜色空间,使其更容易被人理解。它还类似于艺术家使用的经典色轮,这也使得使用该模型更容易选择颜色。

基本元素是**色相**(Hue),它围绕色轮旋转,0 度是红色,360 度也是红色。**饱和度**(Saturation)定义了颜色的纯度,0 是最纯净的,100 表示颜色饱和到变成暗淡的白色。**明度**(Value)定义了颜色的亮度,0 表示黑色(缺乏光线),100 表示当前颜色(色相)最浅的色调。

这个模型非常容易理解,因为改变一个分量,你就能看到预期的变化,而不是未知的颜色变化……因此,这个颜色模型几乎专门用于所有图形程序和所有设计师……

HSV 模型中的绿色条的显示方式易于人类理解。
色相固定为 120,明度固定为 100,饱和度从 100 到 0(从左到右)

HSV 模型表示为一个圆柱体。
(基于 SharkD - Wikimedia Commons 的作品)

Alpha 通道

**Alpha** 通道于 20 世纪 80 年代后半期引入计算机图形学。Alpha 通道表示颜色的透明度,100% 是完全不透明的颜色,0% 是完全透明的颜色。这个 A 元素可以应用于我们讨论的两种颜色模型……

注意:在接下来的颜色集中,我没有使用 Alpha 通道,它在这里只是为了完整性……

选择颜色集

基本思想是使用更少的颜色。环顾四周,你会发现大多数设计精良的网站使用的颜色都非常少(例如,HP 使用 4 种颜色——包括白色)。即使需要更多颜色,也是精心选择的。由于我们(至少大部分人)缺乏选择协调的颜色的天赋,我们可以使用这些基于设计师经验形成的算法……

所有这些公式都基于这样一个想法:你选择一种颜色,然后得到一套能够和谐搭配的颜色(选择的颜色是其中的一种)。

我现在将解释每个公式如何工作,以及它们各自的优缺点。

互补色

此方法创建一个包含两种颜色的集合,其中第二种颜色位于色轮的对面。最好选择冷暖色对(如橙色-蓝色或洋红色-绿色)以获得更好的对比度。无论如何,重要的是选择一种占主导地位的颜色。

优点:此方法可在颜色集中产生高对比度。

缺点:可能难以创建平衡的颜色集,尤其是在使用去饱和颜色作为基础时(去饱和颜色比普通颜色更灰/白)。

分裂互补色

此方法创建一个包含三种颜色的集合,其中另外两种颜色位于互补色的两侧(与互补色的实际距离可能是最终结果的重要因素)。与互补色方法一样,最好选择冷暖色对以获得更好的结果,并将距离保持在 15 到 45 度之间……

优点:此方法可在颜色集中产生高对比度,并比互补色方法提供更多细微差别(更多颜色!)。

缺点:可能难以创建平衡的颜色集,尤其是在使用去饱和颜色作为基础时。

三色组

此方法创建一个在色轮上均匀分布的三种颜色的集合。

优点:集合中的颜色非常和谐,但仍具有良好的对比度。

缺点:对比度低于互补色或分裂互补色。

四色组

 

此方法创建一个在色轮上均匀分布的四种颜色的集合。实际上,这是互补色的“双倍”,具有三色组的和谐性。

优点:集合中的颜色非常和谐,但每对颜色都具有互补色方法带来的对比度。

缺点:由于颜色数量较多,更难平衡。

五色组

此方法创建一个在色轮上均匀分布的五种颜色的集合。

优点:集合中的颜色非常和谐(如同四色组),但对比度低于之前的集合。

缺点:由于颜色数量较多,更难平衡。

邻近色

此方法创建一个包含三种颜色的集合,其中另外两种颜色紧邻原始颜色(与原始颜色的实际距离可以是最终结果的重要因素)。最好避免集合中使用冷暖色(不要选择位于冷暖色区域边界的颜色),并将距离保持在 15 到 45 度之间。

优点:这是一个丰富且易于创建的颜色集。

缺点:集合中对比度不高。

单色

此方法创建一个包含五种颜色的集合,其中新颜色是原始颜色的不同色调,通过改变颜色的饱和度来实现。这五种颜色在饱和度轴上均匀分布(在色轮上从内到外)。

优点:非常容易创建平衡且和谐的集合。

缺点:缺乏任何颜色对比度。

组合

此方法创建一个包含 25 种颜色的集合。这些颜色是通过依次应用五色组方法和单色方法组合而成的。该方法结合了单色的平衡性和五色组的对比度。

优点:集合在主颜色和子集之间都非常和谐。可用于大型网站,通过颜色区分网站的不同主题。

缺点:由于颜色数量较多,更难平衡。

色盲

2% 到 8% 的人患有某种形式的色盲。色盲是由眼睛中缺失或功能失调的一种或多种视锥细胞引起的。患有此病症的人可能无法区分颜色,会混淆颜色,或者只能看到相同颜色的不同深浅。

对于这些人来说,仅靠颜色信息是不够的——在设计网站时,将颜色信息与形状、图标和/或文本配对,以提供更好的用户体验……

左侧是正常视力者看到的色轮,
右侧是患有**失绿型色觉异常**(最常见的色盲形式)的人看到的景象……

全色盲

为盲人设计网站远远超出了本文的范围(我们谈论的是颜色——盲人没有颜色!)。如果您接到一个要求为盲人设计项目的任务,您需要找到该领域的专家(即使在设计师中这也是一项专业知识)。

代码部分

我曾承诺不写代码,但毕竟这是一个编程网站……

在这一部分,我将展示我们所见过的想法的一种可能实现的 Kern。我在这里展示的示例代码**不是**附带在文章中的代码!附加的代码是一个 Visual Studio 扩展,它允许您创建上述颜色集并将单个颜色插入到您的代码中……这里的代码仅仅是一个示例,它只显示了与本文相关的部分(如何在 WPF 中创建颜色调色板不是我们的主题 :-))

RGB 与 HSV 相互转换

如前所述,RGB 颜色模型几乎被所有语言/框架支持。在 .NET 中,我们有 `Color` 类,它基于 RGB 模型表示颜色,问题在于颜色集是围绕 HSV 色轮计算的,因此我们必须能够在这两个模型之间进行双向转换。

当然,这些计算不是商业秘密,可以在网上找到……

RGB 转 HSV

$ R'=R\div255\\ G'=G\div255\\ B'=B\div255\\~\\ C_{max}=\max(R',G',B')\\ C_{min}=\min(R',G',B')\\ \Delta=C_{max}-C_{min}\\~\\ H=\left\{\begin{matrix} 0^{\circ},&\Delta=0\\ 60^{\circ}\times\left(\frac{G'-B'}{\Delta}\mod6\right),&C_{max}=R'\\ 60^{\circ}\times\left(\frac{B'-R'}{\Delta}+2\right),&C_{max}=G'\\ 60^{\circ}\times\left(\frac{R'-G'}{\Delta}+4\right),&C_{max}=B' \end{matrix}\right.\\~\\ S=\left\{\begin{matrix} 0,&C_{max}=0\\ \frac{\Delta}{C_{max}},&C_{max}\neq0 \end{matrix}\right.\\~\\ V=C_{max} $

HSV 转 RGB

$ H'=H\\ S'=S\div100\\ V'=V\div100\\~\\ C=V'\times S'\\ X=C\times\left(1-\left|\left(\frac{H'}{60^{\circ}}\right)\mod2-1\right|\right)\\ m=V'-C\\~\\ \left(R',G',B'\right)=\left\{\begin{matrix} \left(C,X,0\right),&0^{\circ}\leq H'<60^{\circ}\\ \left(X,C,0\right),&60^{\circ}\leq H'<120^{\circ}\\ \left(0,C,X\right),&120^{\circ}\leq H'<180^{\circ}\\ \left(0,X,C\right),&180^{\circ}\leq H'<240^{\circ}\\ \left(X,0,C\right),&240^{\circ}\leq H'<300^{\circ}\\ \left(C,0,X\right),&300^{\circ}\leq H'<360^{\circ} \end{matrix}\right.\\~\\ R=\left(R'+m\right)\times255\\ G=\left(G'+m\right)\times255\\ B=\left(B'+m\right)\times255 $

以及这些公式在 C# 中的一种可能实现

public class ColorEx
{
    public static short MaxHue = 360;
    public static short MaxSaturation = 100;

    private bool _IsRGBDirty = false;
    private bool _IsHSVDirty = false;

    public ColorEx ( )
    {
        RGB2HSV( );
    }

    public ColorEx Clone ( )
    {
        return ( new ColorEx( )
        {
            R = R,
            G = G,
            B = B
        } );
    }

    #region Properties

    private Color _Color = new Color( )
    {
        A = 255,
        R = 128,
        G = 128,
        B = 128
    };

    public Color Color
    {
        get
        {
            if ( _IsRGBDirty )
            {
                HSV2RGB( );
            }

            return ( _Color );
        }
    }

    public byte R
    {
        get
        {
            if ( _IsRGBDirty )
            {
                HSV2RGB( );
            }

            return ( _Color.R );
        }
        set
        {
            _Color.R = value;

            _IsHSVDirty = true;
        }
    }

    public byte G
    {
        get
        {
            if ( _IsRGBDirty )
            {
                HSV2RGB( );
            }

            return ( _Color.G );
        }
        set
        {
            _Color.G = value;

            _IsHSVDirty = true;
        }
    }

    public byte B
    {
        get
        {
            if ( _IsRGBDirty )
            {
                HSV2RGB( );
            }

            return ( _Color.B );
        }
        set
        {
            _Color.B = value;

            _IsHSVDirty = true;
        }
    }

    private short _H = 0;

    public short H
    {
        get
        {
            if ( _IsHSVDirty )
            {
                RGB2HSV( );
            }

            return ( _H );
        }
        set
        {
            // Hue is circular (degree)
            _H = ( short )( ( value < 0 ? 360 : 0 ) + ( value % 360 ) );

            _IsRGBDirty = true;
        }
    }

    private byte _S = 0;

    public byte S
    {
        get
        {
            if ( _IsHSVDirty )
            {
                RGB2HSV( );
            }

            return ( _S );
        }
        set
        {
            if ( value >= 0 && value <= 100 )
            {
                _S = value;

                _IsRGBDirty = true;
            }
        }
    }

    private byte _V = 0;

    public byte V
    {
        get
        {
            if ( _IsHSVDirty )
            {
                RGB2HSV( );
            }

            return ( _V );
        }
        set
        {
            if ( value >= 0 && value <= 100 )
            {
                _V = value;

                _IsRGBDirty = true;
            }
        }
    }

    #endregion

    #region Helpers

    private void RGB2HSV ( )
    {
        double nR = _Color.R / ( double )255;
        double nG = _Color.G / ( double )255;
        double nB = _Color.B / ( double )255;
        double nCmax = Math.Max( nR, Math.Max( nG, nB ) );
        double nCmin = Math.Min( nR, Math.Min( nG, nB ) );
        double nDelta = nCmax - nCmin;
        double nH = 0;
        double nS = 0;
        double nV = nCmax;

        if ( nDelta != 0 )
        {
            if ( nCmax == nR )
            {
                nH = ( ( nG - nB ) / nDelta ) % 6.0;
            }
            else if ( nCmax == nG )
            {
                nH = ( ( nB - nR ) / nDelta ) + 2.0;
            }
            else if ( nCmax == nB )
            {
                nH = ( ( nR - nG ) / nDelta ) + 4.0;
            }
        }

        nH *= 60.0;

        if ( nH < 0 )
        {
            nH += 360;
        }

        if ( nDelta != 0 )
        {
            nS = nDelta / nCmax;
        }

        nS *= ( double )100;
        nV *= ( double )100;

        _H = ( short )( nH );
        _S = ( byte )( nS );
        _V = ( byte )( nV );

        _IsHSVDirty = false;
    }

    private void HSV2RGB ( )
    {
        double nS = _S / ( double )100;
        double nV = _V / ( double )100;
        double nDelta = nV * nS;
        double nH = _H / 60.0;
        double nX = nDelta * ( 1 - Math.Abs( ( nH % 2 ) - 1 ) );
        double nM = nV - nDelta;
        double nR = 0;
        double nG = 0;
        double nB = 0;

        if ( nH >= 0 && nH < 1 )
        {
            nR = nDelta;
            nG = nX;
        }
        else if ( nH >= 1 && nH < 2 )
        {
            nR = nX;
            nG = nDelta;
        }
        else if ( nH >= 2 && nH < 3 )
        {
            nG = nDelta;
            nB = nX;
        }
        else if ( nH >= 3 && nH < 4 )
        {
            nG = nX;
            nB = nDelta;
        }
        else if ( nH >= 4 && nH < 5 )
        {
            nR = nX;
            nB = nDelta;
        }
        else
        {
            nR = nDelta;
            nB = nX;
        }

        nR += nM;
        nG += nM;
        nB += nM;

        nR *= 255;
        nG *= 255;
        nB *= 266;

        _Color.R = ( byte )( nR );
        _Color.G = ( byte )( nG );
        _Color.B = ( byte )( nB );

        _IsRGBDirty = false;
    }

    #endregion
}

此代码创建了一个新的颜色类,它同时支持 RGB 和 HSV,并且始终保持同步……你在这段代码中最需要注意的一点是 H 是循环的,因为它以度为单位测量,所以 361 和 1 是相同的……

计算颜色集

三色组

除单色外,所有颜色集都通过围绕当前色轮移动原始颜色来创建集合,所以首先让我们看一个示例……

private void Triads ( ColorEx BaseColor )
{
    ColorEx oTriad1 = BaseColor.Clone( );
    ColorEx oTriad2 = BaseColor.Clone( );

    oTriad1.H += 120;
    oTriad2.H -= 120;

    // ...
}

由于三色组用于创建在色轮上均匀分布的 3 种颜色,我们使用 120° 的值(\(360^{\circ}\div 3 = 120^{\circ}\)),但所有其他色相的操纵都做同样的事情,只是计算的值根据集合而定……

单色

唯一通过操纵原始颜色的饱和度来创建颜色的集合……

private void Monochromatics ( ColorEx BaseColor )
{
    byte nGap = 20;
    byte nS = BaseColor.S % nGap;

    ColorEx oMonochromatic0 = new ColorEx( ) { H = BaseColor.H, S = nS, V = BaseColor.V    };
    ColorEx oMonochromatic1 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + nGap ), V = BaseColor.V };
    ColorEx oMonochromatic2 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + ( 2 * nGap ) ), V = BaseColor.V };
    ColorEx oMonochromatic3 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + ( 3 * nGap ) ), V = BaseColor.V };
    ColorEx oMonochromatic4 = new ColorEx( ) { H = BaseColor.H, S = ( byte )( nS + ( 4 * nGap ) ), V = BaseColor.V };

    // ...
}

这些仅仅是关于如何操纵颜色值以获得集合的示例——在附加代码中,您将找到创建本文中所有颜色集的具体代码……

摘要

如果您想作为一名 Web 开发人员交付高质量的工作,那么您的代码能够正常运行且编写良好是远远不够的——您还必须为您的网站提供完美的视觉效果和感觉。如果您是独自一人,刚开始您的职业生涯,那么为小型网站聘请专业设计师可能会非常昂贵。本文可以帮助您选择正确的颜色……

简而言之

所以。您已经确信了颜色的重要性,但仍然不想弄乱代码!

您可以下载并安装基于本文的 Visual Studio 扩展——它是免费的!

© . All rights reserved.