Skip to Content »

Tech Life of Recht » archive for 'Flash'

 Taking a picture with a webcam from Flash

  • May 6th, 2007
  • 1:05 am

We're developing a small prototype for a system at work, which involves working with a number of people in the system. To make it extra spiffy, we display a picture next to each person, but then the question of how to get the picture came up. I got the idea to build a Flash applet which could capture an image from a webcam and send it to the server. Possible when you know how to do it, but it took me a couple of days to figure out. Of course, it was complicated somewhat by me running Linux and not having access to the usual Macromedia Flash tools.
So, here's how to do it using swfmill and MTASC, two command line utilities for compiling Flash movies.

First, set up a movie with swfmill. This defines the frames and any assets to be used. For a simple webcam capture, we only need to add a movie object to the movie:

CODE:
  1. <movie width="320" height="300" version="8">
  2.   <frame>
  3.     <library>
  4.       <clip id="VideoDisplay" name="VideoDisplay">
  5.         <frame>
  6.           <video id="VideoSurface" width="320" height="240" />
  7.           <place id="VideoSurface" name="video" />
  8.         </frame>
  9.       </clip>
  10.     </library>
  11.   </frame>
  12. </movie>

Save this as movie.xml and use swfmill to compile it:

CODE:
  1. swfmill simple movie.xml movie.swf

This generates an empty flash file with a movie asset in it. Now we can add the logic using ActionScript. This sets up a text field and the movie.

CODE:
  1. import flash.display.BitmapData;
  2. import flash.geom.Matrix;
  3. import flash.geom.Rectangle;
  4.  
  5. class Application extends MovieClip {
  6.   static function main() {
  7.     _root.createTextField("info", 0, 0, 5, Stage.width, 20);
  8.     _root.info.setNewTextFormat(new TextFormat("Arial", 10));
  9.     _root.info.text = "Webcam: Click the image to capture";
  10.  
  11.     var display = _root.attachMovie("VideoDisplay", "display", _root.getNextHighestDepth());
  12.     var cam = Camera.get();
  13.     display.video._y = 30;
  14.     display.video._width = 320;
  15.     display.video._height = 240;
  16.     display.video.attachVideo(cam);
  17.  
  18.     display.onPress = function() {
  19.       var bd = new BitmapData(320, 240);
  20.       var pixels = new Array();
  21.       var w = bd.width;
  22.       var h = bd.height;
  23.       var out = new LoadVars();
  24.  
  25.       bd.draw(display.video, new Matrix());
  26.       for (var a = 0; a <w; a++) {
  27.         for (var b = 0; b <h; b++) {
  28.           var tmp = bd.getPixel(a, b).toString(32);
  29.           pixels.push(tmp);
  30.         }
  31.       }
  32.       out.img = pixels.join(",");
  33.       out.height = h;
  34.       out.width = w;
  35.       out.send("image_upload", "image_upload_frame", "POST");
  36.     }
  37.   }
  38. }

Save this in Application.as and compile the final flash file:

CODE:
  1. mtasc -version 8 -cp /usr/share/mtasc/std8 -swf movie.swf -main -out webcam.swf -frame 1 Application.as

It's important to include the std8 dir - your installation dir might be different. The command compiles the final flash file in webcam.swf, which can then be included in a webpage like any other flash file.

This will show a live webcam, and when you click on the frame, it will be captured. Unfortunately, it's impossible to send binary data to the server, so we have to encode the image as a normal string. The quick and dirty, but very inefficient, way of doing this is to iterate over all pixels, get the color code for the pixel, and add the color to a list. The list can then be converted to a comma-separated string and sent to the server. This is done using out.send, which in this case sends it to the image_upload url in the frame named image_upload_frame.

The server then needs to parse the received string and use each element as the color of a specific pixel. Here's the Ruby/Rails code to do that:

CODE:
  1. def image_upload
  2.   img_data = params[:img].split(',')
  3.   h = params[:height]
  4.   w = params[:width]
  5.  
  6.   image = PureImage:Image.new(w, h, 0x000000)
  7.  
  8.   i = 0;
  9.   w.times {|x|
  10.     h.times {|y|
  11.       p = img_data[i]
  12.       col = p.to_i(32)
  13.       r = (col>> 16) & 0xff
  14.       g = (col>> 8) & 0xff
  15.       b = col & oxff
  16.       image.set(x, y, [r,b,g]
  17.       i = i + 1
  18.     }
  19.   }
  20.  
  21.   png = PureImage::PNGIO.new
  22.   png.save(image, "/tmp/image.png")
  23. end

The picture has now been saved in /tmp/image.png, and can be used to whatever purpose.

Size

A 320x240 picture will result in a string of about 530kb - which is quite a lot. It can be compressed by removing the comma-separation and using a fixed length list instead. Another way is to use back-references for colors, so instead of sending each color full, some fields can point back to a previous field with the same color.
The string can also be compressed, but that's pretty heavy in flash, and even then, the (binary) compressed data must be converted to a string for safe transportation. In the end, I changed the implementation to only send a thumbnail of the image. That was what I needed, so that'll suffice for now. Doing that is simply a question of adding a scale(ratio, ratio) to the empty Matrix object, and setting the BitmapData size to the scaled size.