ImageMagick – Let the magic begin

flash - Imagemagick

Often when i have looked at sites like zazzle, photofunia etc: it has amazed me so as to what the power of graphics in the hand of a programmer could do. so recently i thought of writing down this tutorial to help people understand how each field has its own strength.
This is all about Imagemagick coupled with flash actionscript. Being a actionscript developer i have found this a new field to venture into that makes flash coupled with imagemagick seamlessly the strongest competit
or on the web.

So lets get started on this journey together. First we get started with preparing our front end, and i choose flex.

Application demo

Get Adobe Flash player

Preparing the Front End:

Open up your flexBuilder and create a new flex project. I name mine as Imagemagickdemo.  Set up your project to publish for flash player 10.  If you use flex builder 3 you may want to check out how to change your flex sdk to work with flash player 10 .

Once your flex is ready jump into design view and pull in two buttons. Set labels as Browse and Sketch respectively. Below that set up a panel component. provide ids for the components as btnBrowse, btnSketch, pnl respectively.

Now jump into the code view. Here is my flex code.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" applicationComplete="init()" width="550" height="400">
<mx:Canvas top="20" bottom="20" left="20" right="20">
<mx:Panel layout="absolute" right="20" left="20" top="56" id="pnl" bottom="19">
<mx:Image width="100%" height="100%" id="img"/>
</mx:Panel>
<mx:Button x="40" y="10" label="Browse" id="btnBrowse" click="Selectfiles()"/>
<mx:Button x="142" y="10" label="Sketch" id="btnSketch" click="turnToSketch()"/>
</mx:Canvas>
<mx:Script>
<![CDATA[
import flash.net.FileReference;
import flash.net.Responder;
import flash.events.Event;
import flash.display.Loader;
import flash.display.LoaderInfo;
import mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import flash.net.NetConnection;
import mx.managers.CursorManager;
private var __fref:FileReference;
private var __loader:Loader;
private var conn:NetConnection;
private var magickResponder:Responder;
private var imageHolder:Image;
private function init():void
{
        conn = new NetConnection();
        conn.objectEncoding = ObjectEncoding.AMF3;
        conn.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onPermissionError);
        conn.connect("http://your-domain/amfphp/gateway.php");
        magickResponder = new Responder(onResult, onFault);
       
        __fref = new FileReference();
        __fref.addEventListener(Event.SELECT, onItemSelect);
        __fref.addEventListener(Event.CANCEL, onItemCancel);
        __fref.addEventListener(Event.COMPLETE, onItemComplete);
}
private function UIInit():void
{
        btnBrowse.enabled = true;
        btnSketch.enabled = false;      
}
private function Selectfiles():void
{
        __fref.browse();
}
private function onItemSelect(e:Event):void
{
        __fref.load();
}
private function onItemCancel(e:Event):void
{
       
}
private function onItemComplete(e:Event):void
{
        btnBrowse.enabled = false;
        btnSketch.enabled = true;
               
        __loader = new Loader();
        __loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
        __loader.loadBytes(__fref.data);
}
private function onComplete(e:Event):void
{
        if(pnl.numChildren > 0) pnl.removeChildAt(0);
        imageHolder = new Image();
        pnl.addChild(imageHolder);
        ImageResizer.ResizeImage(LoaderInfo(e.target).loader,pnl.width-50,pnl.height-50);
        imageHolder.addChild(LoaderInfo(e.target).loader);
}
private function turnToSketch():void
{
        conn.call("ImageKing.ConvertFunction", magickResponder,__fref.data);
        CursorManager.setBusyCursor();
}
private function onPermissionError(e:SecurityErrorEvent):void
{
               
}
// response handlers
private function onResult(e:*):void
{
        CursorManager.removeBusyCursor();
        UIInit();
        var resultImageLoader:Loader = new Loader();
        resultImageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
        resultImageLoader.loadBytes(ByteArray(e));
}
private function onFault(e:*):void
{
        Alert.show(e);
}
]]>
</mx:Script>
</mx:Application>

The data property of the filereference object holds the byte array.Now that we know how we select the file and render it to screen as image data, we will proceed with sending this data to server for processing.
But first let me share a few more things:
1. I use ImageResizer class to resize the images being rendered by aspect so that they fit in my canvas.
you can get the class file here.

2. Note that i use a Image class object to render my image to th epanel. This is because the Panel is a UICompenent and thus cannot render the Loader which s not a UIComponent.

Finally we call the turnToSketch() method to send the data to server.

conn.call("ImageKing.ConvertFunction", magickResponder,__fref.data);
CursorManager.setBusyCursor();
Note:  The CursorManager.setBusyCursor() method is optional.

We will discuss the remaining of front end code, namely the result handler after we return from the backend code.

Preparing the Back End:

I asume that the reader knows about setting up amfphp on the server. If not  i recommend this article to help you catch up. Or if you have read the link about setting up amfphp flash remoting in the previous section you would already be aware of it.

Now to the back end… 🙂

amfphp services for flash/flex etc are typically php classes. How we write them ?   well we are going to see shortly :). Just as before let me first post the code then get on with the explaination. So here is the php class.

<?php
class ImageKing
{

var $output_dir = "trash";
var $server_url = "http://your-domain/amfphp/services";


var $default;

function  ImageKing(){
$this->methodTable = array(
"Convert" => array(
"description" => "sketches the image",
"access" => "remote", // available values are private, public, remote
"arguments" => array ("arg1")
)
);
}  

function ConvertFunction($ba,$compressed=false)
{
$data = $ba->data;

if(!file_exists($this->output_dir) || !is_writeable($this->output_dir))
trigger_error ("please create a 'temp' directory first with write access", E_USER_ERROR);


if($compressed)
{
if(function_exists(gzuncompress))
{
$data = gzuncompress($data);
} else {
trigger_error ("gzuncompress method does not exists, please send uncompressed data", E_USER_ERROR);
}
}

$ext = '.jpg';
$file = tempnam($this->output_dir, 'flashvision').$ext;


file_put_contents($file, $data);

$command = 'convert '.$file.' -charcoal 5 '.$file;
//$command = 'convert '.$file.$ext.' -matte -background none  -wave 10x75  '.$file.'.png';
passthru($command);

return new ByteArray(file_get_contents($server_url.'/'.$file));
}

}
?>

as you can see.. the two variables :

var $output_dir = "trash";
var $server_url = "http://your-domain/amfphp/services";

are used as private variables of the ImageKing class. This is why we use “->”operator to access them. Since we are just concerned with the Convert class lets check that out. 🙂

Now let me recall from the front end code:

conn.call("ImageKing.ConvertFunction", magickResponder,__fref.data);

We call the Convert method of the ImageKing class by passing the byte data of the selected image as parameter and we set up Responder magickResponder to react to the incoming result.

The Convert function in php thus accepts the byte data object in $ba. It then extracts the actual byte array from it like this: $data = $ba->data;

Next these are come checks to ensure if data is in compressed format we will use gzip in php to uncompress it before reading. else we create a temporary file,

$ext = '.jpg';
$file = tempnam($this->output_dir, 'flashvision').$ext;

for sake of simplicity i store my extension as “.jpg” as fixed.
Now we dump the byte array into the temp file:

file_put_contents($file, $data);

Now time to do the magick 🙂

‘abra ca dabra’…

$command = 'convert '.$file.' -charcoal 5 '.$file;

This code calls Image magick  (if installed in your server) and applies a charcoal effect on the image file. then we use amfphp’s byte array support to return a byte array of the converted image to our waiting client.

Back to the Front End:

private function onResult(e:*):void
{
CursorManager.removeBusyCursor();
UIInit();
var resultImageLoader:Loader = new Loader();
resultImageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
resultImageLoader.loadBytes(ByteArray(e));
}

In the result handler function , flash recieves the byte array and creates a Loader object to render it. we again use the loadbytes() method of the loader class to read the byte array as image data and then add it to the panel, replacing the previous normal image. I bet you can achieve more speed with a dedicated server and compressed byte array to start with.
Get full source code here

No Comments - Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

*