AS3, Flash,

In part 3 we were getting very thin on time, but the objective was in sight.

We are getting near the end of the development phase now, so its time to pull out all the stops. We have an image local load from server, we have it placed and resized inside a second degree holder, we also have a ability to save the image to the server as a JPG using PHP. Next step is image manipulation.

To start with we want a drag and drop operation on the image holder. I personally never use native drag’n’drop, as I prefer a bit more control. The method is simple, define the start of the drag operation in this case MOUSE_DOWN on the image holder. At which point we set a Boolean which is checked in an ENTER_FRAME function. The drop operation will either be a MOUSE_UP on the holder clip or a MOUSE_OUT on the main content holder, in which case we clear the boolean and stop the ENTER_FRAME and start to listen for the next drag operation again. We also take a snapshot of where the mouse is in the holder when we start dragging and then in the ENTER_FRAME we know when the mouse moves how far to move the picture (we can’t look at the mouse position inside the image holder because we will be scaling this clip)

pt.pp.addEventListener(MouseEvent.MOUSE_DOWN, msdwn);
var mdown:Boolean=false;

var baseMX:Number=0;
var baseMY:Number=0;

pt.mouseEnabled = true

function msdwn(me:MouseEvent) {
pt.pp.removeEventListener(MouseEvent.MOUSE_DOWN, msdwn);
baseMX=pt.bm.mouseX;
baseMY=pt.bm.mouseY;
addEventListener(Event.ENTER_FRAME, moveOEF);
pt.addEventListener(MouseEvent.MOUSE_UP, mseup);
pt.pp.addEventListener(MouseEvent.MOUSE_OUT, mseup);
mdown=true;
}

function moveOEF(ee:Event) {

if (mdown) {
pt.pp.x=pt.pp.x+pt.bm.mouseX-baseMX;
pt.pp.y=pt.pp.y+pt.bm.mouseY-baseMY;
baseMX=pt.bm.mouseX;
baseMY=pt.bm.mouseY;
}
}

function mseup(me:MouseEvent) {

mdown=false;
pt.pp.addEventListener(MouseEvent.MOUSE_DOWN, msdwn);
removeEventListener(Event.ENTER_FRAME, moveOEF);
pt.removeEventListener(MouseEvent.MOUSE_UP, mseup);
pt.pp.removeEventListener(MouseEvent.MOUSE_OUT, mseup);
}

Next we have scaling, I want to be able to focus in and out on areas of the image and I decided for speed to use a Slider component for this [NB: If I had more time I would stay away from components simply because they bring HUGE overhead with them which I cannot control, it’s always best to use your own tested components as you know how to skin them and customize their behavious. TIP: whenever I complete a project I always have a look inside and if there are any groovy component clips I can use later, I copy them to a new fla file inside my components directory and then name the file very well]

The slider in this case is going to do two types of operation, zoom in to 500% and zoom out, let’s say to about 1%. SO I drag an instance to the stage, resize it, use the component inspector to set a min and max from 0 – 1 and the tick value same as step to 0.01. I have added labels to show what it will do, namely at the halfway point (value = 0.5 as default) we shall have 100% [of the resized image!]

The SliderEvent.CHANGE event is activated when the slider changes, and here we want to return a scaling factor to change the parent image holder. If the value is 0 – 0.5 we want to return 0-1, simple, factor it by 2. If the value is 0.51 – 1 then we want to return a factor of 1.01 – 5 on a linear scale. A simple piece of algebra (remember y=mx+c) will yield the equation to use to provide the sliding scale.

var factor:Number = 1;

import fl.events.SliderEvent;

sld.addEventListener(SliderEvent.CHANGE, sldCH);

function sldCH(sl:SliderEvent) {
var tval:Number = sld.value
factor = (tval<=0.5)?2*tval:7.959*tval-2.959
var MCMidX:Number = MovieClip(parent).pt.pp.x+MovieClip(parent).pt.pp.width/2
var MCMidY:Number = MovieClip(parent).pt.pp.y+MovieClip(parent).pt.pp.height/2

MovieClip(parent).pt.pp.scaleX = MovieClip(parent).pt.pp.scaleY = factor
MovieClip(parent).pt.pp.x = MCMidX-MovieClip(parent).pt.pp.width/2
MovieClip(parent).pt.pp.y = MCMidY-MovieClip(parent).pt.pp.height/2

txtVS.text = String(Math.round(factor*10)/10)
}

Notice I have also centred the scaling effect (look at midpoint before, then adjust after the scale for the new width and height to always keep the operation centred) NOTE: remember I mentioned re-using work, in this case the slider and centreing operation is useful, so stick the whole thing in a clip and save it as a seperate fla document after commenting what is happening. TIme is vanishing and I want to add some more options – bingo – just remembered a wonderful download I have in my library which uses Grant Skinners colourMatrix class (www.gskinner.com), find it, slot it in, connect it and test. Wow.

AhhHA! I hear you say, he is cheating. Me? Cheat ? Absolutely ! Every day I cheat, I re-use, I hack, that’s what flash development is. In this case I highly recommend looking at the code Grant kindly supplied, play with it and understand it.

Now the last development stage is the notification, I am going to do a simple version here and just start a fileReference download operation. For all the smug people out there who will mention the exisitng methods of automatically creating the file download of jpg and png formats from a bitmapData object, yes we could use these also, however for this example, I also wanted to keep a server version and that’s why I am using the GD library. By having it on the server we can do many things, send links to it, create galleries, ooh, lot’s of things.

“The Adobe actionscript 3 help system is your friend”, repeat this mantra every day at least three times. Nobody keeps every bit of AS3 in their heads, it’s not possible. Use the help system. In this case I know for a fact that theres is a very simple FileReference.download example in the help system, drag and drop, change the URL and wham, bang, thankyou maam. Now the bad news, it will not work. And this is where experience comes into play. A security check put in place will stop flash 9 player published files from running this script UNLESS it comes from a button press. So attach the download operation to a button and you are in business.

I also quickly threw in some workflow code here as well. Once the PHP is called, remove the CONVERT and RESET buttons. Once the PHP returns add the DOWNLOAD button. Once the download finishes we need to access some functionality to remove the DOWNLOAD button, remove the image and reset the controls and add back in the UPLOAD button. This function also does the same functionality as the cancel button, so tie that in to. And we end up with the following:


var downloadURL:URLRequest;
var file:FileReference;
var filename:String;

//filename already defined on the php save

dlb.addEventListener(MouseEvent.CLICK,FileReference_download);

function FileReference_download(me:MouseEvent) {

downloadURL = new URLRequest();
downloadURL.url=”images/”+filename;
file = new FileReference();
statust.text=”Download Ready – “+filename+” click download button to receive”;

file.addEventListener(Event.CANCEL, FDcancelHandler);
file.addEventListener(Event.COMPLETE, completeHandler);
file.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
file.addEventListener(Event.OPEN, openHandler);
file.addEventListener(ProgressEvent.PROGRESS, progressHandler);
file.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
file.addEventListener(Event.SELECT, selectHandler);

file.download(downloadURL, filename);
}

function FDcancelHandler(event:Event):void {

}

rss.addEventListener(MouseEvent.CLICK, reset);

function reset(me:MouseEvent) {
onReset(null);
ub.visible=true;
dlb.visible=false;
cv.visible=false;
rss.visible=false;
this.pt.pp.removeChild(image);
ub.addEventListener(MouseEvent.CLICK, ImbrowseHandler);
}

function completeHandler(event:Event):void {
statust.text=”completed download – resetting”;
reset(null);
}

function ioErrorHandler(event:IOErrorEvent):void {

}

function openHandler(event:Event):void {

}

function progressHandler(event:ProgressEvent):void {
var file:FileReference=FileReference(event.target);
}

function securityErrorHandler(event:SecurityErrorEvent):void {
}

function selectHandler(event:Event):void {
var file:FileReference=FileReference(event.target);
}

This is IT ! IT WORKS! Halleluljah, praise baby Jesus etc. But, no, sorry, you forgot the steps. My clock now has 45 minutes left before deadline and I still have a lot to do. So far the system has no design to it, we haven’t tested, or QA’d the application and the copy is missing.

So I am happy with the application technology, time for some quick designy stuff – I literally threw this on in 10 minutes – gave the app a background, placed text on the app with a description and instructions (the copy), changed the watermark on the image, played with the layout of the components, added some button states etc. Fast!

Normally, I would never suggest that a developer do any system testing aside from unit testing – ie taking a specific sub section of an application and doing black box testing (running through all possible sequences from input to output with no regard for what is happening) followed by specific code testing, looking for structural weak points where errors may occur in either the input to a subsystem, the processing or the output.

HOWEVER – many times you will be in a situation where there is nobody else available and time she is a creeping. It’s going to be hard, try and train yourself to be objective and to behave as though you were a user, click all buttons like mad, try and break it, put silly values in text fields, tab, go nuts on the app. What should be tested:

  • Unit Testing – black box and structured – during the development and at completion of sub-system
  • System Testing – all sub-systems, integration, rollback, flow, CPU overload
  • Browser testing – I know its all the same player, but some times browser behaviour does differ – at least do IE, firefox and Safari
  • Player Testing – It’s useful if you have two machines to keep a browser with an older player version for backwards compatability
  • Usability – a curious item, but this is more for feeling and intuitiveness off the application, but very important

If you encounter problems or bugs, fix them immediately as theres always the possibility of follow through, ie later bugs being thrown by the first. Also make sure you are running on a debug player so you will see errors as they occur. It is quite astounding how often when browsing the net on the debug version how often you will see flash applications break apart due to lack of testing.

For such a simple app, I managed to fully test it in about 20 minutes, finding several problems, one of which was the fact that after downloading one image successfully, the second image was an identical copy to the first image, this was because the pixels array containing the ARGB pixel values was populated with a push statement and this was never reset. ANother item was the workflow, buttons were still active and pressable when they shouldn’t have been, cancel options and tech were missing, a few little things really. For a larger project I would recommend doing this more formally and tasking the activity out to a mid level user with a scripted test scenario containing expected and actual results.

Final stages are implementation delivery and maybe training. In such a simple case the implementation is already there, just need to inform the customer of the URL, training ? Well, it’s such a simple system process, you can talk them through it once. Usually there are quite a few extra steps for larger systems, but that’s not really in the scope of this tutorial. Briefly some of the extra stages that would occur are items like code optimisation (applying an OOP structure, checking all standard code optimisations are in place), a big step is documentation – highly underated and very necessary, code should be commented in logical blocks, a system design document should be written, a flow diagram showing all modules, data sources and communication scripts, and a use case study should have been made identifying the user roles and responsibilities (interestingly this document leads directly to the user test script!)

In reality, all we have really accomplished here is a prototype, and that’s OK, but anything larger would need a lot more work putting into the project in order to produce a valid outcome with respect to planning, time and budget. So remember that when you start a client project the steps taken here. Where normally you might just start hacking, remember the steps taken here and that we only started playing with the keyboard AFTER we had determined how long we had and the steps we would need to accomplish the task.

Hope you enjoyed the journey. I have included a zip of the files so you can have a play and maybe grab some stuff you didnt have before. If you are looking to take this forward why not create a second page of thumnails (create a much smaller jpg) of all the user uploads, maybe add mail notification via PHP, add some more image manipulation tools, hell, why not stick in user registration and have accounts. Just out of curiosity I decided to do a quick version 2 of the application, visible here, which uses the Adobe JPGEncoder.as class, the change to the code took about 20 minutes and provides an instant solution as the whole operation is happening locally on the client machine. Just for your benefit it you are trying this, the difference is that the class takes the bitMapData as input and returns a byteArray, then you will need to change the fileReference.download to a fileReference.save. Why not try that too, or use the PNGEncoder.

source files from the tutorial

NB Just out of curiosity I also made an android 10.1 version of this too. So if you have an android phone have a quick look. Obviously when developing for the mobile we have to be a lot more careful about the UI and how the options for the mouse (in reality a finger) will work. The app works pretty well on the mobile, I also added some custom easing to the drag operation as this works better with a finger. You can find it here: abdroid 10.1 image manipulator tool