LOFTER for ipad —— 让兴趣,更有趣

点击下载 关闭
sgf-py开发指南(四)浅谈Color与Canvas的实现

Color

1. 颜色在计算机中的表示方法:

        通常计算机中的颜色是由RGB组成,特殊的,印刷颜色是CMYK,这里只讨论计算机显示的颜色。RGB各个分量的值在区间[0, 255]上。不同的分量数值的大小会展示出不同的颜色,每个R,G,B分量均有256个色阶。

2.  颜色存储方式

        通常用c语言中的数据类型,一个WORD,来存储一个像素。

        16位颜色:由16位的2进制表示,由于显卡的不同,一般有三种种表示方式:

        1. ARGB,A占1位或为空,与其它各占5位,555模式,5*3 + 1 = 16

        2. RGB分别占5位,6位,5位,565模式,5 + 6 + 5 = 16

        3. ARGB分别占四位, 4 * 4 = 16

        32位颜色:由32位的2进制表示,一般是个ARGB各占8位,8 * 4 = 32

3. 16进制颜色:

        一般颜色也可以表示成:#16进制的数字,例如#ffff0000或#ff0000。

        表示方法是从高位到低位分别是Alpha值00 - ff(ff是10进制的255),R值00 - ff,G值00 - ff,B值00 - ff。一般也会省略alpha,一个颜色占两位,不足两位的的高位用0补齐(如 01, 00,0f),即#aarrggbb。

        所以可知#ffff0000为alpha是255的红色,#ff0000是不带alpha的红色

4. sgf-py alpha混合:

        因为pygame的不足之处就是无法用draw画出半透明的线(带alpha),所以我决定自己写alpha混合函数与底层渲染方法,来弥补这一不足。

        alpha混合:

        alpha混合就是将带有alpha的颜色值换算成不带有alpha的颜色值。因为显示屏上显示的颜色均是由RGB三色混合而来,所以带有alpha颜色也就是由程序计算转换成RGB颜色,例如半透明的效果,实际上是半透明处的颜色给人一种半透明的感觉。

        alpha混合算法:

        我在查阅资料后选定了一种:

        top为前景色,bottom为背景色,返回alpha混合后的颜色, 注意这里的a是0 - 255已经映射在0-1范围内的的值

5. sgf-py brush类

        brush类负责进行绘图,类似于前面介绍的painter,不过painter是单纯的将pygame.draw包装起来,而brush则是自己实现算法,幸运的是pygame.Surface提供set_at的API,所以可以兼容pygame.Surface。

        1. 画一个点:

        画一个点非常简单,将混合后的颜色传set_at即可。

        2. 画一条直线:

        画一条直线并不像想象的那么容易,这里必须提到一个概念:栅格化或者叫光栅化,点阵化,就是将矢量图形显示到显示器上。因为显示器是一个一个小的像素点构成,要使显示器显示一条直线就要将这条直线转化成一个一个小的像素点,这个过程就是栅格化。

        这里我选用经典的Bresenham直线演算法来实现,可以画直线之后,就可以创建其它新的图形了

        关于抗锯齿的处理,我采取了cube interpolation的插值方式进行抗锯齿

        (关于Bresenham算法,与插值方法的详细描述,可以在维基百科上找到)

        3. 画圆

        同样采用Bresensham画圆算法,同样采用cube interpolation

6. 绘制效果比对:

        效果如下图,半透明的为brush画的,不透明的为painter(对pygame.draw的包装)画的

            场景代码如下:

可以看到painter的颜色值为(255,255,255, 100),但依然无法显示为半透明

Canvas

        画布,与surface兼容的画布,内部数据包含有一个数组,用于记录画布每个像素点的颜色值。同时有一个surface。

1.矩阵的存储方式:

        有一下几种方式:

        1. 简单的一维数组

        首先因为二维数组速度的原因,选择的一维数组,长度 = CanvasWidth * CanvasHeight, 获取(x, y)位置的颜色值取法为 color = RecordColors[y * CanvasWidth + x], 优点是取值简单,缺点是占用内存空间大

        2. 矩阵的压缩存储:

        Canvas的像素矩阵一般可以看做一个稀疏矩阵

        因为python没有指针,不像c那样方便,所以这里选用了两种:三元组法伪地址法

        三元组即一个3 * n的表,表头记录非0元素的个数和总行数,总列数,可以将最终的三元组表抽象成一个一维数组。

        伪地址法即一个2 * n的表,表头是非0元素个数和伪地址编址的最大值,

        伪地址计算方法:行标 = [伪地址 / 总行数], 列标 = [伪地址 / 总列数]

        同样最终的结果可以抽象成一个一维数组。

        优点显而易见,占用内存空间少,但缺点是取值时的运算量大。

        算法就是用时间换空间或用空间换时间的方法。所以在相应的环境下选择相应的算法才是最好的。

2. 在Canvas上绘图的过程

        绘制时检测该位置的颜色值,将要进行绘制的颜色值与该位置的颜色值进行alpha混合,得到颜色值,绑定到surface上,然后更新存储在该位置的颜色值。

最后的问题

        相信懂得计算机的人都知道,python解释器使用cup串行执行python代码,对于高密集的像素运算来说,速度实在是太慢了。在这一系列绘图核心库中光是绘制一条线就要经历以下几个计算过程:

        1. 栅格化:计算这条线上每一个点在屏幕上对应的像素点

        2. 抗锯齿:对每一个点进行至少一次插值计算

        3. alpha混合:计算每一个点在与该位置原本就有的像素点进行混合后的颜色

        4. 计算显示的颜色:将每一个点由ARGB转换成RGB

        5. 呈现:将每一个处理完成的点显示在pygame.surface对应的位置上

        python的运算速度对图像绘制来说确实太慢,不过可以借助GPU实现并行计算,通过使用NVIDIA的pycuda可以将计算委托给GPU完成,这就是所谓硬件加速,让cpu专注于逻辑运算。但是,如果使用pycuda则需要NVIDIA的显卡与调用显卡驱动,或者说,我为什么不直接用c语言开发一个图形库呢?先不说有没有必要,我当初选择pygame不就是因为简单吗?或者回到原点,绘制一条半透明的线可能根本没有必要......

        不过这就不是这篇文章讨论的问题了。

        

        

推荐文章
评论(0)
分享到
转载我的主页