Duplicate SWF or raster content of an Image component

Using the BitmapData class is quite easy and fast copy or manipulate an external image loaded into a Flex application, it's quite interesting the difference between the data type of the content property of an Image component because it changes if a bitmap or a SWF has been loaded:
  • the first one is a Bitmap
  • the second on is a MovieClipLoaderAsset


In order to let you copy the content also with a SWF file loaded into the Image component you can use a Loader and the loadBytes() method.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
verticalAlign="middle"
backgroundColor="white">

<mx:Script>
<![CDATA[
import mx.core.MovieClipLoaderAsset;
import mx.core.BitmapAsset;
import mx.collections.ArrayCollection;

[Embed(source="placeholder.swf")]
private const ARTWORK_PLACE_HOLDER_SWF:Class;

[Embed(source="placeholder.png")]
private const ARTWORK_PLACE_HOLDER_PNG:Class;

[Bindable]
private var collection:ArrayCollection = new ArrayCollection();

private function handleImage(e:Event):void{

if(e.currentTarget.selectedValue == "PNG"){

img.source = ARTWORK_PLACE_HOLDER_PNG;

}else{

img.source = ARTWORK_PLACE_HOLDER_SWF;

}

}

private function dumpImage(source:Image):void {

var asset:* = source.content;
var data:BitmapData;
var bitmap:Bitmap;

try{

data = Bitmap(source.content).bitmapData;
bitmap = new Bitmap(data);

collection.addItem({image:bitmap, label:"item #" + (collection.length + 1)});

}catch(error:Error){

var loader:Loader = new Loader();

loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapData);

var swf:MovieClipLoaderAsset = asset as MovieClipLoaderAsset;
loader.loadBytes(swf.movieClipData);

}

}

private function onBitmapData(e:Event):void{

e.target.removeEventListener(e.type, arguments.callee);

var content: MovieClip = MovieClip((e.currentTarget as LoaderInfo).content)
var data:BitmapData = new BitmapData(content.width, content.height);
data.draw(content, null, null, null, null, true)
var bitmap:Bitmap = new Bitmap(data)

collection.addItem({image:bitmap, label:"item #" + (collection.length + 1)});

}

]]>
</mx:Script>

<mx:HBox>
<mx:Panel title="Source image">
<mx:HBox verticalAlign="middle" horizontalAlign="center" width="100%" height="100%">
<mx:Image id="img" source="{ARTWORK_PLACE_HOLDER_SWF}" />
</mx:HBox>

<mx:RadioButtonGroup id="imageSelector" change="handleImage(event)" />
<mx:RadioButton label="SWF" selected="true" group="{imageSelector}" />
<mx:RadioButton label="PNG" group="{imageSelector}" />

<mx:ControlBar>
<mx:Button label="Copy image" click="dumpImage(img)" />
</mx:ControlBar>
</mx:Panel>

<mx:TileList id="tileList" dataProvider="{collection}" width="450" height="500" columnCount="4" verticalScrollPolicy="on">
<mx:itemRenderer>
<mx:Component>
<mx:VBox>
<mx:Image source="{data.image}" />
<mx:Label text="{data.label}" />
</mx:VBox>
</mx:Component>
</mx:itemRenderer>
</mx:TileList>
</mx:HBox>

</mx:Application>

Call AMF RPC methods from Flash

If you would like consume some services through RemoteObject from Flash in the same fashion you do in Flex, you will need to follow these step:
  • Add the rpc.swc and framework.swc from your {FLEX_HOME}/frameworks/libs to you ptoject or .fla classpath

  • Optional: If you are compiling an Actionscript Project in Flash Builder you'll also need the resource bundles for those swcs. So add also the rpc_rb.swc and framework_rb.swc from {FLEX_HOME}/frameworks/locale to your classpath.

  • Look at the code below, the initializeRPC and registerClasses methods are what you need to make things work.


You are now ready to call remote methods via amf like this simple example.
Notes:
You will bring nearly 100kb of footprint to your swf due to dependencies from the flex framework. You reduce that footprint by registering a custom collection class instead of the ArrayCollection. The only requirement is that your collection class implements the IExternalizable interface.
import mx.collections.ArrayCollection;
import mx.core.mx_internal;
import mx.logging.Log;
import mx.logging.targets.TraceTarget;
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;
import mx.messaging.config.ConfigMap;
import mx.messaging.config.LoaderConfig;
import mx.messaging.messages.AcknowledgeMessage;
import mx.messaging.messages.AcknowledgeMessageExt;
import mx.messaging.messages.CommandMessage;
import mx.messaging.messages.CommandMessageExt;
import mx.messaging.messages.ErrorMessage;
import mx.messaging.messages.RemotingMessage;
import mx.rpc.AbstractOperation;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
import mx.utils.ObjectProxy;

use namespace mx_internal;

const ID:String = "my-amf";
const ENDPOINT:String = "http://your.domain/amf/gateway";
const DEFAULT_SOURCE:String = "your.service";
const DEFAULT_SOURCE:String = "your.service";

var remoteObject:RemoteObject;

/**
* This initializes the rpc library.
* After this you should see the requests going through.
* Note: LoaderConfig won't show up in the code hints, so you have to write down the import manually.
*/
function initializeRPC():void {

LoaderConfig.mx_internal::_url = loaderInfo.url;
LoaderConfig.mx_internal::_parameters = loaderInfo.parameters;
}

/**
* We register a bunch of class aliases that are needed by the rpc methods.
* The list *may be* incomplete, if you get some TypeCoertion error try registering that class.
*/
function registerClasses():void {

registerClassAlias("flex.messaging.messages.ErrorMessage", ErrorMessage);
registerClassAlias("flex.messaging.messages.CommandMessage", CommandMessage);
registerClassAlias("flex.messaging.messages.RemotingMessage", RemotingMessage);
registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
registerClassAlias("DSC", CommandMessageExt);
registerClassAlias("DSK", AcknowledgeMessageExt);
registerClassAlias("flex.messaging.config.ConfigMap", ConfigMap);
registerClassAlias("flex.messaging.io.ObjectProxy", ObjectProxy);
registerClassAlias("flex.messaging.io.ArrayCollection", ArrayCollection);
}

/**
* Prepare the RemoteObject, creating the appropriate AMF channel
* and setting up the default event handlers.
*/
function prepareDefaultRemoteObject():void {

var channel:AMFChannel = new AMFChannel(ID, ENDPOINT);

var channelSet:ChannelSet = new ChannelSet();
channelSet.addChannel(channel);

remoteObject = new RemoteObject();
remoteObject.source = DEFAULT_SOURCE;
remoteObject.destination = DEFAULT_DESTINATION;
remoteObject.channelSet = channelSet;
remoteObject.addEventListener(FaultEvent.FAULT, onDefaultServiceFault);
remoteObject.addEventListener(ResultEvent.RESULT, onDefaultServiceResult);
}

/**
* Create and execute the remote method.
*/
function makeAnonymousRemoteCall():void {

var operation:AbstractOperation = ro.getOperation("getProduct");
operation.send();
}


/**
* Handles faulty replies from the remote methods.
*/
function onDefaultServiceFault(event:FaultEvent):void {

trace(event.fault.faultDetail);
}

/**
* Handles successful replies from the remote methods.
*/
function onDefaultServiceResult(event:ResultEvent):void {

trace(event.result.toString());
}

// All RPC classes use the Flex logging API so enabling it in the Flash project could be helpful.
Log.addTarget(new TraceTarget());

initializeRPC();
registerClasses();

prepareDefaultRemoteObject();
makeAnonymousRemoteCall();

Binary search

Binary search is a way of searching data inside a sorted list that can drastically reduce the time of the search operations.
In this example I'm searching for an integer because it's the most reliable data type in as. When comparing strings or complex objects some expedient should be taken to be sure that the comparison it's correct. The Comparable interface of Java could be a good solution.
To give insight about how fast is this method, to run 500 searchs in a list of 1000000 items the binarySearch took 2 to 5 milliseconds doing only 16-17 comparisons.
The regular indexOf method of the Array class took 450 milliseconds to perform the same task.
The bit operations (>>>) used to take the middle number (probe) also helped a bit, using a regular division (/2) increased the time to run the task to 4-9 milliseconds.
public function binarySearch(keys:Array, target:int):int
{
var high:int = keys.length;
var low:int = -1;

while (high - low > 1)
{
var probe:int = (low + high) >>> 1;
if (keys[probe] > target)
{
high = probe;
}
else
{
low = probe;
}
}

return (low == -1 || keys[low] !== target) ? -1 : low;
}

Extending a custom component

If you are trying to extend a custom component and you get the following error

"Multiple sets of visual children have been specified for this component (base component definition and derived component definition)."

the problem is that you have created an MXML component with some children hardcoded;

Surfing the net I've found an interesting discussion here but the solution provided there hasn't convinced me at all.

After some attempts I've finally found a nice solution that is not to hardcode components inside the MXML but create components using actionscript and then add them to the displayList in the createChildren method.
override protected function childrenCreated():void{

super.childrenCreated();

if(!btn){

btn = new Button();
btn.label = "my as button";
addChild(btn);

}

}

Circular preloader

How to create a circular animation in As3, useful for preloaders
(tested on Flash Cs3/4 but it should works fine in Flex too)
var angle:Number = 0
var centerX:Number = 50
var centerY:Number = 50;
var radius:Number = 22;
var speed:Number = 0.1

var circle:Shape = new Shape();
addChild(circle)
circle.graphics.beginFill(0x00FF00);
circle.graphics.moveTo(centerX, centerY);

function onEnterFrame(e:Event) {

if (angle > 2 * Math.PI)
this.removeEventListener(Event.ENTER_FRAME, onEnterFrame)

trace("angle", angle)
circle.graphics.lineTo(centerX + Math.cos(angle) * radius,
centerY + Math.sin(angle) * radius);

angle += speed;
}
this.addEventListener(Event.ENTER_FRAME, onEnterFrame)

Duplicate a display object

Quick and dirty solution to duplicate a DisplayObject in Flash, check the duplicate flex component snippet to get a more elegant soltuion.
function duplicateDisplayObject(target:DisplayObject):DisplayObject {

// create duplicate
var targetClass:Class = Object(target).constructor;
var duplicate:DisplayObject = new targetClass();

// duplicate transformation properties
duplicate.transform = target.transform;
duplicate.filters = target.filters;
duplicate.cacheAsBitmap = target.cacheAsBitmap;
duplicate.opaqueBackground = target.opaqueBackground;

// Check the scale9
if (target.scale9Grid) {

var rectangle:Rectangle = target.scale9Grid;
duplicate.scale9Grid = rectangle;

}

return duplicate;
}

Manage bitmap output levels

Imagine to want to reach the same effect of the output levels panel you have in Photoshop through ActionScript, you can get the result by remaping the color channel values in an image that has up to four arrays of color palette data, one for each channel.

In order to do this you can simply use the paletteMap() method of the BitmapData and recalculate the values for each channel to use inside the method accordingly to 2 different points (black and white).
Download the last release of nabiro to get the class that do the job for you.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">

<mx:Script>
<![CDATA[
import com.gnstudio.nabiro.ui.images.utils.OutputLevels;
import mx.events.SliderEvent;

private var bmd:BitmapData;

override protected function childrenCreated():void{

super.childrenCreated();

var url:URLRequest = new URLRequest("central_park.jpg")

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageCompleted);

loader.load(url);

}


private function imageCompleted(e:Event):void{

e.target.removeEventListener(e.type, arguments.callee);

bmd = Bitmap(e.currentTarget.content).bitmapData;

var output:OutputLevels = new OutputLevels(100);
image.source = new Bitmap(output.setLevels(bmd.clone(), 0, 200));

}

private function outputLevels(e:SliderEvent):void{

var output:OutputLevels = new OutputLevels(e.target.values[0] + (e.target.values[1] - e.target.values[0]) / 2);
image.source = new Bitmap(output.setLevels(bmd.clone(), e.target.values[0], e.target.values[1]));

}

]]>
</mx:Script>

<mx:Image id="image" verticalAlign="middle" complete="imageCompleted(event)" horizontalAlign="center" left="30" right="30" top="30" bottom="30"/>
<mx:HSlider bottom="5" right="30" left="30" minimum="0" maximum="255" thumbCount="2" values="[0, 255]" liveDragging="false" change="outputLevels(event)" />

load binary data via xml message

say you are dealing with a backend via xml messages, for some instance you need to load binary data. Flash player can load images, sounds, videos and other movies, but these are operations that go separated from the data exchange context. You can join xml messages and binary content using base64 classes.
The advantages are:
- concurrent messages with content
- you can build up your own distributed objects
- more opportunities to hardening your movies, e.g content protection, cryptography, progressive and conditioned loading
and the cons:
- messages are greater, almost 20/30%
- if you plan to getting touch with real data, some ByteArray skills needed
- some people prefers amf, faster and data driven
// these two code files show how to load a sample image using xml message generated from php


// as3, TryB64php.as

/**
* @author jaco
*
* created on 16/10/2009 16.56.51
*/
package {

import flash.display.*;
import flash.events.*;
import flash.text.*;
import flash.net.*;
import flash.geom.*;
import flash.utils.*;

// as3, got from http://dynamicflash.com/goodies/base64/
// if you're in flex/air, there are mx.utils.Base64* classes
import com.dynamicflash.util.Base64;


/**
*
*/
public class TryB64php extends MovieClip {

private static const PHP_URL:String = "get_xml_b64response.php";

private var ul:URLLoader;
private var ld:Loader;

/**
* the constructor
*/
function TryB64php(){

var ur:URLRequest = new URLRequest(PHP_URL);
ld = new Loader();
ul = new URLLoader();

addChild(ld);

ul.addEventListener(Event.COMPLETE, onULComplete);
ul.load(ur);
}

private function onULComplete(evt:Event):void {

var xml:XML = XML(ul.data);

var ba:ByteArray = Base64.decodeToByteArray(xml);
ld.loadBytes(ba);
}
}
}


// php, get_xml_b64response.php

<?php
/*
* Created on 16/ott/2009
*
* jaco_at_pixeldump
*/

define("XML_HEADER", "<?xml version=\"1.0\"?>");
define("NL", "\r\n");

define("RESPONSE_INFO", "info");
define("RESPONSE_VALUE_BASE64", "base64");

/**
* read resource from path
* @param file name
* @param directory
*
* @return xml instance with base64 encoded data
*/
function get_resource($resFN, $resDir = "./") {

$filePath = $resDir .$resFN;
$fRes = file_get_contents($filePath, FILE_BINARY);

return create_simpleXmlMessage(base64_encode($fRes), RESPONSE_VALUE_BASE64);
}

/**
* create an xml message
* @param message string
* @param response type
*
* @return formatted xml instance with given data
*/
function create_simpleXmlMessage($str, $type = RESPONSE_INFO) {

$xmlStr = XML_HEADER .NL;
$xmlStr .= create_xmlNode("response", $str, array("type" => $type, "timeStamp" => mktime()));

return $xmlStr;
}

/**
* create a simple xml node with some data population
* @param node name
* @param node content
* @param attributes
* @param CDATA flag
*
* @return formatted xml node with given data
*/
function create_xmlNode($nodeName, $nodeContent = "", $attrs = array(), $cDataFlag = true){

$xmlStr = "<" .$nodeName ." ";
$sfx1 = $cDataFlag ? "<![CDATA[" : "";
$sfx2 = $cDataFlag ? "]]>" : "";

if(count($attrs))
foreach($attrs as $k => $v) $xmlStr .= $k ."=\"" .$v ."\" " ;

if(strlen($nodeContent)){
$xmlStr .= ">" .$sfx1 .$nodeContent .$sfx2;
$xmlStr .= "</" .$nodeName .">";
}
else $xmlStr .= "/>";

return $xmlStr;
}

// the app

header("Content-type: text/xml");
echo get_resource("sample_avatar.png"); // sample image
?>

Edit multiple items in sorted ArrayCollection

Do following if you want to traverse through sorted ArrayCollection, update multiple items and trigger only one collection change event to optimize performance of components that use the collection.
<!--
@page { margin: 2cm }
P { margin-bottom: 0.21cm }
-->
var collection:ArrayCollection = new ArrayCollection();

collection.addItem({bar:"bar1", foo:1});
collection.addItem({bar:"bar2", foo:2});
collection.addItem({bar:"bar3", foo:3});
collection.addItem({bar:"bar4", foo:4});

var sort:Sort = new Sort();
sort.fields = [new SortField("foo")];

collection.sort = sort;
collection.refresh();

collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, function(e:CollectionEvent):void {

trace(e.type);

});

collection.disableAutoUpdate();

var cursor:IViewCursor = collection.createCursor();

while (!cursor.afterLast) {

cursor.current.foo++;

cursor.view.itemUpdated(cursor.current);

cursor.moveNext();

}

collection.enableAutoUpdate();

Detect daily saving times

Thanks mainly to the efforts of a London builder named William Willett we have the controversial notion of daylight saving time (DST). This practice means that in some locations, all clocks are put one hour ahead of UTC time during the summer months.
Well, if you use local time on your site (in a real time clock for example) and your site is viewed by someone out with the US, then at certain times of year the time on your clock will be out by an hour. In the case of Europe, between the last Sunday in March and the first Sunday in April the local time on your machine will be one hour ahead of Flash local time.

This small script check the DST accordingly to US convention (first sunday of April last sunday of October) and can be adjusted for your needs in different countires.
var start:Date = new Date()

var startCounter:int = 1;
start.setMonth(3, startCounter);

for(var i:int = 0; i < 10; i++){

startCounter++
start.setMonth(3, startCounter)

if(start.getDay() == 0){

break;

}

}

var end:Date = new Date();
var endCounter:int = 31;

end.setMonth(3, endCounter);

for(var j:int = 0; j < 10; j++){

endCounter--;
end.setMonth(9, endCounter)

if(end.getDay() == 0){

break;

}

}

trace("start date", start)
trace("end date", end)