1.2 ColorTransform对RGB数值的操作及应用
flash.geom.ColorTransform是Flash内置的一个色彩变换类。它支持色彩通道值的线性变换。
所谓线性变换,是指一次函数模式的变换:dst = src * multiplier + offset。我们可以将每个通道的值与常量进行四则运算。对于ColorTransform类而言,通道的原数值和变换后的数值存在如下关系。
red(dst) = red(src) * redMultiplier + redOffset
green(dst) = green(src) * greenMultiplier + greenOffset
blue(dst) = blue(src) * blueMultiplier + blueOffset
alpha(dst) = alpha(src) * alphaMultiplier + alphaOffset
这当中,dst代表结果值,src代表初始值。
这个运算看起来并不复杂,我们也能手工实现。但是,自己写的算式无法直接应用于显示对象或者BitmapData上,我们必须逐个像素点地去修改颜色(当然,ColorMatrixFilter、自定义滤镜、颜色表等方法也能实现),并且对于矢量图而言,处理前还不得不先转换成位图。而用ColorTransform的话则非常方便,我们只要对指定对象应用了它,每个像素点的颜色就会自动做出相应的变化,从而实现图形色调、亮度等属性的整体变换乃至反色效果,而且不会丢失矢量信息。
此外,ColorTransform还支持通过color属性将对象的所有像素点设置成同一种颜色。假设myColorTransform.color = 0xFFFF00,被应用了myColorTransform的对象就会呈现为单一黄色的状态,它跟下面的代码完全等价。
代码清单1-2
myColorTransform.redMultiplier = myColorTransform.greenMultiplier = myColorTransform.blueMultipler = 0;
myColorTransform.redOffset = 255;
myColorTransform.greenOffset = 255;
myColorTransform.blueOffset = 0;
因为与src相乘的系数都被设置为0,所以不管src是多少,dst的值都不会受到任何影响,red(dst)和green(dst)始终等于255,blue(dst)始终等于0,于是所有像素点的RGB色彩值都等于0xFFFF00(黄色),而alpha(不透明度)则不发生变化。
ColorTransform最常用的地方有两处。
1 DisplayObject.transform.colorTransform = myColorTransform;
2 BitmapData.colorTransform(myColorTransform);
这里我们只测试第一项,第二项在BitmapData的相关章节再进一步深入讨论。
1.2.1 RGB测试用例的书写
新建一个ActionScript项目,名为ShapeColorTransformTest,我们在里面创建几个不同颜色的小圆,观察它们在应用了colorTransform之后的变化,代码如下。
代码清单1-3
package{
[SWF(width = "800", height = "600")]
public class ShapeColorTransformTest extends Sprite{
private var _testSprite_src:Sprite; //用于测试的显示对象(变换前)
private var _testSprite_dst:Sprite; //用于测试的显示对象(变换后)
private var _myColorTransform:ColorTransform; //颜色转换对象
public function ShapeColorTransformTest(){
init();
}
private function init():void{
_testSprite_src = getTestSprite();
addChild(_testSprite_src);
_testSprite_dst = getTestSprite();
_testSprite_dst.x = 200;
addChild(_testSprite_dst);
applyTransform();
}
//创建用于测试的显示对象
private function getTestSprite():Sprite{
var _testSprite:Sprite = new Sprite();
var _shape1:Shape = new Shape(); //添加一个黑色的矩形底
_shape1.graphics.beginFill(0x000000);
_shape1.graphics.drawRect(40, 30, 170, 290);
_shape1.graphics.endFill();
_testSprite.addChild(_shape1);
/依次添加颜色分别为暗蓝、暗红、暗绿和纯白的4个圆/
_testSprite.addChild(new Circle(50, 0x0000CC, 100, 200, "add"));
_testSprite.addChild(new Circle(50, 0xCC0000, 150, 200, "add"));
_testSprite.addChild(new Circle(50, 0x00CC00, 125, 250, 1,"add"));
_testSprite.addChild(new Circle(37.5, 0xFFFFFF,125,100,1,add"));
return _testSprite;
}
private function applyTransform():void{
_myColorTransform = _testSprite_dst.transform.colorTransform; //初始化颜色变换对象(从显示对象里获取)
_myColorTransform.redMultiplier = 1.5;
_testSprite_dst.transform.colorTransform = _myColorTransform; //应用到显示对象上(变换后必须重新赋值,否则变换效果不起作用,详情可查阅帮助文档)
}
}
在后续的章节中,我们会经常使用圆来表示一个点或者创建一个色块。为便于这一功能的重用,我们可以把这个可爱的圆封装成一个Circle类并放入到公共类库。
小提示
有的书喜欢用Ball来命名这样的类,这里统一取名为Circle,因为纯色的圆并没有球的那种立体感。
代码清单1-4
package com.gemei.display{
public class Circle extends Sprite{
//为节省本书篇幅,我把x,y,alpha, blendMode这些基本属性都封装到Circle类里面了,项目开发中不推荐这么做
public function Circle(radius:Number = 50, color:uint = 0x000000, x:Number = 0, y:Number = 0, alpha:Number = 1, blendMode:String = BlendMode.NORMAL){
super();
this.x = x; this.y = y; this.blendMode = blendMode;
graphics.beginFill(color, alpha);
graphics.drawCircle(0, 0, radius);
graphics.endFill();
}
}
}
以上代码创建了两个外观一模一样的显示对象,然后添加到舞台上显示(使用BlendMode只是为了方便创建多个色块,后续章节笔者会给出详细的介绍),我们用ColorTransform对其中一个进行变换,从而跟不变换的那个进行比较。
1.2.2 初始效果及颜色属性的测试
按F11/Ctrl+F11测试,效果如图1-2所示。如果左右两组小圆色彩上看起来有所差别,那就请大家校对一下是否在输入代码的过程中出现了笔误,同时注意检查显示器是否因为老化、视角等问题而导致同一种颜色在不同的位置有不同的效果。因为现在的代码仅仅做了一次赋值,中途并没有对ColorTransform进行其他处理。
下面我们先从效果最明显的color开始测试。设置一下_myColorTransform.color = 0xFFFF00看看,如图1-3所示。
整个显示对象都变黄了。此外,我们用下面的代码也能实现同样的效果。
代码清单1-5
_myColorTransform.redMultiplier = _myTransform.greenMultiplier = _myColorTransform.blueMultiplier = 0;
_myColorTransform.redOffset = _myColorTransform.greenOffset = 0xFF;
代码起作用了,我们尝试复杂一点的变换。
1.2.3 线性/倍乘提高降低亮度
线性提高亮度(见图1-4):_myColorTransform.redOffset = _myColorTransform.greenOffset = _myColorTransform.blueOffset = 100。
线性降低亮度(见图1-5):_myColorTransform.redOffset = _myColorTransform.greenOffset = _myColorTransform.blueOffset = -100。
倍乘提高亮度(见图 1-6):_myColorTransform.redMultiplier = _myColorTransform. greenMultiplier = _myColorTransform.blueMultiplier = 1.5。
倍乘降低亮度(见图 1-7):_myColorTransform.redMultiplier = _myColorTransform. greenMultiplier = _myColorTransform.blueMultiplier = 0.7。
我们可以看到,通过ColorTransform增大R、G、B这三个通道的值,能够使所有色彩都变得明亮;相反,减小它们的值,色彩就变暗。然而,这种感觉多少有点别扭,它们看起来更像是给图形蒙上了一层灰,效果特别不自然。倍乘提高亮度的感觉稍好一点,因为黑色和白色在经过运算之后,通道值都没有发生变化,黑色的RGB均为0,乘以1.5后不变;白色的RGB均为255,乘以1.5后大于上限255,于是结果仍为255。
我们可以尝试将线性变化和倍乘变化结合在一起,让亮度变暗的效果舒服些,比如让黑色的RGB结果小于0,而白色始终保持255。
代码清单1-6
_myColorTransform.redMultiplier = _myColorTransform.greenMultiplier = _myColorTransform. blueMultiplier = 1.5;
_myColorTransform.redOffset = _myColorTransform.greenOffset = _myColorTransform.blueOffset = - 127;
我们应用上了这个 ColorTransform 以后,255的变换结果等于 255,而 0 的变换结果等于−127,取下限之后就是 0,于是黑和白看起来就没有变化了,如图1-8所示。
然而,这始终是针对极端数值的权宜之计,局限性很大,对其他颜色的可控性较差,可能在单色的矢量图下感觉不明显,但如果换成色彩比较丰富的照片,那么效果依然不尽如人意,大家可以找些照片来测试一下。而且,从这些例子我们可以看出,RGB的数值跟人类的视觉系统并不协调,至少亮度处理方面是这样的情况。
以上例子对每个通道都做出了完全相同的变换,下面我们尝试给不同通道设置不同的值,看看有没有新的发现。
1.2.4 单个通道的线性/倍乘变化
线性提高红色的成分(见图1-9):_myColorTransform.redOffset = 127。
线性减弱红色的成分(见图1-10):_myColorTransform.redOffset = -127。
倍乘提高红色的成分(见图1-11):_myColorTransform.redMultiplier = 1.5。
倍乘降低红色的成分(见图1-12):_myColorTransform.redMultiplier = 0.7。
我们调整单个通道的值,图像的色彩发生了一些微妙的变化。因为现在只对单个通道进行计算,所以总的来说,反射出来的色光都会增多或者减少,如果想保持反射的色光总量不变,就应该多调整一个或者两个通道以抵消红色通道的改变。
1.2.5 ColorTransform在色彩处理方面的不足
在以上的测试中,有些本来不同的颜色在经过ColorTransform的转换以后变得出奇的一致。如果想给图像做变色效果,那这样的情况往往是我们需要避免的。此时,ColorTransform就有点心有余而力不足了。
归根到底,这还是因为RGB模式在人类的视觉体验方面做得不够友好。
接下来,我们看看之前没测试过的alpha变换。理所当然,之前的图像就不太适用了,因为图像里每个像素点的alpha都等于1。
**
1.2.6 Alpha测试用例的书写**
我们把ShapeColorTransformTest复制为ShapeColorTransform ForAlphaTest,然后对代码进行如下调整。
首先我们把getTestSprite修改为如下格式。
代码清单1-7
//创建用于测试的显示对象
private function getTestSprite():Sprite{
var _f:Array = [new BlurFilter(80, 80)];
var _testSprite:Sprite = new Sprite();
/添加两个圆,颜色分别为白和黑/
_testSprite.addChild(new Circle(50, 0xFFFFFF, 100, 100)).filters = _f;
_testSprite.addChild(new Circle(50, 0xFFFFFF, 100, 250)).filters = _f;
return _testSprite;
}
而applyTransform函数则调整如下。
代码清单1-8
private function applyTransform():void{
_myColorTransform = _testSprite_dst.transform.colorTransform;
_myColorTransform.alphaMultiplier = 0;
_myColorTransform.alphaOffset = 127;
_testSprite_dst.transform.colorTransform = _myColorTransform;
}
运行效果如图1-13所示。以上代码用模糊滤镜BlurFilter生成了带两个透明渐变圆的Sprite。本来这里用透明渐变填充会更加合理,但考虑到渐变代码比较复杂,而且这里还没介绍到,就先用模糊滤镜。
下面我们尝试一下alpha变换。
**
1.2.7 线性提高或降低alpha值**
提高alpha值(见图1-14):_myColorTransform.alphaOffset = 100。
降低alpha值(见图1-15):_myColorTransform.alphaOffset = -100。
粗略一看,它跟设置alpha似乎没什么两样,但仔细观察我们就会发现,当alphaOffset提高的时候,周边比较透明的像素会逐渐淡入到1;相反,降低的时候,周边会慢慢淡出到0。所以,虽然这仅仅是一个简单的线性变换,但是拿来做光圈或者黑洞的扩散/收缩效果将会相当不错。
下面,我们做个直接设置alpha的版本来对照一下效果。
1.2.8 设置alpha值
倍乘降低alpha(见图1-16):_myColorTransform.alphaMultiplier = 0.7。
注意,设置alpha跟设置颜色不同,它要在原有像素的基础上设置倍率才符合alpha的概念,效果才跟设置displayObject.alpha一致。
倍乘提高alpha(见图1-17):_myColorTransform.alphaMultiplier = 1.5。
我们可以看出,alpha的变换呈现为整体性,因此没有扩散收缩的效果,如果大家有跟着我们一起测试的话,就不妨用补间引擎或者enterFrame来测试两种效果在渐入渐出时的差别。我们认为alphaOffset的效果要比multiplier漂亮多了。
如果我们像设置颜色那样,把multiplier弄成0,像素点的透明度就统一起来了。这个效果虽然不甚美观,但是很适合用来做一些图像的预处理工作。
代码如下。
代码清单1-9
_myColorTransform.alphaMultiplier = 0;
_myColorTransform.alphaOffset = 127;
效果如图1-18所示。
在alpha通道方面,ColorTransform没让我们失望,毕竟它独立于色彩模式,而且只有一个值,所以运算的处理也相对容易把握一些。
1.2.9 用ColorTransform实现反色效果
本节的最后,笔者给大家介绍一个稍稍有点意思的效果——反色。这种颠覆性的变换,线性的ColorTransform也能做到吗?答案是肯定的!所谓的反色,就是白变黑,黑变白,浅变深,深变浅,它的计算公式也非常简单,用100%减去原值就能得到结果色,即
dst = 255 – src;
套到ColorTransform的计算公式中,就有如下格式。
red(dst) = red(src) * (-1) + 255。
green(dst) = green(src) * (-1) + 255
blue(dst) = blue(src) * (-1) + 255
换而言之,只要把offset都设为255、multiplier都设成−1,就可以得到所谓的反色效果了(可能有的读者还没想过将multiplier设置为负数吧)。
代码清单1-10
_myColorTransform.redMultiplier = -1;
_myColorTransform.greenMultiplier =-1;
_myColorTransform.blueMultiplier = -1;
_myColorTransform.redOffset = 255;
_myColorTransform.greenOffset = 255;
_myColorTransform.blueOffset = 255;
可出来的效果跟预期不一致,如图1-19所示。
经过将近两周的纠结,笔者终于找到了问题的症结所在——跟BlendMode发生冲突了。因为BlendMode.ADD也是像素运算,两者混合后的运算机制及其优先级规则有待作进一步的研究。
我们把BlendMode.ADD一句去掉之后,反色效果跃然屏上,如图1-20所示。