AS3: BitmapData linked to certain sprite in library? - actionscript-3

I have a problem. I have a sprite added to library and I linked and named its class "CarriedHead_Normal". Now I want to display this sprite and make every black(000000) pixel invisible (just convert background to alpha).
I know I can use this
var bmpd:BitmapData = new BitmapData(width, height, true, 0x000000)
But how am I supposed to merge this with my function, so I call the class from the library and make it's bg transparent?
My current code:
var imageSprite = new Sprite();
addChild(imageSprite);
var libraryImage:Bitmap = new Bitmap(new CarriedHead_Normal(0, 0))
imageSprite.addChild(libraryImage);
Thanks in advance.

A number of ways you could do it, some working better than others:
On a side node, your question title has nothing to do with your actual question
1) Use blend modes
Depending on your graphics and background, you might be able to get away with a simple blendMode - check out BlendMode.SCREEN in particular. Note that some blend modes require that your parent container have a blendMode of BlendMode.LAYER
2) copyPixels() using an alpha BMD
copyPixels() lets you specify an alpha BitmapData to use when deciding which pixels to copy over (actually the alpha value of the pixels to copy over). If you have an alpha BitmapData that matches your black areas, you can remove it like that:
var b:BitmapData = new BitmapData( myImageBMD.width, myImageBMD.height, true, 0x00000000 );
b.copyPixels( myImageBMD, myImageBMD.rect, new Point, alphaBMD, new Point, false ); // copy our image, using alphaBMD to remove the black
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html#copyPixels()
3) floodFill() the black areas
If your black area is continuous and you know a pixel that it starts on (e.g. it's a black frame around an image), then you can use floodFill() to remove the black areas
var b:BitmapData = new BitmapData( myImageBMD.width, myImageBMD.height, true, 0x00000000 );
b.copyPixels( myImageBMD, myImageBMD.rect, new Point ); // copy our image so we keep the original
b.floodFill( 0, 0, 0x00000000 ); // assuming the first pixel is black, floodFill() from here, replacing with transparence
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html#floodFill()
This technique will work best on pixel art, where the edges are well defined.
4) threshold() to remove the black
threshold() takes pixel values and replaces them with the value you set if they pass a certain test (<, <=, ==, etc). You can use this to replace black with transparence.
var b:BitmapData = new BitmapData( myImageBMD.width, myImageBMD.height, true, 0x00000000 );
b.copyPixels( myImageBMD, myImageBMD.rect, new Point ); // copy our image so we keep the original
b.threshold( b, b.rect, new Point, "==", 0xff000000, 0x00000000 ); // test the pixels; if they're black, replace with transparence
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html#threshold()
Again, this works best when the edges are well defined.
5) Use filters
You can use applyFilter() to create a new image, using a BitmapFilter to remove the black. Possibly only of the other filters will work, but ShaderFilter should definitely do the job (you write your own shader, so you can essentially do what you want). I don't have any real experience with filters, so I can't tell you how they work, but some googling should give you the necessary code
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html#applyFilter()
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/filters/ShaderFilter.html
6) Use photoshop (or equivalent)
The easiest method; just edit your image to remove the black; much less hassle :)

Try something like:
var libraryImage:Bitmap = spriteToBitmap(new CarriedHead_Normal());
imageSprite.addChild(libraryImage);
function spriteToBitmap(sprite:Sprite, smoothing:Boolean = false):Bitmap
{
var bitmapData:BitmapData = new BitmapData(sprite.width, sprite.height, true, 0x00000000);
bitmapData.draw(sprite);
bitmapData.threshold(bitmapData, bitmapData.rect, new Point(), '==', 0xff000000, 0x00000000);
return new Bitmap(bitmapData, "auto", smoothing);
}

Related

Merge two images and retain its transparency

It was a while since I programmed AS3. Now I have a problem where I need to merge the two images where the upper image is a png that must retain its transparency. The upper image is an area that must pass through the lower image. A bit like a masked layer.
The result of this merge should result in a a display object. This object will later be sent to a method with the following signature:
public function addImage (
display_object:DisplayObject,
x:Number = 0,
y:Number = 0,
width:Number = 0,
height:Number = 0,
image_format:String = "PNG",
quality:Number = 100,
alpha:Number = 1,
resizeMode:String = "None",
blendMode:String = "Normal",
keep_transformation:Boolean = true,
link:String = ''
):void
Any advice is of the utmost interest. Thanks!
UPDATE;
After some struggling I've come up with this:
var bitmapDataBuffer:BitmapData = new BitmapData ( front.loader.width, front.loader.height, true );
bitmapDataBuffer.draw ( front.loader );
var bitmapOverlay:BitmapData = new BitmapData ( front.loader.width, front.loader.height, true );
bitmapOverlay.draw ( frontBanner.loader );
var rect:Rectangle = new Rectangle(0, 0, front.loader.width, front.loader.height);
var pt:Point = new Point(0, 0);
var mult:uint = 0x00;
bitmapOverlay.merge(bitmapDataBuffer, rect, pt, mult, mult, mult, mult);
var bmp:Bitmap = new Bitmap(bitmapOverlay);
pdf.addImage(bmp,0,0,0,0,ImageFormat.PNG,100,1,ResizeMode.FIT_TO_PAGE);
The problem is that my background image (represented by bitmapDataBuffer) will be totally overwritten by my upper image (the one I call overlay).
The overlay image is a png image. This image has a part of it that is transparent. Through this transparency I want to see my background image.
Any more suggestions?
You should be more specific about what kind of merge you want. You have a few options:
BitmapData.copyPixels - Provides a fast routine to perform pixel manipulation between images with no stretching, rotation, or color effects. This method copies a rectangular area of a source image to a rectangular area of the same size at the destination point of the destination BitmapData object.
BitmapData.merge - Performs per-channel blending from a source image to a destination image. For each channel and each pixel, a new value is computed based on the channel values of the source and destination pixels.
BitmapData.draw - Draws the source display object onto the bitmap image, using the Flash runtime vector renderer. You can specify matrix, colorTransform, blendMode, and a destination clipRect parameter to control how the rendering performs.
Each will work out for a different thing - the first will just copy some image over another (can keep/merge alphas). The second will merge channels data and modify them. The third one is the easiest and can draw one bitmap over another, as well as use blend modes.
Just chose one! :)
In order to make overlay image over the buffer image in your case, you are to use copyPixels() with mergeAlpha set to true.
bitmapDataBuffer.copyPixels(bitmapOverlay, rect, new Point(), null, null, true);
This will place data from bitmapOverlay to those parts of bitmapDataBuffer where overlay's alpha is above 0, blending semitransparent regions with the background.

as3 - setting textures alpha-value

For an Actionscript 3 "drawing application", I want to be able to chose a Texture and set it's transparency.
Therefore I try to set the alpha-transparency of a texture.
But it's not working.
What I do:
At first I use graphics.linestyle() to set the lines thickness and ALPHA-value.
After that I (a) load a png, (b) read it's bitmapData and (c) then use it with lineBitmapStyle.
Result:
When drawing lines (with moveTo, lineTo, etc) the lines use the texture, but IGNORE THE "Alpha" which was set with lineStyle.
What am I doing wrong?
myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, setTexture);
setTexture(e:Event):void
{
e.currentTarget.removeEventListener(e.type, arguments.callee);
//Try 1: Trying to set the Alpha-Trasparency with "lineStyle"-Command:
myDrawContainer.graphics.lineBitmapStyle(5, 0xFF0000, 0,6);
//Try 2: Trying to set the Alpha-Transparency by changing the Alpha-Value of the loaded content:
myLoader.content.alpha = 0.6;
//Getting the BitmapData of the Image:
BitmapDataOfMyTexture = Bitmap(LoaderInfo(e.target).content).bitmapData
//"Using" the TBitmapData as "Color/Texture" for my Drawing:
myDrawContainer.graphics.lineBitmapStyle( BitmapDataOfMyTexture );
//Test-Drawing:
myDrawContainer.graphics.moveTo( 0, 0 );
myDrawContainer.graphics.moveTo( 500, 500 ); //-> RESULT: Textured Line WITHOUT Transparency!
}
RESULT: I get a line which uses the texture but WITHOUT Transparency.
(Update) THE SOLUTION: (Thanks to DodgerThud)
For setting/changing the Alpha-Channel of a loaded image, you don't use "lineStyle" but...
Create a NEW colorTransform-object
Then set it's "alphaMultiplier"-attribute to the specific alphaChannel
And then apply this newly created colorTransform-Object to the loaded BitmapData, by using the "colorTransform"-Method of the loaded BitmapData.
BUT :
This does NOT work with images that don't have an alpha-Channel or don't have their alpha-channel activated. Those images only get DARKER when lowering the alpha-Channel. In those cases you have to do this:
At FIRST I create NEW BitmapData-Object with "new", set its width and height to the width and height of the loaded Image and set it 3rd Argument to TRUE (= transparency: ON). So you got a "Container" which has transparency ACTIVATED.
Then you use "copyPixels" on this "Container"-Object to fill it with the pixels of the LOADED BitmapData-Object.
And right after this the above approach with the "colorTransform"-Object brings the expected result.
So HERE'S THE WORKING CODE:
myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, setTexture);
setTexture(e:Event):void
{
e.currentTarget.removeEventListener(e.type, arguments.callee);
//Getting the BitmapData of the Image:
BitmapDataOfMyTexture = Bitmap(LoaderInfo(e.target).content).bitmapData
//Create an ADDITIONAL BitmapData-Object with 3rd
//argument set to TRUE and with same width and height
//as the LOADED image:
var BMDContainerWithAlphaActivated:BitmapData;
BMDContainerWithAlphaActivated = new BitmapData(BitmapDataOfMyTexture.width, BitmapDataOfMyTexture.height, true, 0xFFFFFF);
//Copy the pixels of the loaded image into the newly created
//"BitmapData-Container with activated AlphaChannel":
BMDContainerWithAlphaActivated.copyPixels(BitmapDataOfMyTexture, new Rectangle(0, 0, BitmapDataOfMyTexture.width, BitmapDataOfMyTexture.height), new Point(0,0))
//Modify the Alpha-Value (of the NEW BitmapData-Object):
var colorChanges:ColorTransform = new ColorTransform();
colorChanges.alphaMultiplier = 0.3;
BMDContainerWithAlphaActivated.colorTransform(new Rectangle(0, 0, BitmapDataOfMyTexture.width, BitmapDataOfMyTexture.height), colorChanges);
//"Using" the (NEW) BitmapData as "Color/Texture" for my Drawing:
myDrawContainer.graphics.lineBitmapStyle( BMDContainerWithAlphaActivated );
//Test-Drawing:
myDrawContainer.graphics.moveTo( 0, 0 );
myDrawContainer.graphics.moveTo( 500, 500 ); //-> RESULT: Textured Line WITH Transparency 0.3!
}
Ah I see, this is a bit more complex than I initially thought.
Alright, looking at the documentation for lineBitmapStyle shows me that the function expects the following parameters: lineBitmapStyle(bitmap:BitmapData, matrix:Matrix = null, repeat:Boolean = true, smooth:Boolean = false)
Now, matrix, repeat and smooth will not help us here (matrix is used here for transformation, i.e. positioning, rotation etc.), but bitmap:BitmapData might. What we need to do is manipulate the BitmapData of the loaded PNG file before passing it to lineBitmapStyle. Sadly we cannot set the alpha value directly on the BMD, so we can try to colorTransform it.
This is untested code, but I think it is the right approach:
..
//store the bitmapdata in a seperate local variable
var bmd:BitmapData = LoaderInfo(e.target).content;
//create a ColorTransform Object to change the values of the BMD
var cTransform:ColorTransform = new ColorTransform();
//now here I am unsure, manipulating the alpha value of the BMD
cTransform.alphaMultiplier = 0.6;
//defining the rectangle dimensions of the bmd, we want it to be over the entire texture
var rect:Rectangle = new Rectangle(0,0,bmd.width,bmd.height);
//apply the colorTransformation on the BMD
bmd.colorTransform(rect,cTransform);
...
//the now manipulated BMD gets set as lineBitmapStyle
myDrawContainer.graphics.lineBitmapStyle(bmd);
And now that I think about it, maybe we can workaround setting the alpha value on the BMD, by creating a Bitmap first, set the alpha value there and use the bitmapdata of the Bitmap instead. Like this:
var bmd:BitmapData = LoaderInfo(e.target).content;
var bm:Bitmap = new Bitmap(bmd);
bm.alpha = 0.6;
myDrawContainer.graphics.lineBitmapStyle(bm.bitmapData);
Alright, the first snippet from above seems to be the way to do it, but the transparent value of the BitmapData needs to be true. Given that you do not directly create the BitmapData yourself and the value is false, we have quite a tricky situation here.
Another approach would be to create an additional bitmapdata that allows for transparancy and draw() the bitmapdata of the loaded image on it:
var bmdSource:BitmapData = LoaderInfo(e.target).content;
var bmd:BitmapData = new BitmapData(bmdSource.width, bmdSource.height,true,0xffffffff);
var cTransform:ColorTransform = new ColorTransform();
cTransform.alphaMultiplier = 0.6;
var rect:Rectangle = new Rectangle(0,0,bmd.width,bmd.height);
bmd.colorTransform(rect,cTransform);
//now we have a completely white bitmapdata bmd, with an alpha value of 0.6
//we draw the contents of the bmdSource onto bmd, the alpha value effect should carry over
bmd.draw(bmdSource);

make visual clone of displayObject that's nested within other displayObjects, and add the clone to the stage layer in the same location, rotation, etc

I want to be able to grab a copy of a DisplayObject that is nested within other transformed DisplayObjects (rotated, scaled, stretched objects), and be able to stamp it back into the same visual location, but on the stage layer. Essentially, being able to make a clone of a nested DisplayObject, but be able to add the clone to the stage layer, yet have it perfectly align (visually) with the original (same position, scale, rotation)
I have been working with something along the lines of:
// draw the pixels of a displayobject into a new bitmap object
var bitmapData:BitmapData = new BitmapData(nestedSprite.width, nestedSprite.height, true, 0xFFFFFF);
var bitmap:Bitmap = new Bitmap(bitmapData);
bitmapData.draw(nestedSprite);
// put the copy on the top most layer
stage.addChild(bitmap);
// position the copy to perfectly overlay the original, but on the top stage layer
var point:Point = nestedSprite.localToGlobal(new Point(0, 0));
bitmap.x = point.x;
bitmap.y = point.y;
But this only works well for displayObjects whose parents are not transformed; and for displayObjetcs that are perectly at the (0,0) origin. It falls apart for centered aligned objects or scaled parents, etc.
I am aware that I can add a matrix param to the .draw() method, as well as a clipping rectngle, and scale my bitmap afterwards, or setting the transform of one object to another, or use .transform.concatenatedMatrix, or use nestedObject.getBounds(null), or nestedSprite.getBounds(nestedSprite), etc. But I have unfortunately fallen into doing trial and error programming on this one, and with some many variables, this is never a good way to solve a programming problem.
I believe this function should work, the only extra step was offsetting the concatenated matrix so that the target would draw with its top left at (0, 0) on the Bitmap even if its origin was somewhere else. Hopefully the rest is self explanatory, but I can add more comments if anything doesn't make sense.
function createBitmapClone(target:DisplayObject):Bitmap {
var targetTransform:Matrix = target.transform.concatenatedMatrix;
var targetGlobalBounds:Rectangle = target.getBounds(target.stage);
var targetGlobalPos:Point = target.localToGlobal(new Point());
// Calculate difference between target origin and top left.
var targetOriginOffset:Point = new Point(targetGlobalPos.x - targetGlobalBounds.left, targetGlobalPos.y - targetGlobalBounds.top);
// Move transform matrix so that top left of target will be at (0, 0).
targetTransform.tx = targetOriginOffset.x;
targetTransform.ty = targetOriginOffset.y;
var cloneData:BitmapData = new BitmapData(targetGlobalBounds.width, targetGlobalBounds.height, true, 0x00000000);
cloneData.draw(target, targetTransform);
var clone:Bitmap = new Bitmap(cloneData);
// Move clone to target's global position, minus the origin offset.
clone.x = targetGlobalPos.x - targetOriginOffset.x;
clone.y = targetGlobalPos.y - targetOriginOffset.y;
return clone;
}
Unfortunately, pixelBounds seems to return an origin of (0, 0) if there are any filters on the DisplayObjects, which obviously breaks things.
Edit: Replaced target.transform.pixelBounds with target.getBounds(target.stage) as a slight improvement. This keeps the position correct if there are filters, but filters on parent DisplayObjects still won't be included, and filters on the target can overlap the edges of the Bitmap. I'm not sure if there's a simple way to work around that.
Update: Jimmi Heiserman spotted that this function is broken if the swf is scaled. Without stage.scaleMode = StageScaleMode.NO_SCALE; though, the stageWidth and stageHeight parameters seem to stay unchanged, so the only (rather hacky) workaround I've found is to add an "unscaled" test Sprite and use its concatenatedMatrix to adjust the clone's position and scale:
function createScaledBitmapClone(target:DisplayObject):Bitmap {
var targetTransform:Matrix = target.transform.concatenatedMatrix;
var targetGlobalBounds:Rectangle = target.getBounds(target.stage);
var targetGlobalPos:Point = target.localToGlobal(new Point());
// Calculate difference between target origin and top left.
var targetOriginOffset:Point = new Point(targetGlobalPos.x - targetGlobalBounds.left, targetGlobalPos.y - targetGlobalBounds.top);
// Create a test Sprite to check if the stage is scaled.
var testSprite:Sprite = new Sprite();
target.stage.addChild(testSprite);
var testMatrix:Matrix = testSprite.transform.concatenatedMatrix;
target.stage.removeChild(testSprite);
// Move transform matrix so that top left of target will be at (0, 0).
targetTransform.tx = targetOriginOffset.x * testMatrix.a;
targetTransform.ty = targetOriginOffset.y * testMatrix.d;
var cloneData:BitmapData = new BitmapData(targetGlobalBounds.width * testMatrix.a, targetGlobalBounds.height * testMatrix.d, true, 0x00000000);
cloneData.draw(target, targetTransform);
var clone:Bitmap = new Bitmap(cloneData);
// Move clone to target's global position, minus the origin offset, and cancel out stage scaling.
clone.x = targetGlobalPos.x - targetOriginOffset.x;
clone.y = targetGlobalPos.y - targetOriginOffset.y;
clone.scaleX = 1 / testMatrix.a;
clone.scaleY = 1 / testMatrix.d;
return clone;
}
Have you tried passing the parents transform into draw? draw takes a transform matrix as the second param.
If you have a handle on the parent you can use something like this
bitmapData.draw(nestedSprite, parent.transform.matrix);

To check whether Movie Clip got filled at one place

I have created a empty movie clip .
And i,m brushing on that .(i.e)Filling(Begin Fill) it with ellipse for every click .
How do I check whether it is filled at particular place .(say 400 x 400 from x=0,y=0)?
First you would need to convert it to a bitmapdata format.
Then you would use the function getPixel to query it.
import flash.display.Bitmap;
import flash.display.BitmapData;
// Lets say drawingpad is a MovieClip that is getting ellipses drawn on it.
var bitmap:Bitmap = new Bitmap(new BitmapData(stage.stageWidth,stage.stageHeight));
bitmap.draw(drawingpad);
var value:uint = bitmap.bitmapData.getPixel(400,400);
Hope this helps
I assume with "400 x 400 from x=0,y=0" you mean a the area between 0,0 and 400,400.
You need to copy the content of the rectangle of your MovieClip to a Bitmap, to be able to get information about its pixels.
// coordinates as in the question's example, could be method parameters
var x1:int=0;
var y1:int=0;
var x2:int=400;
var y2:int=400;
// bitmap with transparency, with a size of the rectangle to check
var bitmap:Bitmap = new Bitmap(new BitmapData(x2-x1, y2-y1, true, 0x00000000));
// draw the painted mc to the bitmap, displaced to upper left x and y
bitmap.draw(mc, new Matrix(1,0,0,1,-x1,-y1)));
Using getColorBoundsRect, you can check if a bitmap contains a colour, or if it contains any other colours. Now check if it contains any other colour than 'transparent' (= any other colours with an alpha value>0):
var maskColor:uint = 0xFF000000; // ignore rgb, only check alpha values
var color:uint = 0x00000000; // 'empty', fully transparent color
// get a rect containing all pixels with not fully transparent colors
var colorBoundsRect:Rectangle = bitmap.getColorBoundsRect(maskColor, color, false);
if (colorBoundsRect.width == 0 && colorBoundsRect.height == 0){
trace("empty"); // rect contains no visible pixels
}
Assuming the places where it's not filled are transparent, a simple hitTestPoint should do the trick:
myObj.hitTestPoint( 400.0, 400.0, true ); // true means we use the pixels of the shape and not the bounding box
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/DisplayObject.html#hitTestPoint()

Bitmap conversion - Creating a transparent + black image from a B&W source

I have a whole bunch of jpg files that I need to use in a project, that for one reason or another cannot be altered. Each file is similar (handwriting), black pen on white BG. However I need to use these assets against a non-white background in my flash project, so I'm trying to do some client-side processing to get rid of the backgrounds using getPixel and setPixel32.
The code I am currently using currently uses a linear comparison, and while it works, the results are less than expected, as the shades of grey are getting lost in the mix. Moreso than just tweaking my parameters to get things looking proper, I get the feeling that my method for computing the RGBa value is weak.
Can anyone recommend a better solution than what I'm using below? Much appreciated!
private function transparify(data:BitmapData) : Bitmap {
// Create a new BitmapData with transparency to return
var newData:BitmapData = new BitmapData(data.width, data.height, true);
var orig_color:uint;
var alpha:Number;
var percent:Number;
// Iterate through each pixel using nested for loop
for(var x:int = 0; x < data.width; x++){
for (var y:int = 0; y < data.height; y++){
orig_color = data.getPixel(x,y);
// percent is the opacity percentage, white should be 0,
// black would be 1, greys somewhere in the middle
percent = (0xFFFFFF - orig_color)/0xFFFFFF;
// To get the alpha value, I multiply 256 possible values by
// my percentage, which gets multiplied by 0xFFFFFF to fit in the right
// value for the alpha channel
alpha = Math.round(( percent )*256)*0xFFFFFF;
// Adding the alpha value to the original color should give me the same
// color with an alpha channel added
var newCol = orig_color+alpha;
newData.setPixel32(x,y,newCol);
}
}
var newImg:Bitmap = new Bitmap(newData);
return newImg;
}
Since it's a white background, blendMode may give you a better result.