AS3多线程快速入门(二):图像处理[译]

原文链接:http://esdot.ca/site/2012/intro-to-as3-workers-part-2-image-processing

在《AS3多线程快速入门》系列教程的第一部分中,我们研究了AS3 Worker的基本原理,包括多种通信方式,还展示了一个简单例子:Hello World Worker。

在这篇文章里,我将更进一步,向你展示如何利用多线程做些有用的功能,比如图像处理!在这次例子中,我将一边给一个大位图应用锐化滤镜,一边让主UI线程持续保持在30fps的渲染帧率。

演示:单线程版本

你可以在下面简单看看我们要做的是什么。这个是没有使用workers的版本,你可以看到:在位图处理过程中Slider完全锁定了无法移动。

注:如果你看不见这个SWF程序,检查下你是否已经下载了Flash Player11.4。如果使用的是谷歌浏览器,别忘了要禁用老版本的插件

演示:多线程版本

下面是同一个演示程序,但是使用了Worker。你可以看到它的UI渲染流畅地保持在30fps。

注:如果你看不见这个SWF程序,检查下你是否已经下载了Fash Player11.4。如果使用的是谷歌浏览器,别忘了要禁用老版本的插件

代码

在实际开始写代码前,未雨绸缪下是很重要的。尤其是在处理多线程时,我们确实需要创建一个在worker之间调度数据的高效系统。否则,你的主线程将会因为序列化和反序列化数据造成的巨大开销而陷入困境。

经过一点思考,我决定把这个程序设计成这样:

1.bitmapData通过一个shareable为true的byteArray对象来与worker共享。
2.我们使用bitmapData.setPixels()和bitmapData.copyPixelsToByteArray()方法在bitmapData和byteArray之间来回转换。
3.主线程发出”SHARPEN”命令给worker,然后worker线程在完成时将返回”SHARPEN_COMPELETE”命令。
4.worker线程将使用一个间隔500ms的timer来检测是否需要重新启动一个锐化操作。防止运行过度的锐化操作。

文档类代码

首先是类结构,这里我们将使用和上一个教程里一样方式:用loaderInof.bytes。这个构造函数会被运行两次,第二次运行的是worker,它会创建一个SharpenWorker类的实例,这个实例将负责处理所有与主线程的通信。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

public class ImageWorkerExample extends Sprite

{

public function ImageWorkerExample(){

    //主线程

    if(Worker.current.isPrimordial){

        initUi();

        initWorker();

    }

    //如果不是主线程,就是worker

    else {

        sharpenWorker = new SharpenWorker();

    }

}

在看SharpenWorker类之前,我们先继续读主类。

initUi()方法只是简单地创建了一个slider,一个image,并且添加他们到舞台。这里不需要去关注它。

下一个方法是initWorker(),可以看看代码内的注释。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

protected function initWorker():void {

    //从主swf里创建worker

    worker = WorkerDomain.current.createWorker(loaderInfo.bytes);

    //创建到worker的MessageChannel通信对象

    mainToBack = Worker.current.createMessageChannel(worker);

    //创建来自worker的MessageChannel通信对象并添加监听.

    backToMain = worker.createMessageChannel(Worker.current);

    backToMain.addEventListener(Event.CHANNEL_MESSAGE, onBackToMain, false, 0, true);

    //现在我们有两个通信对象,把它们作为共享属性注入到worker线程

    //这样,worker线程就能在另一边获取它们

    worker.setSharedProperty("backToMain", backToMain);

    worker.setSharedProperty("mainToBack", mainToBack);

    //给worker传递初始化图像宽高尺寸

    worker.setSharedProperty("imageWidth", origImage.width);

    worker.setSharedProperty("imageHeight", origImage.height);

    //转换位图数据并存储到共享的byteArray对象里,与worker线程共享。

    imageBytes = new ByteArray();

    imageBytes.shareable = true;

    origImage.copyPixelsToByteArray(origImage.rect, imageBytes);

    worker.setSharedProperty("imageBytes", imageBytes);

    //最后,启动worker线程.

    worker.start();

}

在以上代码中,我们做了这些事:

1.使用我们自身的loaderInfo.bytes创建了worker。
2.创建了MessageChannel对象,让主线程和worker线程之间能互相通信。
3.复制位图数据到共享的byteArray对象内。
4.与worker线程共享位图数据。
5.启动worker线程。

下一步操作是在主类里通知worker去锐化位图,并且在完成任务时响应。

首先,我们要添加Slider的CHANGE事件处理函数:

?


1

2

3

4

5

protected function onSliderChanged(value:Number):void {

    //给我们的worker线程发送锐化命令.

    mainToBack.send("SHARPEN");

    mainToBack.send(value);

}

然后,我们添加worker的完成任务时的响应函数。

?


1

2

3

4

5

6

7

protected function onBackToMain(event:Event):void {

    var msg:String = backToMain.receive();

    if(msg == "SHARPEN_COMPLETE"){

        imageBytes.position = 0;

        image.bitmapData.setPixels(image.bitmapData.rect, imageBytes);

    }

}

以上就完成主类的代码了!

Worker代码

worker类的结构非常简单明了:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

public class SharpenWorker extends Sprite

{

public function SharpenWorker(){

    //获取当前worker线程的引用(自身)

    var worker:Worker = Worker.current;

    //监听mainToBack的SHARPEN事件

    mainToBack = worker.getSharedProperty("mainToBack");

    mainToBack.addEventListener(Event.CHANNEL_MESSAGE, onMainToBack);

    //使用backToMain抛出SHARPEN_COMPLETE命令

    backToMain = worker.getSharedProperty("backToMain");

    //从共享属性缓存池里获取位图数据。

    imageBytes = worker.getSharedProperty("imageBytes");

    var w:int = worker.getSharedProperty("imageWidth");

    var h:int = worker.getSharedProperty("imageHeight");

    imageBytes.position = 0;

    imageData = new BitmapData(w, h, false, 0x0);

    backToMain.send(imageBytes.length);

    imageData.setPixels(imageData.rect, imageBytes);

    //创建计时器间隔检测锐化值是否已经改变了

    timer = new Timer(500);

    timer.addEventListener(TimerEvent.TIMER, onTimer, false, 0, true);

    timer.start();

}

这里我们做了这些事:

1.持有主线程共享的MessageChanel对象引用。
2.通过共享的byteArray对象数据初始化bitmapData。
3.在内部存储byteArray对象的引用,这样我们从现在开始就可以往里面写入数据了。
4.创建一个Timer来检测是否需要重新锐化。

接下来,我们需要响应SHARPEN请求。这在mainToBack的监听函数里做了处理:

?


1

2

3

4

5

6

7

8

9

10

11

protected function onMainToBack(event:Event):void {

    if(mainToBack.messageAvailable){

        //获取消息类型

        var msg:* = mainToBack.receive();

        //锐化

        if(msg == "SHARPEN"){

            targetSharpen = mainToBack.receive();

        }

    }

}

注意下在这里我们实际上除了存储下锐化值并没有做任何事。如果每一次请求我们都立即应用锐化操作将会非常浪费性能。锐化操作需要超过500ms时间才能完成,但是主线程会以33ms的间隔发送锐化命令。所以很明显,如果我们响应每次请求将会造成巨大的堵塞。最终你的worker线程会崩溃。

相反,我们在时间间隔500ms的TIMER事件处理函数里应用锐化滤镜:

?


1

2

3

4

5

6

7

8

9

10

11

12

protected function onTimer(event:TimerEvent):void {

    if(targetSharpen == currentSharpen){ return; } //直到锐化值改变才启动锐化

    currentSharpen = targetSharpen;

    //锐化位图并复制它到byteArray对象里

    var data:BitmapData = ImageUtils.SharpenImage(imageData, currentSharpen);

    imageBytes.length = 0;

    data.copyPixelsToByteArray(data.rect, imageBytes);

    //通知主线程锐化操作已经完成

    backToMain.send("SHARPEN_COMPLETE");

}

这就是所有的代码了。主线程将会发送附带锐化值的SHARPEN命令,worker线程获取到它后,更新共享的byteArray对象数据并发回SHARPEN_COMPLETE命令。一旦接收到SHARPEN_COMPLETE命令,主线程将会使用共享的byteArray数据更新位图显示对象。

注:如果你对锐化操作本身比较感兴趣,这个优秀的滤镜来自gskinner.com.

你可以在这里下载到完整的测试代码工程:

ImageWorkerExample(包括没有多线程的代码例子)

多线程使用愉快!

来自:http://blog.domlib.com/articles/326.html

时间: 2024-08-09 23:53:48

AS3多线程快速入门(二):图像处理[译]的相关文章

AS3多线程快速入门(三):NAPE物理引擎+Starling[译]

原文链接:http://esdot.ca/site/2012/intro-to-as3-workers-part-3-nape-physics-starling [更新]Adobe在11.4正式发布的最后一刻移除了ByteArray.shareable功能的支持,推迟到11.5版本再发布.为了解决这个问题,源码已经被我更新过了.但这里还是留下完整的示例代码,因为它能最终会正常运行的. 在<AS3多线程快速入门>系列教程的第一部分中,我们研究了AS3 Worker的基本原理,包括多种通信方式,还

AS3多线程快速入门(一):Hello World[译]

原文链接:http://esdot.ca/site/2012/intro-to-as3-workers-hello-world 随着AIR3.4和Flash Player11.4的测试版发布,Adobe终于推出了多年来被要求最多的API:多线程! 如今,使用AS3 Workers让创建真正的多线程应用变得非常简单,只需要几行代码即可.这个API相当的简单易懂,而且他们还做了一些非常有用的事:比如 ByteArray.shareable这个属性,能实现在worker之间共享内存:还有一个新的 Bi

Java 高级 --- 多线程快速入门

这世上有三样东西是别人抢不走的:一是吃进胃里的食物,二是藏在心中的梦想,三是读进大脑的书 多线程快速入门 1.线程与进程区别 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行. 所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务.通常由操作系统负责多个线程的调度和执行. 使用线程可以把占据时间长的程序中的任务放到后台去处理,程序的运行速度可能加快,在一些等待的任务实现上如用户输入.文件读写和网络收发数据等,线

java 多线程 快速入门

------------恢复内容开始------------ java 多线程 快速入门 1. 进程和线程 什么是进程? 进程是正在运行的程序它是线程的集合 进程中一定有一个主线程 一个操作系统可以有多个线程  什么是线程? 线程就是独立的运行一条执行路径 一个独立的执行单元 , 一个执行流程 为什么要使用多线程? 多线程提高程序效率 , 使用多线程 , 每个线程互补影响 2.创建线程的方式有哪些 1.使用继承 Thread类方式 如下示例 结果 1.继承 Thread 类 class Crea

MySQL快速入门(二)

1 多表关联查询 从快速入门,我们已经学会了如何在一张表中读取数据,这是最基础简单的查询表中的数据,但是在实际中经常需要从多个表中读取数据. 本章我将会向大家介绍如何使用MySQL在多个表中查询数据. 想要从多个表中查找数据,就要用到JOIN关键字 JOIN 按照功能大致分为如下三类: 1.CROSS JOIN(交叉连接) 2.INNER JOIN(内连接或等值连接) 3.OUTER JOIN(外连接) 1.1 交叉连接 交叉连接的关键字:CROSS JOIN                  

java多线程快速入门(二)

通过继承Thread类来实行多线程 package com.cppdy; //通过继承Thread类来实行多线程 class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 30; i++) { System.out.println("线程打印:"+i); } } } public class ThreadDemo { public static void main(String

java多线程快速入门(二十)

1.Java.util的线程安全工具类 Vector(线程安全) ArrayList(线程不安全) HashTable(线程安全) HashMap(线程不安全) 2.将线程不安全集合变为线程安全集合 package com.cppdy; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class ThreadDemo16 { public static void main(S

java多线程快速入门(二十一)

CountDownLatch(闭锁)计数器 有一个任务A,它要等待其他4个任务执行完毕之后才执行,此时就可以利用CountDownLatch来实现这种功能 package com.cppdy; import java.util.concurrent.CountDownLatch; class MyThread17 extends Thread{ private CountDownLatch countdownlatch; public MyThread17(CountDownLatch coun

多线程快速入门

应用程序:就是可以执行的软件,如QQ.YY语言. 什么进程:就是正在运行的程序,是所有线程的集合. 什么线程:每一个线程是进程中的一条执行路径,一个独立执行的单元.(是独立运行的一个执行路径或者执行顺序). 每个线程互补影响,因为自己都是独立运行的. 创建线程有方式: 继承Thread类,2.实现Runnanle接口.3.使用匿名内部类.4.实现Callable接口 ,5.线程池创建 多线程第一种实现方式: 继承Thread类,覆盖run()方法. Thread源码解析: public clas