注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

斗天堂

douzsh还活着

 
 
 

日志

 
 

Image Manipulation in Flex  

2008-05-26 09:28:21|  分类: flash |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

http://www.insideria.com/2008/03/image-manipulation-in-flex.html

 

It seems that the number one request I get for development work is creating applications that do image manipulation or vector drawing or a combination of the two. This article is about my experiences in building applications in Flex to manipulate images. It will cover the basics of loading an image, saving a reference to it, adjusting color, applying pixel effects, changing its dimensions and orientation and ultimately saving these changes. The aim of this article is not to provide production ready solutions but instead to provide ideas for implementing image manipulation solutions in Flex.

Loading image data


The first thing you need to know about loading images into the Flash Player is the limits the Player has with respect to the maximum size of display objects and the maximum size of bitmap data. Currently the Flash Player has a hard coded limit as to the size of bitmap data you can create in the Flash Player (Flash Player 9 and earlier. I am hoping these limits are removed in Flash Player 10). If you create a new BitmapData object in ActionScript it cannot exceed a size of 2880 x 2880. These values are hard coded into the Flash Player and will result in an ‘Invalid BitmapData’ exception if they are exceeded.

For display objects there are no hard coded values (that I have been able to determine) but through testing I have discovered that if a display object exceeds 8191 x 8191 it won’t be rendered. This also applies to the dimensions of images being loaded. If the dimensions of the image exceed this size it will load but it will not be rendered on the display list. So theoretically you can load an image that is up to 8191 x 8191 but if you plan on accessing its bitmap data it can’t exceed 2880 x 2880. More on this below. One thing to keep in mind is that these limits (in the case of BitmapData anyways.) are related to memory allocation so the larger the image you load the more memory you will consume. Ultimately you are going to want to either limit the size of the image a user can load or scale the image down in your application so let’s look at this now.

For simple projects you could just load the image into an image tag and manipulate it. For more complex applications that require functionality like zoom, pan, multiple copies of same image ( for example a thumbnail and full size version) or non destructive editing a better strategy is to load the image with a Loader and store the bitmap data of the image in a variable. You can then use this data to create multiple bitmaps and apply different manipulations to each. This will greatly decrease the amount of memory used and create a faster more responsive application.

In the sample code below you will see how to load an image with the Loader class and store the bitmap data in a variable, scaling it down if necessary.

private var original:BitmapData;  private static const MAX_WIDTH:uint = 2880;  private static var MAX_HEIGHT:uint = 2880;      private function loadImage(url:String):void  {   var request:URLRequest = new URLRequest(url);       var imageLoader:Loader = new Loader();   imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, image_completeHandler);    // add other listeners here    imageLoader.load(request)  }       private function image_completeHandler(event:Event):void  {    var bmd:BitmapData = Bitmap(event.currentTarget.content).bitmapData;           var originalWidth:Number = bmd.width;    var originalHeight:Number = bmd.height;    var newWidth:Number = originalWidth;    var newHeight:Number = originalHeight;          var m:Matrix = new Matrix();          var scaleX:Number = 1;    var scaleY:Number = 1;       if (originalWidth > MAX_WIDTH || originalHeight > MAX_HEIGHT)    {      sx =  MAX_WIDTH / originalWidth;      sy = MAX_HEIGHT / originalHeight;      var scale:Number = Math.min(sx, sy);      newWidth = originalWidth * scale;      newHeight = originalHeight * scale;      }    m.scale(scale, scale);    original = new BitmapData( newWidth, , newHeight);            original.draw(bmd, m);         }


Let’s walk through the code.
We first create a variable to store the bitmap data of the image that we are loading. We then create 2 constants for the maximum width and height this bitmap data can be.

In the loadImage method we take the url of the image as a parameter, create a new URLRequest object with it. We then create a new Loader and attach an event listener to its complete event (other listeners can be added but we are only interested in this event right now). We then call the load method of the loader with the URLRequest as its parameter.

In the event handler we need to access the content property of the loader instance that loaded the image. The content in this case is a Bitmap object. Within a Bitmap there is a property called bitmapData which contains an array of pixel data for this Bitmap. This data is an instance of the BitmapData class.

Once we have a reference to the bitmapData we can check its dimensions and if they exceed our maximum size we can draw it to a new bitmapData object at a new size. To accomplish this we get the amount we need to scale and create a new BitmapData object at this new size. We then use the draw method of the BitmapData class to draw the loaded image to the new BitmapData object and use a matrix to provide the new scale. This BitmapData can now be stored in a variable and used to create as many bitmaps you need each with its own transformations. The transformations applied to you bitmaps don’t affect the original BitmapData so you can always use it as a reference. Now that we have a bitmap data instance that we can use let’s do something with it.

Adjusting Color


One of the things that you are probably going to want to do when manipulating an image is adjust its color. Within the Flash Player API’s there are a number of ways to do this but no matter which you choose what you are basically doing is changing the red, green and blue (and possibly alpha) values of the image. The two main ways to change the values is to either use a ColorTransform or a ColorMatrixFilter. Let’s look at each and how they differ and when to use each.

Using ColorTransform

ColorTransform universally adjust the values of the red, green and blue channels of a display object . Depending on whether you are manipulating a bitmap or a display object the color transform is applied differently. For a display object it is a property of the transform object. For bitmaps it is passed as a parameter to the colorTransform method. To adjust the color you either create a new instance of ColorTransform or get a reference to an existing one and change the red, green and blue values and/or offsets.

Adjusting brightness with a ColorTransform:

*to adjust brightness you change the offsets of the red, green, and blue channels equally

   var ct:ColorTransform = new  ColorTransform();  ct.redOffset = value;  ct.blueOffset = value;   ct.greenOffset = value;  image.transform.colorTransform = ct; // apply the transform to a display object


Using ColorMatrixFilter

The ColorMatrix filter uses a 4 x 5 matirx to adjust the values. Unlike the ColorTransform which universally adjusts the red, green and blue channels, the ColorMatrixFilter adjusts each pixel. The speed of the ColorMatrixFilter is proportional to the size of the image (the number of pixels to change).

Adjusting brightness with a ColorMatrixFilter:

var cmf:ColorMatrixFilter = new ColorMatrixFilter( [ red, green, blue,0, 0, red, green, blue, 0, 0, red,green, blue, 0, 0, 0, 0, 0, 1, 0 ]);    var filtersArray:Array = new Array();  filtersArray.push(cmf);    image.filters = filtersArray;

or

var matrix:Array = new Array();  matrix = matrix.concat([1, 0, 0, 0, value]); // red  matrix = matrix.concat([0, 1, 0, 0, value]); // green  matrix = matrix.concat([0, 0, 1, 0, value]); // blue  matrix = matrix.concat([0, 0, 0, 1, 0]);  // alpha  var cmf:ColorMatrixFilter = new ColorMatrixFilter(matrix);     var filtersArray:Array = new Array();  filtersArray.push(cmf);    image.filters = filtersArray;


No matter which you choose you have to remember that each pixel is broken down into its red, green, blue and alpha channels and the range for each channel is 0 to 255. Any values less than 0 will be set to 0 (0x00) and any values greater than 255 will be set to 255 (0xFF). With each of them you can apply multiple effects simultaneously but it is difficult to undo incremental changes made by concatenating multiple ColorTransform objects together. If you use the filter approach undoing one of your effects is as easy as removing it from the filters array and reapplying the array to the display object. With the filter approach you can also apply multiple filters by pushing each of them into the filters array and turning them off by removing them but the filter approach is slower if you have a larger image. More information on the differences can be found in the Flex documentation.

More examples of adjusting color
*Note - there are different formulas that can be used to calculate the values and offsets for these. The ones used here were taken from the Actionscript 3 Cookbook by Joey Lott, Darron Schall and Keith Peters.

Contrast
You adjust brightness by either scaling or offsetting the color values. You change contrast by changing both with all the values being equal and all of the offsets being equal. *value is a number between 0 and 1

   var a:Number = value * 11;  var b:Number = 63.5 - (value * 698.5);  redValue = greenValue = blueValue = a;  redOffset = greenOffset = blueOffset = b;  var cmf:ColorMatrixFilter = new ColorMatrixFilter(a, 0, 0, 0, b, 0, a, 0, 0, b, 0, 0, a, 0, b, 0, 0, 0, 1, 0);


Saturation
These are the constants for the luminance contrasts for the red, green and blue channels

   var red:Number = 0.3086; // luminance contrast value for red  var green:Number = 0.694; // luminance contrast value for green  var blue:Number = 0.0820; // luminance contrast value for blue  var a:Number = (1-value) * red + value;  var b:Number = (1-value) * green;  var c:Number = (1-value) * blue;  var d:Number = (1-value) * red;  var e:Number = (1-value) * green + value;  var f:Number = (1-value) * blue;  var g:Number = (1-value) * red ;  var h:Number = (1-value) * green;  var i:Number = (1-value) * blue + value;  var cmf:ColorMatrixFilter = new ColorMatrixFilter(a, b, c, 0, 0, d, e, f, 0, 0, g, h, i, 0 ,0, 0, 0, 0, 1, 0);


Grey Scale
Apply a grey scale effect by red, green and blue values to their luminance contrast values.

   var red:Number = 0.3086; // luminance contrast value for red  var green:Number = 0.694; // luminance contrast value for green  var blue:Number = 0.0820; // luminance contrast value for blue  var cmf:ColorMatrixFilter = new ColorMatrixFilter(red, green, blue, 0, 0, red, green, blue, 0, 0, red, green, blue, 0, 0, 0, 0, 0, 1, 0);


Negative
Apply a negative effect by reversing the values of the matrix

   var cmf:ColorMatrixFilter = new ColorMatrixFilter(-1, 0, 0, 0, 255, 0, -1, 0, 0, 255, 0, 0, -1, 0, 255, 0, 0, 0, 1, 0);

Applying Effects


Unlike other aspects of image manipulation there is really only one way to apply effects to your images, a ConvolutionFilter. Like the ColorMatrixFilter, the ConvolutionFilter also uses a matrix to change the image but in this case the matrix can be any size. There are a number of factors that affect performance and they are outlined in the Flex documentation. The most common matrix you will use is a 3x3 matrix. Applying effects is basically the same as applying color effects - you create a filter and add it to the filters array of the display object. Here are some common filters (the first two parameters are the dimensions of the matrix and the third is the matrix).
*the values I am using here are also taken from the Actionscript 3 cookbook

Embossing
Use a negative value in the center and opposite values on each side.

   var emboss:ConvolutionFilter = new ConvolutionFilter(3, 3, [-2, -1, 0, -1, 1, 1, 0, 1, 2])


Edge Detection
Use a negative value in the center with symmetrical surrounding values

   var edge:ConvolutionFilter = new ConvolutionFilter(3, 3, [0, 1, 0, 1, -3, 1, ,0, 1, 0])

You can change the center value (-3) to apply more or less of an effect. The smaller the number the more of an effect is applied.

Sharpening
This is the opposite of the Edge Detection matrix - a positive value in the center with symmetrical negative values

   var sharpen:ConvolutionFilter = new ConvolutionFilter(3, 3, [0, -1, 0, -1, 5, -1, 0, -1, 0]);


The larger the center value the less drastic the effect.
There are other parameters and properties of the ConvolutionFilter that can give some very interesting effects so I encourage you to experiment with them.

Rotating and Flipping


Like color adjustments there are different ways to rotate and/ or flip (mirror) an image in the Flash Player. The first way would be to simply change the rotation, scaleX, and scaleY properties of the display object. This may be fine for simple applications but for more complex applications you will want to use a matrix to manipulate these properties. As noted in the in the Adjusting Color section, each display object has a property called ‘transform’ which contains all of the transformations applied to the display object. The ‘matrix’ property is used to move, rotate, scale and skew the display object. As an example let’s scale the image to twice its original size.
To use the matrix to make changes you get a reference to the matrix (you can’t change the matrix directly)

   var m:Matrix = image.transform.matrix;    //make your changes (scale it in the x and y direction by a factor of 2)    m.scale(2, 2);    //and reapply the matrix to the display object    image.transform.matrix = m;


One thing of note - If you get a reference to the matrix and then make changes all of your changes will be applied onto any changes that have already been applied. In other words they are applied incrementally. As an example if the matrix for the display object already has a rotation of 30 and you apply a rotation of 10 the result will be a rotation of 40.If this is not your desired effect you can simply create a new Matrix and apply it instead of referencing the matrix that already exists.
The other reason a matrix is the better choice is that you can pass it as a parameter in bitmapData.draw. This allows you to make multiple changes and then using you original bitmap data you can draw a new image with all of these transformations.
If you look at the example under ‘Loading image data’ you will see we used a matrix to scale the image down after it was loaded.

Rotating
To use a matrix to rotate an image you either create a new Matrix with appropriate parameters

   var q:Number  = 30 * Math.PI / 180 // 30 degrees in radians    var m:Matrix = new Matrix(Math.cos(q), Math.sin(q), -1 * Math.sin(q), Math.cos(q));    //or as a shortcut use the rotate method    var m:Matrix = new Matrix();    m.rotate(q) ;


When you rotate something in the Flash Player it will rotate around its registration point. This by default is the top left corner. If you want to rotate it around a different point you will need to offset it in the negative direction, do the rotation and then put it back where it was.

   var m:Matrix = new Matrix();    // rotate around the center of the image    var centerX:Number = image.width / 2;    var centerY:Number = image.height /2;    m.translate(-1 * centerX, -1 * centerY);    m.rotate(q);    m.translate(centerX, centrerY);


Flipping
To flip and image is a 2 step process. The first step is to multiply the current scaleX and/or scaleY by -1 and the second is to adjust the x and y position. When you flip and image the registration point does not change and its drawn in the opposite direction. To compensate you will need to change the x position by its width and its y position by its height.

   var m:Matrix = new Matrix();    m.scale(-1, 0); // flip horizontal assuming that scaleX is currently 1    m.translate(image.width, 0); // move its x position by its width to put it in the upper left corner again


 

Cropping, Panning and Zooming


Like everything else we have looked at, there are different ways to change the area of an image that you see. You could change the x, y, width and height properties on the display object or a Rectangle to mark out the pixels you want to see.

There are a few reasons that changing the size of the display object is not the best solution. If the user zooms in enough to make the display object larger than 8191 x 8191 it simply will no longer be rendered. Also if you are applying filters with a matrix the new value of each pixel needs to be calculated which can have dire consequences on performance. You only want to apply you filters to the pixels that the user actually sees. The better approach is to redraw the area of the image that the user wants to see using bitmapData.draw and a matrix.

Since you are storing a reference to the original bitmap data of the image you can use a Rectangle to draw any area of that data onto a new bitmap and then use a matrix to scale it to fit the area of the screen it is displayed in.

When you first load the image you set the rectangle to the dimensions of the original bitmap data and store it in an instance variable

   var zoomArea:Rectangle    zoomArea = original.rect // rect is a property of bitmapData containing the rectangle of the data


The properties of zoomArea can be updating if you want to zoom in or out or pan the image around. The rectangle can then be used to draw an area of the original bitmapData onto a new bitmap.
To pan the image around you can adjust the left and top position of the rectangle through its offset property.

   zoomArea.offset(panX, panY);


To zoom in or out you adjust the size of the rectangle

   zoomArea.inflate(newWidth, newHeight);


Once you have made the adjustments you can then draw a new bitmapData with the dimensions of zoomArea using the copyPixels method of the bitmapData class. The copyPixels method takes a rectangle as the second parameter.

   var newBitmapData:BitmapData = new BitmapData(zoomArea.width, zoomArea.height)    newBitmapData.copyPixels(original, zoomArea, new Point(0, 0));


The copyPixles method is a fast way to copy bitmap data but it will reset any transformations you have made so these all need to be replied. You could also use the rectangle as the clipRect parameter of the bitmapData.draw method.

Printing and Encoding


Once your user has made all of these changes they are probably going to want to print or save them. The Flex 3 SDK now contains a great way to capture these changes. In the mx.graphics package there is a class called ImageSnapshot and this can be used to capture the bitmapData of you image to print or create a jpeg or png to save. You can event use it to encode a byte stream to send to the server. I am not going to go too deeply into this but will mention that if you used a matrix to draw your bitmap then the resolution will now be at screen resolution regardless of what the dpi was before it was loaded. If you simply send the display object to the printer from the stage it will probably appear pixelatted. You will want to capture the display object with the ImageSnapshot.captureImage and scale it up to about 300 dpi to get a crisper image.

Where to go from here


Image manipulation is a broad subject and there are a lot of different approaches you can take. It really comes down to the project requirements. I hope this article gave you some insight into the different approaches you can take. For maximum speed and performance in your application you should familiarize yourself with the Flash Player APIs mentioned in this article. They include Loader, Bitmap, BitmapData, Matrix, Rectangle, Math, ImageSnapshot, JPEGEncoder, PNGEncoder, PrintJob, FlexPrintJob and all of the bitmap filters.

  评论这张
 
阅读(304)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017