色彩空间的编程
假设有这么一个问题,一个油气田公司的报表,想表示在过去的一年里,十二个月中每个月的原油产量和天然气产量的趋势是什么样的,怎么用一个简单的图表表示?大家可能会立刻想到,把每个月的产量在图表上标一个点,然后把这些点连起来,就成了趋势线,原油产量连成一条线,天然气产量连成另一条线。那么如何区分哪条线是原油,哪条线是天然气呢,答案也是很容易想到的,就是用红颜色的线表示原油,蓝颜色的线表示天然气。或者说红颜色的线表示原油,绿颜色的线表示天然气。到目前为止,用户和程序员想的都是一样的。
这时候,用户说了,我想用黄色的线表示原油,用紫色的线表示天然气,行不行?程序员稍稍迟疑了一下,行是行,只不过不如红、绿、蓝三种颜色来得方便。有一点色彩方面知识的人都知道,红、绿、蓝这三种颜色是最纯净的三种色,非常悦眼,称为三原色,其它的颜色都是由这几种颜色混和成的。从程序员的角度来看,产生所有颜色只要用RGB(r, g, b)这个函数就行,这里r、g、b这三个参数表示这三种色的量,最大为255,最小为0,比如想产生红色,只要用RGB(255,0,0)就行了,同理,绿色就是RGB(0,255,0),蓝色就是RGB(0,0,255),那么黄色是怎么出来的呢,本人在过去很长一段时间内都不知道,直到会用RGB函数以后才试出来的,是RGB(255,255,0),也就是红色和绿色一混合就出来了。同样紫色也是,得试很多次才能试出来,当然,色彩学得比较好的不用试也知道。这就是前面的用户说要画黄色线和紫色线时程序员比较迟疑的原因,毕竟不是三原色之一,还得试出来。
在程序员纠结画什么颜色好时,用户又说了,我想把成品油的产量也加上去,颜色随你定。程序员这才放心了,三条线正好使用三原色,正合适。当程序员正想准备实施编码时,用户又想起什么事来,对程序员说,慢着!你能不能把成品油分类,像重油、轻油之类的,还有石蜡这样的副产品也加上去。程序员稍稍想了一下,数了数:原油、天然气、成品油、重油、轻油、石蜡。共6种,三原色占去三种、三原色中两两混合又是三种,也还合适。这时候用户跑去喝咖啡去了,临走时对程序员说:你先假设几个数,把图表画出来看看效果。程序员则继续编码,不一会儿,图表出来了,图表中的线条花花绿绿的非常好看,连程序员自己都很陶醉,等用户过来检查成果。
用户喝完咖啡过来了,看着五颜色六色的图表也觉得很不错,他忽然想起了一个问题,又对程序员说,效果很不错,嗯......不过,我想把成品油再细分一下,不要重油、轻油了,加上汽油、煤油、柴油、石蜡和沥青,天然气......也细分一下吧,甲烷和乙炔,先暂时分这两种,以后可能分得更细......噢,要不然把所有的这些成分做在界面上,可以选择的,我选哪个,就画哪个,全选上就全都画......用户喝了咖啡,好像补充了能量,一下子想出了很多需求,滔滔不绝地向程序员表述了出来。程序员一开始还频频点头,一边点头一边说行、没问题之类的,到最后,却陷入了沉思之中。
我想大家都猜到了程序员在想什么,如果只是规定好了要画几条线,哪怕是再多的线也没问题,大不了一种颜色一种颜色地试,我们可以在Windows带的画图板中一种一种地试,哪种颜色好看就用哪个,只要任何两种颜色之间不重复就行。当用户说完最后一条需求,程序员纠结了。因为颜色不能再定死了,用户选择了几种,程序员就得画上几条线,线条的数量都是不固定的,程序员怎么能再试完再画呢?
这就出现了这么一个问题,如何生成指定数量的不同的颜色,另外加一个要求,颜色要尽量好看。也就是说生成的颜色要符合两个要求,首要是区分开,其次是好看。
程序员起先是这么想的,把RGB三原色的量分成几份,每次加一份。比如要画条8条不同的线,那么,就把255分成8份,第一条线是RGB(0,0,0),第二条线是RGB(255/8,255/8,255/8),这里面的三个参数都要取整。第三条线就是(255*2/8,255*2/8,255*2/8),以此类推。这样实现起来是相当简单,可实现效果呢,等程序员一画出来,就傻眼了,画出来的线既不符合第一种要求,也不符合第二条要求。第一条是黑色的,后面的依次灰度下降,全都是灰灰的线条,完全没有一点美感可言,而且相邻的两条线几乎分不出来,这个算法完全失败了。
程序员在第一种算法的基础上,吸取教训,产生了第二种算法,第二种算法是这样的,比如要生成9种线,那么在色彩的每一个分量上都分别累加,前三个色彩在红色分量上区分,如第一种颜色为RGB(255/3,0,0),第二种:RGB(255*2/3,0,0),第三种(255,0,0),中间三种色彩在绿色分量上区分,后三种色彩在蓝色分量上区分。这种算法在结果上与前一种算法相比有了长足的进步,相比之下美观多了,而且在量小的情况下也容易互相区分出来。但其缺点也是明显的,只有三种基本颜色,全靠其灰度来区分,当数量稍大时,两种颜色很容易辨别不出来。
以上两种算法犯的毛病有一个共同点就是没有充分利用色彩空间。何为色彩空间?就是将三个原色作为三个坐标轴,形成的一个正立方体,由此可见,原点的颜色为RGB(0,0,0),即为黑色,而与原点相对的那个点的颜色为RGB(255,255,255),即为白色。第一种算法产生的颜色都集中在从黑到白的这条直线上,自然就都是灰色的了。第二种算法都集中在三个坐标轴上,所以可选择的颜色相当局促。那么我们最终要做的就是充分利用这个色彩空间。
程序员经过长久的思考,想出了以下的方法,为了简单起见,不排除产生灰色的线,但要排除白色,因为在图表上显示出不出来,对于给定的数,把这个数均匀地分配到色彩空间中,比如对于9这个数,我们把红色分为2份,这就能产生3个不同的量,0,255/2,255,把绿色分为一份,可以产生2个不同的量,0和255,把蓝色分为一份,可以产生2个不同的量,0和255,把这其中的每一种做组合,就会产生12种组合(3*2*2),再去掉白色,还有11种,最后两种放弃不用,完全可行。这样,我们就充分利用了色彩空间。
有人问了,你这红色分2份,绿色分1份,蓝色分1份,这份数是怎么出来的?这个问题正是算法最关键的地方,如何均分色彩空间。我们力求均匀,也就是我们尽量让每个分量分的份数一样,比如都分2份,但是份数一样也会产生浪费,比如产生9种颜色,每个分量都分一份,产生2*2*2种颜色,不够,都分两份,就会产生3*3*3种颜色,产生27种,大部分用不着,太浪费了。于是我们把每个分量一点一点地往上加,比如都分一份不行,那么把其中一个分量加1,也就是红色分成2份,这下就够了。如何找到这刚好不够用的每份都分为1份呢,那就只好对所要求的颜色数量进行开立方,比如9开立方就是2点几,再取整,就是2,2的话就是分成一份,3的话就是分成2份,以此类推。我们在均分颜色空间时,就从这个开立方取整的数开始找,第一个分量加1看看够不够,不够再把第二个分量加1,最差不过把三个分量都加1。
上面的这个算法是要花费一番工夫来编码实现的,不过相对于实现之后的成就感,这点辛苦就不值得一提了。任何一个成功的算法都是经过反复试算观察结果最终得到的,改进不合理的算法,形成自己的思路,进而实现效果更好的算法,不正是程序员的工作职责所在吗?为工作中的小成就而喜悦,为生活中的小乐趣而快乐,正是人生的一大美事。