{"id":219,"date":"2016-03-17T12:07:37","date_gmt":"2016-03-17T11:07:37","guid":{"rendered":"http:\/\/davikingcode.com\/blog\/?p=219"},"modified":"2016-03-18T13:24:43","modified_gmt":"2016-03-18T12:24:43","slug":"encoding-bitmapdata-using-as3-worker","status":"publish","type":"post","link":"https:\/\/davikingcode.com\/blog\/encoding-bitmapdata-using-as3-worker\/","title":{"rendered":"Encoding BitmapData using AS3 Worker"},"content":{"rendered":"<p>Though AS3 <a href=\"http:\/\/help.adobe.com\/en_US\/FlashPlatform\/reference\/actionscript\/3\/flash\/system\/Worker.html\" target=\"_blank\">Worker<\/a> class is available since a <a href=\"http:\/\/www.bytearray.org\/?p=4423\" target=\"_blank\">while<\/a>, its introduction on iOS is recent: less than 6 months. With AS3 workers being available everywhere, it&#8217;s about time to create small libraries multi-threaded with just a few lines of code!<\/p>\n<p>If you never played with Worker, you should give a look to this great series of <a href=\"http:\/\/esdot.ca\/site\/2012\/intro-to-as3-workers-hello-world\" target=\"_blank\">blog post<\/a>.<\/p>\n<p>There are several ways to create Worker, but if you don&#8217;t want to <a href=\"http:\/\/sleepydesign.blogspot.fr\/2014\/03\/ane-daily-pitfall-ane-and-worker-dont.html\" target=\"_blank\">fall in a pitfall<\/a> while using ANEs in your project, I recommend to use them via a loaded SWF.<\/p>\n<p>So here is a small example making a simple library for encoding bitmap data and save images on disk via a Worker:<br \/>\n<!--more--><\/p>\n<p>The idea is our Main application is getting several <em>Bitmap<\/em>s from URLs and you want to save them on <em>applicationStorageDirectory<\/em> without an UI lag. Workers enable an efficient way to communicate between them via <em>ByteArray<\/em>. So once we have our <strong>Bitmap<\/strong>, we turned it into a <strong>shareable<\/strong> (across Workers) <em>ByteArray<\/em> we sent to our ImageWriterWorker. This <em>ByteArray<\/em> is then turned into <em>BitmapData<\/em> for encoding into PNG or JPG depending file extension. And finally we received an other <em>ByteArray<\/em> than we saved on disk!<\/p>\n<p>Let&#8217;s start with our main class, loading a future SWF &#038; creating the worker.<\/p>\n<pre class=\"brush: as3; title: ; notranslate\" title=\"\">package {\r\n\r\n\timport flash.display.Bitmap;\r\n\timport flash.display.Loader;\r\n\timport flash.display.Sprite;\r\n\timport flash.events.Event;\r\n\timport flash.net.URLRequest;\r\n\timport flash.system.ApplicationDomain;\r\n\timport flash.system.LoaderContext;\r\n\timport flash.system.MessageChannel;\r\n\timport flash.system.Worker;\r\n\timport flash.system.WorkerDomain;\r\n\timport flash.utils.ByteArray;\r\n\r\n\t[SWF(frameRate = &quot;60&quot;, width = &quot;1024&quot;, height = &quot;468&quot;, backgroundColor = &quot;0xFFFFFF&quot;)]\r\n\t\r\n\tpublic class Main extends Sprite {\r\n\t\t\r\n\t\tpublic var worker:Worker;\r\n\t\tpublic var msgChannelMainToImageWriterWorker:MessageChannel;\r\n\t\tpublic var workerByteArrayShared:ByteArray;\r\n\t\t\r\n\t\tpublic function Main() {\r\n\t\t\tsuper();\r\n\t\t\t\r\n\t\t\tvar workerLoader:Loader = new Loader();\r\n\r\n\t\t\t\/\/we specify a loader context for managing SWF loading on iOS.\r\n\t\t\tvar loaderContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);\r\n\r\n\t\t\tworkerLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, _onImageWriterWorkerLoaded);\r\n\t\t\tworkerLoader.load(new URLRequest(&quot;workers\/ImageWriterWorker.swf&quot;), loaderContext);\r\n\t\t}\r\n\r\n\t\tprivate function _onImageWriterWorkerLoaded(evt:Event):void {\r\n\t\t\t\r\n\t\t\tevt.target.removeEventListener(Event.COMPLETE, _onImageWriterWorkerLoaded);\r\n\t\t\t\r\n\t\t\tvar workerBytes:ByteArray = evt.target.bytes;\r\n\t\t\tworker = WorkerDomain.current.createWorker(workerBytes, true);\r\n\t\t\t\/\/ we set the latest arguments to true, because we want to be able to save on disk via the Worker.\r\n\t\t\t\r\n\t\t\tmsgChannelMainToImageWriterWorker = Worker.current.createMessageChannel(worker);\r\n\t\t\t\t\r\n\t\t\tworker.setSharedProperty(&quot;mainToImageWriterWorker&quot;, msgChannelMainToImageWriterWorker);\r\n\r\n\t\t\tworkerByteArrayShared = new ByteArray();\r\n\t\t\tworkerByteArrayShared.shareable = true; \/\/ we share the byte array through the worker\r\n\t\t\tworker.setSharedProperty(&quot;imageBytes&quot;, workerByteArrayShared);\r\n\t\t\t\r\n\t\t\tworker.start();\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Now the worker from our library:<\/p>\n<pre class=\"brush: as3; title: ; notranslate\" title=\"\">package {\r\n\r\n\timport flash.display.BitmapData;\r\n\timport flash.display.JPEGEncoderOptions;\r\n\timport flash.display.PNGEncoderOptions;\r\n\timport flash.display.Sprite;\r\n\timport flash.events.Event;\r\n\timport flash.filesystem.File;\r\n\timport flash.filesystem.FileMode;\r\n\timport flash.filesystem.FileStream;\r\n\timport flash.system.MessageChannel;\r\n\timport flash.system.Worker;\r\n\timport flash.utils.ByteArray;\r\n\r\n\tpublic class ImageWriterWorker extends Sprite {\r\n\r\n\t\tpublic var msgChannelMainToImageWriterWorker:MessageChannel;\r\n\t\t\r\n\t\tpublic var bytes:ByteArray;\r\n\r\n\t\tpublic function ImageWriterWorker() {\r\n\r\n\t\t\tmsgChannelMainToImageWriterWorker = Worker.current.getSharedProperty(&quot;mainToImageWriterWorker&quot;);\r\n\t\t\t\r\n\t\t\t\/\/ simple check to remove errors on execution without the main worker\r\n\t\t\tif (msgChannelMainToImageWriterWorker) {\r\n\t\t\t\t\r\n\t\t\t\tmsgChannelMainToImageWriterWorker.addEventListener(Event.CHANNEL_MESSAGE, messageFromMainWorker);\r\n\t\t\t\r\n\t\t\t\tbytes = worker.getSharedProperty(&quot;imageBytes&quot;);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tprivate function messageFromMainWorker(evt:Event):void {\r\n\t\t\t\r\n\t\t\tif (msgChannelMainToImageWriterWorker.messageAvailable) {\r\n\t\t\t\t\r\n\t\t\t\t\/\/ we receive an object composed with an image width, height, and a url path.\r\n\t\t\t\tvar obj:Object = msgChannelMainToImageWriterWorker.receive();\r\n\t\t\t\t\r\n\t\t\t\tbytes.position = 0; \/\/ read informations from start.\r\n\r\n\t\t\t\tvar bmpd:BitmapData = new BitmapData(obj.width, obj.height, true, 0xFFFFFF);\r\n\t\t\t\tbmpd.setPixels(bmpd.rect, bytes);\r\n\t\t\t\t\r\n\t\t\t\tvar localFile:File = new File(obj.path);\r\n\t\t\t\t\r\n\t\t\t\tvar extension = obj.path.substring(obj.path.lastIndexOf(&quot;.&quot;) + 1, obj.path.length);\r\n\t\t\t\tvar bytesEncoded:ByteArray;\r\n\t\t\t\t\r\n\t\t\t\tif (extension == &quot;jpg&quot;)\r\n\t\t\t\t\tbytesEncoded = bmpd.encode(bmpd.rect, new JPEGEncoderOptions(70));\r\n\t\t\t\t\r\n\t\t\t\telse if (extension == &quot;png&quot;)\r\n\t\t\t\t\tbytesEncoded = bmpd.encode(bmpd.rect, new PNGEncoderOptions(true));\r\n\t\t\t\t\r\n\t\t\t\telse \r\n\t\t\t\t\tthrow &quot;Unknow extension: &quot; + extension;\r\n\t\t\t\t\r\n\t\t\t\tvar stream:FileStream = new FileStream();\r\n\t\t\t\tstream.open(localFile, FileMode.WRITE);\r\n\t\t\t\tstream.writeBytes(bytesEncoded);\r\n\t\t\t\tstream.close();\r\n\r\n\t\t\t\tbytesEncoded.clear();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Now let&#8217;s communicate from our Main worker to our ImageWriterWorker library:<\/p>\n<pre class=\"brush: as3; title: ; notranslate\" title=\"\">private function loader_completeHandler(evt:Event):void {\r\n\r\n\tvar bmp:Bitmap = evt.target.content as Bitmap;\r\n\r\n\tMain.workerByteArrayShared.clear(); \/\/ we don't want the system to get out of memory!\r\n\r\n\tbmp.bitmapData.copyPixelsToByteArray(bmp.bitmapData.rect, Main.workerByteArrayShared);\r\n\r\n\tMain.workerMessageChannelMainToBack.send({width:bmp.width, bmp:bmpd.height, path:File.applicationStorageDirectory.resolvePath(&quot;images\/1.jpg&quot;).nativePath});\r\n}<\/pre>\n<p>The <em>ByteArray<\/em> doesn&#8217;t need to go through the <em>MessageChannel<\/em> since it is <strong>shareable<\/strong>! And finally it&#8217;s our main worker which is giving the full path, because if we&#8217;re trying to access the <em>applicationStorageDirectory<\/em> from our main thread, we got an unworking path: <em>\/Users\/Aymeric\/Library\/Application Support\/[Worker].null\/Local Store\/images\/1.jpg<\/em>.<\/p>\n<p>So everything is done? Yeah, it could works fine if we&#8217;re downloading pictures etc. but in fact we missed something important: concurrency! If our ImageWriterWorker takes more time to encode than the <em>copyPixelsToByteArray<\/em>, we&#8217;re changing the ByteArray before (or even during) the encoding via <em>setPixels<\/em>! We could, in fact, send the <em>ByteArray<\/em> too in the message, but it&#8217;s heavy. So we keep the <em>shareable<\/em> property and we implement a queue. The <em>ImageWriterWorker<\/em> will send a message once it has encoded &#038; saved an image so we can proceed the next element in the queue. So let&#8217;s implement a message channel from the other side:<\/p>\n<pre class=\"brush: as3; title: ; notranslate\" title=\"\">\/\/ Main.as\r\nmsgChannelImageWriterToMainWorker = worker.createMessageChannel(Worker.current);\r\n\r\nmsgChannelImageWriterToMainWorker.addEventListener(Event.CHANNEL_MESSAGE, messagesFromImageWriterWorker);\r\n\r\nworker.setSharedProperty(&quot;imageWriterWorkerToMain&quot;, msgChannelImageWriterToMainWorker);\r\n\r\npublic function messagesFromImageWriterWorker(evt:Event):void {\r\n\t\r\n\tif (msgChannelImageWriterToMainWorker.messageAvailable) {\r\n\t\t\r\n\t\tif (msgChannelImageWriterToMainWorker.receive() == &quot;IMAGE_SAVED&quot;) {\r\n\t\t\t\r\n\t\t\tprocessBitmapQueueToImageWriterWorker();\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\/\/ ImageWriterWorker.as\r\n\r\nmsgChannelImageWriterToMainWorker = Worker.current.getSharedProperty(&quot;imageWriterWorkerToMain&quot;);\r\n\r\n\/\/ then once the stream for saving the image is done:\r\nmsgChannelImageWriterToMainWorker.send(&quot;IMAGE_SAVED&quot;);\r\n\r\n\/\/ let's define our queue in an array:\r\nbitmapsToEncode.push([new Image0(), File.applicationStorageDirectory.resolvePath(&quot;images\/0.jpg&quot;).nativePath]);\r\n\r\npublic function processBitmapQueueToImageWriterWorker():void {\r\n\t\r\n\tif (bitmapsToEncode.length &lt; 1) {\r\n\t\ttrace(&quot;queue finished&quot;);\r\n\t\treturn;\r\n\t}\r\n\t\r\n\tvar tab:Array = bitmapsToEncode.shift();\r\n\t\r\n\tvar bitmap:Bitmap = tab[0];\r\n\t\r\n\tworkerByteArrayShared.clear();\r\n\t\r\n\tbitmap.bitmapData.copyPixelsToByteArray(bitmap.bitmapData.rect, workerByteArrayShared);\r\n\t\t\t\r\n\tmsgChannelMainToImageWriterWorker.send({width:bitmap.width, height:bitmap.height, path:tab[1]});\r\n}<\/pre>\n<p>That&#8217;s it! You can download the <a href=\"http:\/\/davikingcode.com\/blog\/wp-content\/uploads\/2016\/03\/workers_src.zip\">code<\/a>.<\/p>\n<p>A cross platform Worker class was one of the latest cog needed for the AS3 platform! <em>Yeah, but since the UI is on the main thread, we can&#8217;t upload our texture on the GPU without freezing&#8230;<\/em>? Correct. But Adobe is working on the ultimate cog, an asynchronous upload for textures! Keep the faith \ud83d\ude09<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Though AS3 Worker class is available since a while, its introduction on iOS is recent: less than 6 months. With AS3 workers being available everywhere, it&#8217;s about time to create small libraries multi-threaded with just a few lines of code! If you never played with Worker, you should give a look to this great series &hellip; <a href=\"https:\/\/davikingcode.com\/blog\/encoding-bitmapdata-using-as3-worker\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Encoding BitmapData using AS3 Worker<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_discordance_state":"","_discordance_checked":true},"categories":[15],"tags":[8,22,23],"_links":{"self":[{"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/posts\/219"}],"collection":[{"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/comments?post=219"}],"version-history":[{"count":18,"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/posts\/219\/revisions"}],"predecessor-version":[{"id":238,"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/posts\/219\/revisions\/238"}],"wp:attachment":[{"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/media?parent=219"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/categories?post=219"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/davikingcode.com\/blog\/wp-json\/wp\/v2\/tags?post=219"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}