/*----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is Fever Framework code. The Initial Developer of the Original Code is Romain Ecarnot. Portions created by Initial Developer are Copyright (C) 2006 the Initial Developer. All Rights Reserved. Contributor(s): Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -----------------------------------------------------------------------------*/ import com.bourre.commands.CommandManagerFPS; import com.bourre.commands.Delegate; import com.bourre.data.collections.Map; import com.bourre.events.BasicEvent; import com.bourre.events.EventBroadcaster; import com.bourre.events.EventType; import com.bourre.log.Logger; import com.bourre.log.LogLevel; import com.bourre.log.PixlibStringifier; import com.bourre.remoting.BasicFaultEvent; import com.bourre.remoting.BasicResultEvent; import com.bourre.remoting.ServiceMethod; import com.bourre.remoting.ServiceProxy; import com.bourre.remoting.ServiceResponder; import flash.display.BitmapData; import flash.filters.ColorMatrixFilter; import flash.geom.Point; /** * Remoting Bitmap saver using AMFPHP. * *

Use Patrick Minault BitmapDataSaver AMFPHP Service to * save a BitmapData object into a PNG image file. * *

Available image depth are : *

* *

Original AS2 implementation by Patrick Minault.
* More informations on http://www.5etdemi.com/ * *

Remix by Romain Ecarnot to be Pixlib compliant.
* No use of Adobe MX Remoting package. * * {@code * private function _save( ) : Void * { * //Just sample to get a BitmapData instance * var shoot : ScreenShot = new ScreenShot( Fever.stage.root, true ); * shoot.update(); * * var saver : BitmapSaver = new BitmapSaver( 'http://myServ.com/amf/gateway.php', 'BitmapDataSaver' ); * saver.addEventListener( BitmapSaver.onStartEVENT, this, _onStartExport ); * saver.addEventListener( BitmapSaver.onCompleteEVENT, this, _onComplete ); * saver.addEventListener( BitmapSaver.onErrorEVENT, this, _onError ); * * saver.save( shoot.getData(), BitmapSaver.DEPTH_24, 'monImage' ); * } * * private function _onStartExport( event : BasicEvent ) : Void * { * FeverDebug.DEBUG( 'On Start' ); * } * * private function _onComplete( event : NumberEvent ) : Void * { * FeverDebug.DEBUG( 'On Complete' ); * } * * private function _onError( event : BasicFaultEvent ) : Void * { * FeverDebug.DEBUG( 'Error : ' + event.getDescription() ); * } * } * * @author Romain Ecarnot */ class fever.utils.BitmapSaver { //------------------------------------------------------------------------- // Events definition //------------------------------------------------------------------------- /** Event type broadcasted when process starts. */ public static var onStartEVENT : EventType = new EventType( 'onStart' ); /** Event type broadcasted when process is complete. */ public static var onCompleteEVENT : EventType = new EventType( 'onComplete' ); /** Event type broadcasted when error occured. */ public static var onErrorEVENT : EventType = new EventType( 'onError' ); //------------------------------------------------------------------------- // Private properties //------------------------------------------------------------------------- private var _bCancelled : Boolean; private var _bExported : Boolean; private var _sBitmapId : String; private var _sBitmapString : String; private var _sFileName : String; private var _nBitmapDepth : Number; private var _nBlockSize : Number; private var _nSteps : Number; private var _nStepIndex : Number; private var _nStepCount : Number; private var _nStepLength : Number; private var _oBitmap : BitmapData; private var _oService : ServiceProxy; private var _oEB : EventBroadcaster; private var _nCommandCount : Number; private var _dDepthProcessing : Delegate; private var _dExportProcessing : Delegate; private var _mDepthMap : Map; //------------------------------------------------------------------------- // Public Properties //------------------------------------------------------------------------- /** 32 Bits depth mode ( alpha channel support ). */ public static var DEPTH_32 : Number = 32; /** 24 Bits depth mode. */ public static var DEPTH_24 : Number = 24; /** 12 Bits depth mode. */ public static var DEPTH_12 : Number =12; /** Method name for {@code getBitmapId} remoting method. */ public static var GET_BITMAP_ID_METHOD : ServiceMethod = new ServiceMethod( 'getBitmapId' ); /** Method name for {@code saveImagePart} remoting method. */ public static var SAVE_IMAGE_PART : ServiceMethod = new ServiceMethod( 'saveImagePart' ); /** Method name for {@code endSaveImage} remoting method. */ public static var END_SAVE_IMAGE : ServiceMethod = new ServiceMethod( 'endSaveImage' ); /** Defines packet bloc size ( data packet to send ) ( default is 50000 ). */ public function get blockSize() : Number { return _nBlockSize; } public function set blockSize ( p : Number ) : Void { _nBlockSize = p; } //------------------------------------------------------------------------- // Public API //------------------------------------------------------------------------- /** * Constructor. * * @param gatewayURL AMFPhp gateway url * @param serviceName Remoting service name */ public function BitmapSaver( gatewayURL : String, serviceName : String ) { _oEB = new EventBroadcaster( this ); _oService = new ServiceProxy( gatewayURL, serviceName ); _nBlockSize = 50000; _mDepthMap = new Map(); _mDepthMap.put( DEPTH_12, _prepare12BitsCompression ); _mDepthMap.put( DEPTH_24, _prepare24BitsCompression ); _mDepthMap.put( DEPTH_32, _prepare32BitsCompression ); } /** * Saves passed-in {@code bitmap} BitmapData on * server. * * @param bitmap The BitmapData instance to export * @param depth Image depth *

* @param fileName ( optional ) Image filename ( if not defined, used the * unique generated ID ). */ public function save( bitmap : BitmapData, depth : Number, fileName : String ) { if( !bitmap ) { Logger.LOG( 'Invalid BitmapData', LogLevel.ERROR ); return; } //Reset exportation state _bCancelled = false; _bExported = false; _sBitmapId = null; _nBitmapDepth = ( !depth ) ? BitmapSaver.DEPTH_24 : depth; _sFileName = fileName || null; //Store bitmap data _oBitmap = bitmap.clone(); //Exportation initialisation _export( Math.ceil( _oBitmap.width * _oBitmap.height / 2500 ) ); //Call remote to retreive image unique ID _oService.callServiceMethod( BitmapSaver.GET_BITMAP_ID_METHOD, new ServiceResponder( this, _onGetBitmapId, _onRemoteFault ), _nBitmapDepth, _oBitmap.width, _oBitmap.height ); _oEB.broadcastEvent( new BasicEvent( BitmapSaver.onStartEVENT, this ) ); } /** * Exports and saves passed-in {@code target} MovieClip. * * @param target Movieclip content to export * @param alphaChannel Use alpha channel to build BitmapData * @param depth Image depth * * @param fileName ( optional ) Image filename ( if not defined, used the * unique generated ID ). */ public function export( target : MovieClip, alphaChannel : Boolean, depth : Number, fileName : String ) : Void { if( !target ) { Logger.LOG( 'Invalid MovieClip to export', LogLevel.ERROR ); return; } var bmp : BitmapData = new BitmapData( target.width, target.height, alphaChannel ); bmp.draw( target ); save( bmp, depth, fileName ); } /** * Cancels exportation. */ public function cancel() : Void { if( _sBitmapId == null || !_bExported ) { CommandManagerFPS.getInstance().remove( _dExportProcessing ); } else _nStepIndex = _nSteps; _oBitmap.dispose(); _bCancelled = true; } /** * Adds passed-in {@code listener} for receiving passed-in event {@code type} event. * * @param type Name of the Event. * @param listener Listener object * @param ... Additional arguments */ public function addEventListener( type : EventType, listener /*, ... */ ) : Void { _oEB.addEventListener.apply( _oEB, arguments ); } /** * Removes passed-in {@code listener} that suscribed for passed-in {@code t} event. * * @param type Name of the Event. * @param listener Listener object. */ public function removeEventListener( type : EventType, listener ) : Void { _oEB.removeEventListener( type, listener ); } /** * Returns the string representation of this instance. */ public function toString() : String { return PixlibStringifier.stringify( this ); } //------------------------------------------------------------------------- // Private implementation //------------------------------------------------------------------------- /** * Triggered each time an image packet is saved. */ private function _onExportComplete() { _bExported = true; if( !_bCancelled && ( _sBitmapId != null ) ) { _startSavingProcess(); } } /** * Remoting error manager. */ private function _onRemoteFault( event : BasicFaultEvent ) : Void { Logger.LOG( 'Remoting error : ' + event.getDescription(), LogLevel.ERROR ); event.setType( onErrorEVENT ); _oEB.broadcastEvent( event ); } /** * Triggered by the Remoting service when the Bitmap Unique ID is * generated. */ private function _onGetBitmapId( result : BasicResultEvent ) { _sBitmapId = String( result.getResult() ); if( !_sFileName ) _sFileName = _sBitmapId; if( !_bCancelled && _bExported ) _startSavingProcess(); } /** * Starts saving process. */ private function _startSavingProcess( ) : Void { _nSteps = Math.ceil( _sBitmapString.length / blockSize ); _nStepIndex = 0; _sendImagePart(); } /** * Sends image part. */ private function _sendImagePart( ) : Void { var sc : Number = 2; var bytes2 : String = _sBitmapString.substring( Math.round( _nStepIndex / _nSteps * _sBitmapString.length / sc ) * sc, Math.round( ( _nStepIndex + 1 ) / _nSteps * _sBitmapString.length / sc ) * sc ); _oService.callServiceMethod( BitmapSaver.SAVE_IMAGE_PART, new ServiceResponder( this, _onSendImagePart, _onRemoteFault ), _sBitmapId, bytes2 ); } /** * Image part is sended */ private function _onSendImagePart( ) : Void { _nStepIndex++; if( !_bCancelled ) { if( _nStepIndex < _nSteps ) { _sendImagePart(); } else { _oBitmap.dispose(); _oService.callServiceMethod( BitmapSaver.END_SAVE_IMAGE, new ServiceResponder( this, _onEndSaveImage, _onRemoteFault ), _sBitmapId, _sFileName ); } } } /** * Process is ended. */ private function _onEndSaveImage() { if( !_bCancelled ) { _oEB.broadcastEvent( new BasicEvent( BitmapSaver.onCompleteEVENT, this ) ); } } /** * Prepares exportation using correct depth algo. */ private function _export( ns : Number ) : Void { _sBitmapString = ''; var f : Function; if( _mDepthMap.containsKey( _nBitmapDepth ) ) { f = _mDepthMap.get( _nBitmapDepth ); } else f = _prepare24BitsCompression; f.apply( this, [ ns ] ); _nCommandCount = 0; _dExportProcessing = new Delegate( this, _doCommand, ns ); CommandManagerFPS.getInstance().push( _dExportProcessing ); } /** * Prepares 12 Bits exportation. */ private function _prepare12BitsCompression( ns : Number ) : Void { var mat : ColorMatrixFilter = new ColorMatrixFilter([1/17, 0, 0, 0, 0, 0, 1/17, 0, 0, 0, 0, 0, 1/17, 0, 0, 0, 0, 0, 1, 0]); _oBitmap.applyFilter( _oBitmap, _oBitmap.rectangle, new Point(), mat ); _nStepLength = Math.ceil( _oBitmap.width * _oBitmap.height/ns / 3 ) * 3; _dDepthProcessing = new Delegate( this, _exportOne12, _nCommandCount ); } /** * Prepares 24 Bits exportation. */ private function _prepare24BitsCompression( ns : Number ) : Void { _nStepLength = Math.ceil( _oBitmap.width * _oBitmap.height / ns / 2) * 2; _dDepthProcessing = new Delegate( this, _exportOne24, _nCommandCount ); } /** * Prepares 32 Bits exportation. */ private function _prepare32BitsCompression( ns : Number ) : Void { _nStepLength = Math.ceil( _oBitmap.width * _oBitmap.height / ns / 3 ) * 3; _dDepthProcessing = new Delegate( this, _exportOne32, _nCommandCount ); } /** * Process exportation step by step. */ private function _doCommand( maxStep : Number ) : Void { _dDepthProcessing.setArguments( _nCommandCount++ ); _dDepthProcessing.execute(); if( _nCommandCount >= maxStep ) { CommandManagerFPS.getInstance().remove( _dExportProcessing ); _onExportComplete(); } } /** * Exportation in 12 Bits depth. * *

Algo from Patrick Minault sources. */ private function _exportOne12( currStep : Number ) : Void { var pix1 : Number; var pix2 : Number; var pix3 : Number; var maxVal : Number = Math.min( ( currStep + 1 ) * _nStepLength, _oBitmap.width * _oBitmap.height ); var buff = ''; var oldBuff = ''; var rle = false; var numRle = 0; var tmpBmpStr : String = ''; var bmpW = _oBitmap.width; var BASE64_CHARS : Array = [ 'A','B','C','D','E','F','G','H', 'I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X', 'Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n', 'o','p','q','r','s','t','u','v', 'w','x','y','z','0','1','2','3', '4','5','6','7','8','9','+','/' ]; for( var i : Number = currStep * _nStepLength; i < maxVal; i += 3 ) { pix1 = _oBitmap.getPixel(i % bmpW, Math.floor(i / bmpW)); pix2 = _oBitmap.getPixel( ( i + 1 ) % bmpW, Math.floor( ( i + 1 ) / bmpW ) ); pix3 = _oBitmap.getPixel( ( i + 2 ) % bmpW, Math.floor( ( i + 2 ) / bmpW ) ); buff = BASE64_CHARS[( pix1 >> 14 & 0x3f ) + ( pix1 >> 10 & 0x3 ) ] + BASE64_CHARS[( pix1 >> 4 & 0x3f ) + ( pix1 & 0xf ) ] + BASE64_CHARS[( pix2 >> 14 & 0x3f ) + ( pix2 >> 10 & 0x3 ) ] + BASE64_CHARS[( pix2 >> 4 & 0x3f ) + ( pix2 & 0xf ) ] + BASE64_CHARS[( pix3 >> 14 & 0x3f ) + ( pix3 >> 10 & 0x3 ) ] + BASE64_CHARS[( pix3 >> 4 & 0x3f ) + ( pix3 & 0xf ) ]; if( buff == oldBuff ) { if( rle ) { numRle++; if( numRle == 64 ) { tmpBmpStr += '/#'; numRle = 0; } } else { tmpBmpStr += '#'; rle = true; numRle = 0; } } else { if( rle ) { tmpBmpStr += BASE64_CHARS[ numRle ]; rle = false; } tmpBmpStr += buff; } oldBuff = buff; } if( rle ) { tmpBmpStr += BASE64_CHARS[ numRle ]; rle = false; } _sBitmapString += tmpBmpStr; } /** * Exportation in 24 Bits depth. * *

Algo from Patrick Minault sources. */ private function _exportOne24( currStep : Number ) : Void { var pix1:Number; var pix2:Number; var maxVal:Number = Math.min( ( currStep + 1 ) * _nStepLength, _oBitmap.width * _oBitmap.height ); var buff = ''; var oldBuff = ''; var rle = false; var numRle = 0; var tmpBmpStr:String = ''; var bmpW = _oBitmap.width; var BASE64_CHARS : Array = [ 'A','B','C','D','E','F','G','H', 'I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X', 'Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n', 'o','p','q','r','s','t','u','v', 'w','x','y','z','0','1','2','3', '4','5','6','7','8','9','+','/' ]; for( var i : Number = currStep * _nStepLength; i < maxVal; i += 2 ) { pix1 = _oBitmap.getPixel( i % bmpW, Math.floor( i / bmpW ) ); pix2 = _oBitmap.getPixel( ( i + 1) % bmpW, Math.floor( ( i + 1 ) / bmpW ) ); buff = BASE64_CHARS[ pix1 >> 18 ] + BASE64_CHARS[ pix1 >> 12 & 0x3f ] + BASE64_CHARS[ pix1 >> 6 & 0x3f ] + BASE64_CHARS[ pix1 & 0x3f ] + BASE64_CHARS[ pix2 >> 18 ] + BASE64_CHARS[ pix2 >> 12 & 0x3f ] + BASE64_CHARS[ pix2 >> 6 & 0x3f ] + BASE64_CHARS[ pix2 & 0x3f ]; if( buff == oldBuff ) { if( rle ) { numRle++; if(numRle == 64) { tmpBmpStr += '/#'; numRle = 0; } } else { tmpBmpStr += '#'; rle = true; numRle = 0; } } else { if( rle ) { tmpBmpStr += BASE64_CHARS[ numRle ]; rle = false; } tmpBmpStr += buff; } oldBuff = buff; } if( rle ) { tmpBmpStr += BASE64_CHARS[ numRle ]; rle = false; } _sBitmapString += tmpBmpStr; } /** * Exportation in 32 Bits depth. * *

Algo from Patrick Minault sources. */ private function _exportOne32( currStep : Number ) : Void { var pix1 : Number; var pix2 : Number; var pix3 : Number; var maxVal:Number = Math.min( ( currStep + 1 ) * _nStepLength, _oBitmap.width * _oBitmap.height ); var oldBuff = ''; var buff = ''; var rle = false; var numRle = 0; var tmpBmpStr:String = ''; var bmpW = _oBitmap.width; var BASE64_CHARS : Array = [ 'A','B','C','D','E','F','G','H', 'I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X', 'Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n', 'o','p','q','r','s','t','u','v', 'w','x','y','z','0','1','2','3', '4','5','6','7','8','9','+','/' ]; for( var i : Number = currStep * _nStepLength; i < maxVal; i += 3 ) { pix1 = _oBitmap.getPixel32( i % bmpW, Math.floor(i / bmpW)); pix2 = _oBitmap.getPixel32( ( i + 1 )% bmpW, Math.floor( ( i + 1 ) / bmpW ) ); pix3 = _oBitmap.getPixel32( ( i + 2 )% bmpW, Math.floor( ( i + 2 ) / bmpW ) ); pix1 = ( ( 255 - ( pix1 >> 24 & 0xff ) ) << 24 ) | ( pix1 & 0xffffff ); pix2 = ( ( 255 - ( pix2 >> 24 & 0xff ) ) << 24 ) | ( pix2 & 0xffffff ); pix3 = ( ( 255 - ( pix3 >> 24 & 0xff ) ) << 24 ) | ( pix3 & 0xffffff ); buff = BASE64_CHARS[pix1 >> 26 & 0x3f] + BASE64_CHARS[ pix1 >> 20 & 0x3f ] + BASE64_CHARS[ pix1 >> 14 & 0x3f ] + BASE64_CHARS[ pix1 >> 8 & 0x3f ] + BASE64_CHARS[ pix1 >> 2 & 0x3f ] + BASE64_CHARS[ ( pix1 & 0x3 )* 16 + ( pix2 >> 28 & 0xf ) ] + BASE64_CHARS[ pix2 >> 22 & 0x3f ] + BASE64_CHARS[ pix2 >> 16 & 0x3f ] + BASE64_CHARS[ pix2 >> 10 & 0x3f ] + BASE64_CHARS[ pix2 >> 4 & 0x3f ] + BASE64_CHARS[ ( pix2 & 0xf ) * 4 + ( pix3 >> 30 & 0x3 ) ] + BASE64_CHARS[ pix3 >> 24 & 0x3f ] + BASE64_CHARS[ pix3 >> 18 & 0x3f ] + BASE64_CHARS[ pix3 >> 12 & 0x3f ] + BASE64_CHARS[ pix3 >> 6 & 0x3f ] + BASE64_CHARS[ pix3 & 0x3f ]; if( buff == oldBuff ) { if(rle) { numRle++; if(numRle == 64) { tmpBmpStr += '/#'; numRle = 0; } } else { tmpBmpStr += '#'; rle = true; numRle = 0; } } else { if(rle) { tmpBmpStr += BASE64_CHARS[numRle]; rle = false; } tmpBmpStr += buff; } oldBuff = buff; } if(rle) { tmpBmpStr += BASE64_CHARS[numRle]; rle = false; } _sBitmapString += tmpBmpStr; } }