/* ImageCompositor.as Lee Felarca http://www.zeropointnine.com/blog 2-2009 Source code licensed under a Creative Commons Attribution 3.0 License. http://creativecommons.org/licenses/by/3.0/ Some Rights Reserved. */ package com.leelib { import flash.display.BitmapData; import flash.events.EventDispatcher; import flash.events.ProgressEvent; import flash.utils.setTimeout; public class ImageCompositor extends EventDispatcher { private static var _instance:ImageCompositor; private const CHUNK_LENGTH:uint = 75; private const PAUSE_INTERVAL:uint = 66; private var _chunkNum:int; private var _bitmapDatas:Array; private var _callback:Function; private var _data:Array; public function ImageCompositor(enforcer:SingletonEnforcer) { } public static function getInstance():ImageCompositor { if(ImageCompositor._instance == null) ImageCompositor._instance = new ImageCompositor(new SingletonEnforcer()); return ImageCompositor._instance; } /** * Takes a series of bitmaps and averages the color values at each pixel position. * * Because of the computing time needed, the operation runs asynchronously, pausing * periodically to let the display update, etc. * * The client's callback function should accept an Array as a parameter. * * That array is a series of Numbers with the format [nR, nB, nG, nR, nB, nG, ...] * where each element is a value that falls within a range of 0 to 1. * The information is represented in floating point (not as a series of uints or, say, as a BitmapData) * so as to be bit length-independent. * * The nextChunk() function could be modified to do filter-like operations on the incoming data quite easily... * * This method can be used in tandem with PNGEnc48.encode() to create 16-bits-per-channel PNG images. */ public function compositeImage($bitmapDatas:Array, $callback:Function):void { _bitmapDatas = $bitmapDatas; _callback = $callback; _chunkNum = 0; _data = []; nextChunk(); } private function nextChunk():void { var len:int = _bitmapDatas.length; var ub:int = 256 * len; var width:int = _bitmapDatas[0].width; var height:int = _bitmapDatas[0].height var red:uint, green:uint, blue:uint, color:uint, avg:uint; var r:Number, g:Number, b:Number; var yfrom:int = _chunkNum * CHUNK_LENGTH; var yto:int = Math.min((_chunkNum+1) * CHUNK_LENGTH, height) for (var y:int = yfrom; y < yto; y++) { for (var x:int = 0; x < width; x++) { red = green = blue = 0; // sum for (var i:int = 0; i < len; i++) { color = BitmapData(_bitmapDatas[i]).getPixel(x,y); red += color >> 16; green += color >> 8 & 0xff; blue += color & 0xff; } // normalize to 0-1 r = red / ub; g = green / ub; b = blue / ub; // ================================================= // Manipulate normalized values here, if desired * // * but do we really want to reinvent photoshop? :P // ================================================= // example 1: blow out along an square-root curve // r = Math.sqrt(r); g = Math.sqrt(g); b = Math.sqrt(b); // example 2: darken along an exponential curve // r = r*r; g = g*g; b = b*b; // example 3: lighten, relative // r += (1 - r) / 3; g += (1 - g) / 3; b += (1 - b) / 3; // example 4: lighten, linear // r *= 2; g *= 2; b *= 2; // example 5: lighten, absolute // r += 0.5; g += 0.5; b += 0.5; // clamp here if any calculations could colors to fall outside of legal range // r = Math.max( Math.min(r,1), 0); // g = Math.max( Math.min(g,1), 0); // b = Math.max( Math.min(b,1), 0); // ================================================= _data.push(r); _data.push(g); _data.push(b); } } // * Dispatches progress event... var pct:Number = (((_chunkNum+1) * CHUNK_LENGTH) / height) * 100; pct = Math.min(pct, 100); this.dispatchEvent( new ProgressEvent(ProgressEvent.PROGRESS, false,false, pct, 100)); if (yto != height) { _chunkNum++; setTimeout(nextChunk, PAUSE_INTERVAL); } else { _callback(_data); // done } } /** * Creates an (8-bit bound) BitmapData from the * 'scalar array' created with compositeImage() */ public function scalarArrayToBitmapData($data:Array, $width:int, $height:int):BitmapData { var bitmap:BitmapData = new BitmapData($width, $height, false, 0xffffffff); bitmap.lock(); var counter:int; var color:uint, r:uint, g:uint, b:uint; for (var y:int = 0; y < $height; y++) { for (var x:int = 0; x < $width; x++) { r = int($data[counter++] * 256); g = int($data[counter++] * 256); b = int($data[counter++] * 256); color = r << 16 | g << 8 | b; bitmap.setPixel(x,y, color); } } bitmap.unlock(); return bitmap; } } } class SingletonEnforcer {}