查看原文
其他

写给设计师的 OF 编程指南9 - 色彩控制

2016-08-09 Wenzy InsLab

前面的许多篇幅都提到了如何用代码进行造型,但色彩相关的知识点谈得并不多。这节我们就来深入挖掘一下。

色彩基本知识

关于色彩,它在某方面是超出人类直觉的。我们肉眼所能看到的五彩斑斓的颜色,它们背后的组成元素其实都是相同的。只需要红绿蓝三种色光,就能混合出肉眼可见的所有色彩。

你现在所看到的手机、电脑屏幕,都是基于这个原理而制造出来的。红、绿、蓝被称作色光三原色。通过三者的成分比例就能确定某个色彩。这种描述方式也被叫做 RGB 模式。其中红光(red)对应 R,绿光(green)对应 G,蓝光(blue)对应 B。

除了 RGB 模式外,还有一种模式被称为 CMYK 模式,它常常与印刷打交道。印刷中也有三原色,只是与色光中的三原色有所不同。它们分别是红、黄、蓝。其中 C 对应蓝(青),M 对应红(品红),Y 对应黄。理论上只通过 CMY 就可以混合出绝大多数的颜色,但由于原料的生产工艺,使得 CMY 的颜色纯度很难达到 100 %,所以三者混合起来并不能得到足够深的黑色。于是才多了一个 K , 它对应黑色油墨,作为印刷的补充。

对于 RGB 和 CMYK ,你只要理解他们之间一个最显著的性质差异即可。RGB 是加色模式,越“混”越亮。CMYK 是减色模式,越“混”越暗。下图中可以直观看到两种模式的异同。左图可以想象成是在一个漆黑的屋子打上了三种不同颜色的手电筒。而右图则可以看作是在水彩纸上,用红绿蓝三种颜料涂色后的叠加效果。


若你想更深入地了解不同色彩模式之间的对应关系,可以打开 PS 上的拾色器,上面可以直观地看到同种颜色在不同模式下的数值呈现。


最后再介绍一种常用的色彩模式-HSB。HSB 没有“原色”的概念,它是基于人眼对色彩的感受来进行划分的。H 表示色相(hues),S 表示饱和度(saturation),B 表示亮度(brightness)。

色相,表示的是色彩倾向。只要不是黑白灰,任何色彩都有某种固定的色彩指向。拾色器上颜色过渡最丰富的色带就是用来表示色相。在 PS 中它的数值范围为 0 到 360。

饱和度,表示的是色彩的纯度。纯度越高,色彩越鲜艳。范围从 0 到 100。

亮度,表示的是色彩的明亮程度。范围从 0 到 100。

相比 RGB 模式,HSB 的三个维度更符合人眼对色彩的感受。只看 HSB 的数值,你大致可以想象出是怎样的色彩。


对于同一个色彩,RGB 上的数值为(255,153,71),HSB 的数值为 (27,72,100)。
单看 RGB 会很难判断三原色混起来是什么样子的。HSB 则不一样,你只要稍微熟悉具体色彩的色相值。如红为 0,橙为 30,黄为 60。就能知道当 H 为 27 时,这会是一个略微偏红的橙色,较饱和,颜色很亮。

下面将两种模式的三个维度对应成空间中的 x,y,z。绘制成一个色彩立方来进行对比



RGB 和 HSB 只是描述色彩的不同方式。可以用地址作个比喻。假如要告诉别人故宫的位置,你可以说是在北京市东城区景山前街4号,也可以说在北纬 39 度 55 分 15 秒, 东经 116 度23 分 26 秒 。HSB 的描述方式有点像前者,如果你对相关的区域有所了解,能大致明白地点的方位。而RGB 尽管精确,但非常抽象。

HSB 模式是为了方便人去描述色彩才存在的。在屏幕上要显示某个色彩,最终还是需要转换先转成 RGB 模式。

以上介绍了 RGB,HSB,CMYK 三种色彩模式。在程序中,你只需要关注 RGB 和 HSB 这即可。它们各有优点同时又有自身的使用场景。熟悉它就能满足绝大多数的创作需求。

储存色彩的数据类型

在程序上表示色彩,我们之前更多使用的是 RGB 模式。但仅仅通过控制这三个属性,就能表示任何色彩吗?在计算机当中确实如此。

前面有提及,在 Openframeworks 中,除了 R,G,B 之外。我们还可以给色彩指定一个透明度(alpha)。但 Alpha 不属于色彩的构成要素,它的存在只是方便和背后的色彩进行混合。所以对计算机而言,要准确描述某个色彩,只需要控制好关键的 3 个变量。

下面开始介绍一种专门用于储存色彩的数据类型 - ofColor。
它接近于提到的 bool ,int,float 这类数据类型。

这里先不说明ofColor的具体用法。设想一下,假如只能用之前掌握方法来储存某个数据,应该怎么做?

代码示例(9-1):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        int r,g,b; };

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(400,400);    r = 255;    g = 0;    b = 0; } void ofApp::update(){ } void ofApp::draw(){    ofBackground(0);    ofSetRectMode(OF_RECTMODE_CENTER);    ofSetColor(r,g,b);    ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,100,100); }


对于有色彩倾向的颜色,就需要分别创建三个整形变量,来保存色彩红绿蓝三个通道的数据。之后想要调用这组色彩数据,就得把它们写在 ofSetColor 中。

但你会发现这么做会非常麻烦,数据本来是彼此关联的,如果有办法把它们打包起来使用菜会更方便。所以就有了 ofColor。

代码示例(9-2):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofColor myColor; };

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(400,400);    myColor = ofColor(255,0,0); } void ofApp::update(){ } void ofApp::draw(){    ofBackground(0);    ofSetRectMode(OF_RECTMODE_CENTER);    ofSetColor(myColor);    ofDrawRectangle(ofGetWidth()/2,ofGetHeight()/2,100,100); }
  • 与 int 这类数据类型一样,需要先在开头用“ ofColor myColor ”来创建变量

  • setup 中再通过 myColor = ofColor(255,0,0) 来对 myColor 变量进行赋值。 而函数 ofColor(a,b,c) 就表明这一组数字构成了一个 ofColor 类型,才得以传入变量 myColor。写成 myColor = (255,0,0) 会报错。

  • 最后通过 ofSetColor() 来实现色彩设置的操作。ofSetColor() 是一个可重载的函数,根据传入参数的个数和类型会有不同的效果。只传入一个整形变量则代表这是一个只有灰度的色彩,而传入一个 ofColor 变量则表示的色彩范围会更大。你也可以传入一个 ofColor 变量和一个整形变量,将上例中的 ofSetColor() 函数改写成 ofSetColor(myColor,150) ,就能通过第二个数值控制透明度


ofSetColor 的重载方式一览

函数参数含义
ofSetColor(int a)a 表示灰度值,fill(0) 为黑色,fill(255) 为白色
ofSetColor(int r,int g,int b)r,g,b 分别代表红绿蓝三个通道的数值
ofSetColor(int r,int g,int b,int a)r,g,b 分别代表红绿蓝三个通道的数值,a 代表透明度
ofSetColor(ofColor c)c 代表传入的色彩
ofSetColor(ofColor c,int a)c 代表传入的色彩,a 代表透明度


读取 ofColor 中的通道值

除了可以赋值之外,还可以独立地获取 color 变量中的 RGB 数值

代码示例(9-3):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofColor myColor; };

—- ofApp.cpp 内

void ofApp::setup(){    myColor = ofColor(255,125,0);    int r = myColor.r;    int g = myColor.g;    int b = myColor.b;    cout << r << endl;    cout << g << endl;    cout << b << endl; }

控制台输出结果:255,125,0

  • myColor 后加上“.r” ,可获取 myColor 中红通道的数值。在程序中,“.”往往代表父子关系。myColor.r 可以从字面上解读为“ myColor 中的变量 r ”。myColor 中的 r 用程序员的说法就是指成员变量。

  • “.g”,”.b” 则分别获取 myColor 中绿、蓝通道的数值

  • myColor.r 不仅可以获取红通道中的数值,它还能直接赋值

例如

void ofApp::setup(){    myColor = ofColor(255,125,0); }

等价于

void ofApp::setup(){    myColor.r = 255;    myColor.g = 125;    myColor.b = 0; }

十六进制赋值

除了用 10 进制的数字来表示 RGB,还可以用 16 进制。10 进制指的是逢 10 进 1,16 则是逢 16 进 1。它与 10 进制的对应关系是:0 到 9对应 0 到 9;A 到 F 对应10 到 15 。

下图说明了换算方法


当然,如果我们拿到了一组 16 进制的数值,如 ff7800。是不需要用手动的方式去换算。程序中可以直接对色彩变量赋值,非常方便。

网络上所能看到的很多色卡,都采取 16 进制的方式来表示色彩


  • 如设计社区 dribbble,作品都会附带色板( color palette ),如果看到喜欢的配色方案,就能够把它运用到程序之中

代码示例(9-4):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofColor backColor,colorA,colorB,colorC; };

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(400,400);    ofSetRectMode(OF_RECTMODE_CENTER);    backColor = ofColor::fromHex(0x395b71);    colorA = ofColor::fromHex(0xc4d7fb);    colorB.setHex(0xf4a7b4);    colorC.setHex(0xf9e5f0); } void ofApp::update(){ } void ofApp::draw(){    ofBackground(backColor);    ofSetColor(colorA);    ofDrawRectangle(200,200,90,300);    ofSetColor(colorB);    ofDrawRectangle(100,200,90,300);    ofSetColor(colorC);    ofDrawRectangle(300,200,90,300); }


  • 现在的配色就舒服多了,会比随意输入数值得到效果要好

  • 上例中展示了两种赋值方法。一种的写法是 ofColor::fromHex(),里面只要填上 16 进制的色值并在开头加上 0x 即可。开头写上 0x,在 C++ 中就表明这是一个 16 进制的数字

  • 另一种写法是” 色彩变量名.setHex() “,它的含义是调用色彩变量中的成员函数 setHex()。成员函数这名字听上去和上面提到的成员变量有点接近。但它本质上是一个“函数”,不能像变量一样进行赋值。setHex 只是作为 ofColor 类型的“儿子”(成员),被包含在里头。括号中只要填上 16 进制的色值并在开头加上 0x 即可

HSB 模式

除了 RGB 模式,接着就是要介绍 HSB 模式了。下面将展示它的赋值方法。

代码示例(9-5):

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(400,400); } void ofApp::update(){ } void ofApp::draw(){    ofBackground(0);    ofSetRectMode(OF_RECTMODE_CENTER);    for(int i = 0;i < 20;i++){        ofColor col = ofColor::fromHsb(i/20.0 * 255, 255, 255);        ofSetColor(col);        ofDrawRectangle(i * 20 + 10, ofGetHeight()/2, 10, 300);    } }


  • 在 OF 中若希望用 HSB 模式来设置颜色,可通过 ofColor::fromHsb() 来进行赋值

  • 值得注意的是,OF 中默认 H,S,B 的最大值为 255。这与 Photoshop 中 HSB 的最大值有所不同。Photoshop 中 H 的最大值为 360,S 和 B 的最大值为 100。所以需要进行换算


  • 若 Photoshop 中的 HSB 值为(55,100,100)则换算到 OF 中,数值应为 (55 / 360 × 255,255,255 ),即 (40,255,255)。

HSB 模式的应用实例1

因为 RGB 模式不好控制色相的变化。这时候若是希望更灵活地控制色彩,HSB 模式就能派上用场。

代码示例(9-6):

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(800,800);    ofSetBackgroundAuto(false);    ofBackground(0); } void ofApp::update(){ } void ofApp::draw(){    ofSetColor(ofColor::fromHsb(int(ofGetElapsedTimef() * 10) % 255,255,255));    ofSetLineWidth(2);    ofDrawLine(mouseX,mouseY,               mouseX + ofSignedNoise(ofGetElapsedTimef() + 1.2) * 400,mouseY + ofSignedNoise(ofGetElapsedTimef()) * 400); }
  • 控制色相 H 时,用到了 ofGetElapsedTimef(),它会获取程序从开始到现在的运行时间,因此随着时间推移,色相 H 的值便会自动增大,颜色就会发生改变

  • ofSignedNoise() 与 ofNoise() 类似,区别是返回值的范围会在 -1 到 1 之间变化

  • 因为我们希望色彩会呈周期性循环。所以最后在 ofSetColor 函数填写的第一个参数里,进行取模运算。这样可以保证当色相 H 超过 255 时,又能从零开始。

  • ofSetLineWidth() 函数可以控制线条的粗细,里面参数填写的数值,对应的单位为像素


HSB 模式的应用实例2

代码示例(9-7):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        int num;  // 当前已绘制的线条数量        float posX_A,posY_A; // 点 A 的横纵坐标        float posX_B,posY_B; // 点 B 的横纵坐标        float angleA, speedA; // 点 A 的角度,速度        float angleB, speedB; // 点 B 的角度,速度        float radiusX_A, radiusY_A; // 点 A 形成的椭圆在 X(Y)轴方向上的半径        float radiusX_B, radiusY_B; // 点 A 形成的椭圆在 X(Y) 轴方向上的半径 };

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(800,800);    ofSetBackgroundAuto(false);    ofBackground(0);    speedA = 0.0009;    speedB = 0.003;    radiusX_A = 300;    radiusY_A = 200;    radiusX_B = 200;    radiusY_B = 300; } void ofApp::update(){ } void ofApp::draw(){    ofTranslate(ofGetWidth()/2, ofGetHeight()/2);    for (int i = 0; i < 50; i++) {        angleA += speedA;        angleB += speedB;        posX_A = cos(angleA) * radiusX_A;        posY_A = sin(angleA) * radiusY_A;        posX_B = cos(angleB) * radiusX_B;        posY_B = sin(angleB) * radiusY_B;        ofSetColor(ofColor::fromHsb(int(num/500.0) % 255, 255, 255, 10));        ofDrawLine(posX_A, posY_A, posX_B, posY_B);        num++;    } }

运行效果:


输出图片:


  • 你所看到的图案,是由一条运动的直线不断叠加而产生的。直线上两个端点的运动轨迹,分别是两个圆。

  • 通过 HSB 模式控制了色相的变化,随着绘制线条的增多,色相会进行偏移。当大量的半透明直线叠加起来,就能产生非常丰富的色彩过渡。

  • draw 函数中嵌入了一个 for 循环。目的是可以通过 for 循环来控制在一帧当中绘制的线条数量,它相当于控制了绘制速度。增大 for 循环中判定条件的数值可以加速。

下面是原理图,你可以更清楚地看到圆的运动轨迹


调节不同速度以及半径,生成的图形也会有所不同。试着改变 angle,speed,radiusX,radiusY 这些变量看看。



图层混合模式

前面提到的各种色彩模式都用于给图形元件进行着色。除了通过这种方式来控制色彩,Openframeworks 中还能像 Photoshop 一样,使用各种图层图层混合模式。

打开 PS 中的图层窗口,点选图层的混合模式,就可以看到这些选项


这些都是 PS 已有的图层模式。简单地说,混合模式可以理解成是一种颜色的计算模式。它会决定 “颜色A” 加上 “颜色B” 最终得出什么颜色。这里的“颜色A”指的是当前图层背后的颜色(又称基色),“颜色B”指当前图层的颜色(又称混合色)。程序会根据颜色 A,B 的 RGB 数值以及透明度,来计算得到颜色C。它会作为结果色,并显示到屏幕上。

不同的图层模式,就代表不同的计算方式。在此书的下半部分,会进行详细解释,这节先了解用法即可。

先看一个在程序中使用增加模式的示例

代码示例(9-8):

—- ofApp.h 内

#include "ofMain.h" class ofApp : public ofBaseApp{    public:        void setup();        void update();        void draw();        ...        ofImage image1,image2; };

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(800,400);    image1.load("1.jpg");    image2.load("2.jpg"); } void ofApp::draw(){    ofBackground(0);    ofSetColor(255);    ofEnableBlendMode(OF_BLENDMODE_ADD);    image1.draw(0,0,400,400);    image2.draw(mouseX,mouseY,400,400); }


  • ofEnableBlendMode() 函数用于设置图形的混合模式,后面填写 OF_BLENDMODE_ADD 就代表设置成增加模式。

  • 程序中没有图层的概念,但因为图形元件的绘制是有先后之分的。所以图片在混合时,image1 就作为基色,image2 就作为混合色。

  • ADD 模式属于“变亮系”,使用它以后会有提亮效果

下面是 Openframeworks 中可使用的混合模式

Openframeworks 混合模式一览

模式含义
OF_BLENDMODE_ALPHA正常模式(默认模式)
OF_BLENDMODE_ADD增加模式
OF_BLENDMODE_DISABLED减去模式
OF_BLENDMODE_MULTIPLY正片叠底模式
OF_BLENDMODE_SCREEN滤色模式
OF_BLENDMODE_SUBTRACT减去模式

可以试着改变不同的混合模式来观察效果。当示例(9-8)使用正片叠底模式后(需要将背景色设置成白色)


使用减去模式后(需要将背景色设置成白色)


图层混合模式应用实例

混合模式不仅仅可以用于图片,它对画布上的所有图形元素同样适用。下面展示一个关于增加模式的用法,可用于模拟各种发光效果。

代码示例(9-9):

—- ofApp.cpp 内

void ofApp::setup(){    ofSetWindowShape(400,400); } void ofApp::draw(){    ofBackground(0);    ofEnableBlendMode(OF_BLENDMODE_ADD);    int num = 3000 * mouseX/400.0;    for(int i = 0;i < num;i++){        if(ofRandom(1) < 0.5){            ofSetColor(0,50,0);        }else{            ofSetColor(50);        }        ofDrawCircle(ofRandom(50,ofGetWidth() - 50),ofRandom(50,ofGetHeight() - 50),10);    } }


  • 这里通过 ofRandom 函数,在粒子中混入了带透明度的绿色和白色。通过鼠标可以控制圆的数量,由此观察叠加效果

  • OF_BLENDMODE_ADD 与 OF_BLENDMODE_SCREEN 模式很类似,同样都是变亮,但会有微妙差别。你也可以替换成 OF_BLENDMODE_SCREEN 进行对比。OF_BLENDMODE_ADD 经过叠加,纯度和明度会更高,适合模拟发光效果。

关于色彩,这节就介绍到这里。对于这门“语言”,你已经掌握足够多的词汇了。快用代码在形色的世界中遨游吧~



Openframeworks 系列文章

[1][2][3][4]

[5][6][7][8]

资源索引

CreativeCoding学习资源索引

Twitter资源索引


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存