Wednesday, June 20, 2007

Flex native drag-n-drop an image from external applications

Adobe AIR provides some nice tools for interacting with the desktop; local database, filesystem, clipboard and native drag and drop. I have been working on a project which requires the ability to drag images from any source and drop them into a container. Should be a simple task. All of the examples like Christophe Coenraets SalesBuilder on Air and the sample apps from Adobe sure make it look easy enough, but none of them explore what I needed to do; they all drag items out of the container, but not in.

Now given AIR is still a beta release, I hope what I present below will not be needed in the future because this took a while to figure out. Maybe I missed something, so if anyone points out an easier way to do this, I'm all ears.

It seemed the obvious thing to do is something along the lines of:

var data:BitmapData = e.transferable.dataForFormat(TransferableFormats.BITMAP_FORMAT) as BitmapData;
myCanvas.addChild(data.content);

Burn! The bitmapData object appears to have data; it has height, width and rectangle properties, the size value indicates there's data in there, but when attempting to add the image to myCanvas, addChild reports an error of 'child must be non null'. I just couldn't reach in and get at the bits.

This led me down a rosy path of trying just about everything I could think to get that pretty picture I wanted to drag in. I ended up with the following. It basically serializes the transfer object's data to file, then I simply pick it back up as an image (note there are no format checks or validation in this example... study purposes only).

Note that you will need the PNGEncoder class, adopted by Adobe from Tinic Uro. These are working versions for Flex 2 and 3.

JPGEncoder.as (text)
PNGEnc (text)

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
applicationComplete="init()" layout="absolute">

<mx:Script>
<![CDATA[
import flash.filesystem.*;
import flash.geom.Matrix;
import flash.display.IBitmapDrawable;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.desktop.*;
import flash.events.NativeDragEvent;
import mx.controls.Image;

public function init():void {
myCanvas.addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, nativeDragEnter);
myCanvas.addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, nativeDragDrop);
}

public function nativeDragEnter(e:NativeDragEvent):void {
if (DragManager.dragInitiator == myCanvas) return; // If your dragging over yourself, ignore
DragManager.acceptDragDrop(myCanvas);
}

public function nativeDragDrop(e:NativeDragEvent):void {
var data:BitmapData = e.transferable.dataForFormat(TransferableFormats.BITMAP_FORMAT) as BitmapData;
var img:Image = encodeImage(data);
myCanvas.addChild(img);
}

private function encodeImage(data:BitmapData):Image {
// write transfer object's data to file
var stream:FileStream = new FileStream();
var matrix:Matrix = new Matrix();
matrix.translate( 0 - ( data.rect.x + 1 ), 0 - ( data.rect.y + 1 ) );
data.draw( data, matrix );

var png:* = null;
png = PNGEncoder.encode(data);

var file:File = File.applicationResourceDirectory.resolve( "tmp.png" );
stream.open( file, FileMode.WRITE );
stream.writeBytes( png, 0, 0 );
stream.close();

// now we can load the image back in
var img:Image = new Image();
img.load(file.nativePath);
return img;
}


]]>
</mx:Script>

<mx:Canvas id="myCanvas" width"400" height="300" backgroundColor="#FFFFFF">
</mx:Canvas>

</mx:WindowedApplication>

Now, with the added call in the nativeDragDrop() function, we have what we need:

var data:BitmapData = e.transferable.dataForFormat(TransferableFormats.BITMAP_FORMAT) as BitmapData;
var img:Image = encodeImage(data);
myCanvas.addChild(img);

Simple? Not exactly. Obvious? Certainly not! But, it shows that Flex can do quite a lot, with a little elbow grease. I am looking forward to the next AIR release, which should fix a number of other bugs and missives that will make our apps really fly.

10 comments:

Joe Ward said... @ June 25, 2007 11:11 AM

Why not just:

var pic:Bitmap = new Bitmap();
pic.data = data;
myCanvas.addChild(pic);

Greg said... @ June 30, 2007 11:05 AM

I only wish it were that simple Joe. A Bitmap object doesn't have a data property though. And, the docs for TransferableFormats.BITMAP_FORMAT specify BitmapData, not Bitmap, as the return type. Like I say, the image data is in the transfer object, but it's either completely transparent, or it's a bug with the beta.

Gonna keep trying various angles and will post my findings. FWIW, I'm also attempting to paste an image from the new ClipboardManager in Flex 3, and my method above doesn't work on it - humph! Even Kevin Hoyt over on his blog acknowledges the problem.

Paritosh said... @ August 6, 2007 1:28 AM

what about this link...
http://www.cynergysystems.com/blogs/page/andrewtrice?entry=flex_2_bitmapdata_tricks_and

Paritosh said... @ August 6, 2007 1:43 AM

In my system i am getting an error in the line,

png = PNGEncoder.encode(data);

Error message:- 1061: call to possibly undefined method encode through static type Class

Greg said... @ August 6, 2007 4:32 PM

Paritosh, did you pull down the encoder class and add it to you project? You can get it from http://labs.adobe.com/svn/flashplatform/?/projects/corelib/trunk/src/actionscript3/com/adobe/images/PNGEncoder.as

Paritosh said... @ August 9, 2007 1:49 AM

Hi Greg,

I just want an example from you how to copy and paste an image in our AIR application. That is instead of drag drop I just want copy and paste.

Thanx,

Paritosh

Greg said... @ August 9, 2007 9:08 AM

I'm swamped at the moment, but I'll try to get you a clipboard example in a few days.

Datafunk said... @ November 14, 2007 6:51 AM

A solution for rendering raw bitmap data in a flex/air application can be found on this blog website

Carolyn said... @ March 12, 2008 12:59 AM

I was just wrestling with a similar issue. The "simple" solution I found is to create a Bitmap object to hold the BitmapData, then add the Bitmap as a child of an Image, then add the Image as a child of your canvas (or whatever object you want it to appear in).

With your code, that would look something like this:

var data:BitmapData = e.transferable.dataForFormat(TransferableFormats.BITMAP_FORMAT) as BitmapData;

var bitmap:Bitmap = new Bitmap(data);

var img:Image = new Image();
img.addChild(bitmap);

myCanvas.addChild(img);

Warning: I haven't tested this code, so please take it as an example of the concept.

Good luck figuring this out!

Anonymous said... @ September 10, 2008 7:01 PM

Carolyn,

Your code did work as you outlined. Thanks!

- Rich

Post a Comment