《点睛:ActionScript3.0游戏互动编程》——1.2 ColorTransform对RGB数值的操作及应用

简介:

本节书摘来自异步社区《点睛:ActionScript3.0游戏互动编程》一书中的第1章,第1.2节,作者:游志德 , 彭文波 更多章节内容可以访问云栖社区“异步社区”公众号查看。

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所示。


857a6586db74d6d100cc672ee99d12bc19ee67a4


24ab333d41c303f1498a3a2257463d34d8691d5e

整个显示对象都变黄了。此外,我们用下面的代码也能实现同样的效果。

代码清单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。


fbfe5ce0a27563d012b09ab4a5af119d19371234


f114a02d4db3d4cb00ee0b52f4f56059b6c35096

倍乘提高亮度(见图 1-6):_myColorTransform.redMultiplier = _myColorTransform. greenMultiplier = _myColorTransform.blueMultiplier = 1.5。

倍乘降低亮度(见图 1-7):_myColorTransform.redMultiplier = _myColorTransform. greenMultiplier = _myColorTransform.blueMultiplier = 0.7。


aeb0a3b8cdf348c83aa26a0b1a4a8e2a93cef3d4


f4221e231a8ada750dcfe261afae0baee4ec0005

我们可以看到,通过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所示。


af9af54dab3fafa47e36de4e63e84a0886dc9ce2

然而,这始终是针对极端数值的权宜之计,局限性很大,对其他颜色的可控性较差,可能在单色的矢量图下感觉不明显,但如果换成色彩比较丰富的照片,那么效果依然不尽如人意,大家可以找些照片来测试一下。而且,从这些例子我们可以看出,RGB的数值跟人类的视觉系统并不协调,至少亮度处理方面是这样的情况。

以上例子对每个通道都做出了完全相同的变换,下面我们尝试给不同通道设置不同的值,看看有没有新的发现。

1.2.4 单个通道的线性/倍乘变化
线性提高红色的成分(见图1-9):_myColorTransform.redOffset = 127。

线性减弱红色的成分(见图1-10):_myColorTransform.redOffset = -127。


1fa5077a39cd796775a24a949fab339a91bca1c8


912dda11431db5033bd5584f69ba00d32cbf13f7

倍乘提高红色的成分(见图1-11):_myColorTransform.redMultiplier = 1.5。

倍乘降低红色的成分(见图1-12):_myColorTransform.redMultiplier = 0.7。


99075445353fcf01f14f2e00f0ac5371cf54f975


d227e7064c3ffa818e9707227615cf6e26b479f6

我们调整单个通道的值,图像的色彩发生了一些微妙的变化。因为现在只对单个通道进行计算,所以总的来说,反射出来的色光都会增多或者减少,如果想保持反射的色光总量不变,就应该多调整一个或者两个通道以抵消红色通道的改变。

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变换。


b51a85a6cd09f738970125f3cac59019672deed2

**
1.2.7 线性提高或降低alpha值**
提高alpha值(见图1-14):_myColorTransform.alphaOffset = 100。

降低alpha值(见图1-15):_myColorTransform.alphaOffset = -100。


ac2452aaf2c3ab595addaa13b0eaa432c3531237


501fdb1ab3b235a1eec0d2acdc82472da3e8794f

粗略一看,它跟设置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。


b10e33782a19b6d0b24f92f7b12f0f63ebe9145c


372dc074d612d4d32913ea204ba508ac74f68940

我们可以看出,alpha的变换呈现为整体性,因此没有扩散收缩的效果,如果大家有跟着我们一起测试的话,就不妨用补间引擎或者enterFrame来测试两种效果在渐入渐出时的差别。我们认为alphaOffset的效果要比multiplier漂亮多了。

如果我们像设置颜色那样,把multiplier弄成0,像素点的透明度就统一起来了。这个效果虽然不甚美观,但是很适合用来做一些图像的预处理工作。

代码如下。

代码清单1-9

_myColorTransform.alphaMultiplier = 0;
_myColorTransform.alphaOffset = 127;

效果如图1-18所示。

在alpha通道方面,ColorTransform没让我们失望,毕竟它独立于色彩模式,而且只有一个值,所以运算的处理也相对容易把握一些。


339b8f70878ab473741eed884b805b16d96bae3d

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所示。


fbae68f6896f65463bead4ab0bd7c15d1843e07c


2e6ba4f8bd40111a2cac2bfc20276fc108696a3b
相关文章
Python版飞机大战游戏的设计(三)-----游戏背景
Python版飞机大战游戏的设计(三)-----游戏背景
Python版飞机大战游戏的设计(三)-----游戏背景
|
5月前
俄罗斯方块游戏开发实战教程(3):玩家如何控制形状
俄罗斯方块游戏开发实战教程(3):玩家如何控制形状
32 1
|
11月前
|
图形学
【unity每日一记】--控制游戏人物移动的细节
【unity每日一记】--控制游戏人物移动的细节
152 0
|
12月前
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏02支持中文及显示FPS
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏02支持中文及显示FPS
117 0
|
12月前
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏10之一组sprite动画
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏10之一组sprite动画
126 0
|
12月前
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏05图像仿射变换(平移和缩放操作)
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏05图像仿射变换(平移和缩放操作)
116 0
|
12月前
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏09之sprite动画
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏09之sprite动画
130 0
|
12月前
|
Java
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏08控制sprite移动
手把手一步一步教你使用Java开发一个大型街机动作闯关类游戏08控制sprite移动
106 0