AS3: Most efficient way to create a tinted copy of a BitmapData - actionscript-3

I have a BitmapData which is generated by drawing a DisplayObject. I would like to create a pure white silhouette copy of that BitmapData. It seems there would be two approaches to do so:
Apply a ColorTransform to the DisplayObject and then have a new BitmapData with the same dimensions draw it.
Create a new BitmapData, then copyPixels the original, then apply a ColorTransform via BitmapData.colorTransform.
Neither BitmapData will be displayed inside a Bitmap, so applying a ColorTransform to that is not an option. They will instead be turned into a Starling Texture.
Which method would be more efficient? I would run some benchmarks myself, but I'm concerned the answer would be dependent on the size and complexity of the DisplayObject being drawn. Perhaps the copyPixels method would be better when the size is small and worse if it's large, and the draw method would be better if the DisplayObject to be drawn is simple and worse if it's complex. Also, are the copyPixels and BitmapData.colorTransform methods done on the GPU? If so, I suppose that would tilt things in their favor.

So I did some benchmarks, and in the best possible scenario for the draw method, where the DisplayObject to be drawn was simply a colored rectangle, draw was always faster than copyPixels by around 33%, regardless of size. Then I tried using some complex DisplayObjects with lots of shapes inside, and in those cases, draw was 300% to 400% slower. So the answer is "it depends". But I have to say, using the draw method has an upside potential of a 33% speed increase at most, but an enormous downside potential if the DisplayObject is complex.
I did some additional testing of the copyPixels/colorTransform method against the getPixels/setPixels method, and remarkably the getPixels/setPixels method was 20x slower! Here is my copyPixels method:
var start_time:int = getTimer();
var s1:S1 = new S1(); //S1 is simply a 100x100 red circle
var bmd1:BitmapData = new BitmapData(s1.width,s1.height,true,0x00000000);
bmd1.drawWithQuality(s1,null,null,null,null,false,StageQuality.HIGH);
var bmd2:BitmapData;
var sourceRect:Rectangle = new Rectangle(0,0,bmd1.width,bmd1.height);
var point00:Point = new Point();
var colorTransform:ColorTransform = new ColorTransform(0,0,0,1,255,255,255,0);
var w:uint = bmd1.width,
h:uint = bmd1.height,
pixelHex:uint,
pixelAlpha:uint;
var ba:ByteArray;
for (var i:uint=0; i<1000; i++) {
bmd2 = new BitmapData(w,h,true,0x00000000);
bmd2.copyPixels(bmd1,sourceRect,point00);
bmd2.colorTransform(sourceRect,colorTransform);
}
trace("execution time: ", getTimer()-start_time); //execution time: 74
And here is my getPixels method for loop:
for (var i:uint=0; i<1000; i++) {
ba = bmd1.getPixels(sourceRect);
ba.position = 0;
while (ba.bytesAvailable > 0) {
pixelHex = ba.readUnsignedInt();
pixelAlpha = pixelHex >>> 24;
if (pixelAlpha > 0) {
ba.position -= 4;
ba.writeUnsignedInt((pixelAlpha<<24)|0xffffff);
}
}
ba.position = 0;
bmd2 = new BitmapData(w,h,true,0x00000000);
bmd2.setPixels(sourceRect,ba);
}
trace("execution time: ", getTimer()-start_time); //execution time: 1349

Related

as3 - converting bitmap back to vector in view

So I found a V-CAM source, I am now using it and quite happy however, is it possible to untoggled bitmap when the objects that are bitmapped are viewed by the cam? For instance, lets say I have a vector movieclip with a bunch of vector art, I toggle export as bitmap on the movieclip from my IDE, now would it be possible to add on to my VCAM, that everything in its view (it resizes stage) untoggles or redraws back to vector, while the rest of map/movieclip is still in bitmap? And as the VCAM moves away, what was shifted from bitmap to vector gets shifted back to bitmap?
var camColor: ColorTransform = new ColorTransform();
var parentColor: ColorTransform = new ColorTransform();
var cX: Number;
var cY: Number;
var sX: Number;
var sY: Number;
this.visible = false;
var oldMode: String = stage.scaleMode;
stage.scaleMode = StageScaleMode.EXACT_FIT;
cX = stage.stageWidth / 2;
cY = stage.stageHeight / 2;
sX = stage.stageWidth;
sY = stage.stageHeight;
stage.scaleMode = oldMode;
camColor = this.transform.colorTransform;
parentColor = this.parent.transform.colorTransform;
camControl(new Event(Event.ENTER_FRAME));
addEventListener(Event.ENTER_FRAME, camControl);
addEventListener(Event.REMOVED, resetStage);
function camControl(event: Event): void {
camColor = this.transform.colorTransform;
parent.transform.colorTransform = camColor;
var xScale: Number = sX / this.width;
var yScale: Number = sY / this.height;
parent.x = cX - (this.x * xScale);
parent.y = cY - (this.y * yScale);
parent.scaleX = xScale;
parent.scaleY = yScale;
}
function resetStage(event: Event): void {
removeEventListener(Event.ENTER_FRAME, camControl);
parent.transform.colorTransform = parentColor;
parent.scaleX = 1;
parent.scaleY = 1;
parent.x = 0;
parent.y = 0;
}
I think you'd better use another camera with higher bitmap dimensions (2x-4x) to render those scenes from vector that you feel are too pixelized. In terms of export, just export the character's bitmaps 2x-4x larger, or you can just have it as a vector somewhere in your app, maybe hidden, and do realtime render when needed, or plain have it in your display list as a vector and not a bitmap.
In case you need to have some complex vector form into a bitmap-based engine, you can use realtime bitmap drawing of a single source in various postures/rotations, then use those rendered bitmaps to get performance. Check the game "Enigmata: Stellar War" for this technique, how does it look in the process (hint: when it says "Loading boss" it does all the render behind the scenes).
Getting a vectorized source form bitmaps is a lot more processor consuming than having a ready-made vectorized source stored somewhere. Also you won't get your original vector restored in exact form, as converting a vector to a bitmap is a lossy transformation.

Drawing BitmapData to Sprite still using bitmapdata itself?

I'm calling images from library to be use. But because I will be constantly add and remove the images, so I tried to copy the bitmapdata to a sprite and reuse the sprite.
private function loadImage():void {
for (var i:int = 1; i < typeAmount + 1; i++) {
SlotClass = Main.queue.getLoader('main_uiMC').getClass('slot' + i + 'bmp') as Class;
bmpDSlot = new SlotClass() as BitmapData;
bmpDSlotV.push(bmpDSlot)
}
}
private function bitmaping():void {
for (var i:int = 1; i < typeAmount + 1; i++) {
slotS = new Sprite()
slotS.graphics.beginBitmapFill(bmpDSlotV[i - 1], new Matrix, false, true)
slotS.graphics.drawRect(0, 0, bmpDSlotV[i - 1].width, bmpDSlotV[i - 1].height);
slotS.graphics.endFill();
bmpV.push(slotS)
}
Every time I duplicate the sprite, flashdevelop's profiler showed that the bitmapdata is being added as well. Even when I use removeChild to remove the Sprite, the memory usage won't decrease.
Is there a way to better copy the content of the bitmapdata, and can be completely remove when I remove the sprite?
*i will still be using the image, just that on that particular round i would like to remove the sprite that has the image.
You should use a single BitmapData object, create numerous Bitmap objects and operate these.
private function bitmaping():void {
for (var i:int = 1; i < typeAmount + 1; i++) {
slotS = new Bitmap(bmpDSlotV[i-1]);
bmpV.push(slotS);
}
Creating another Bitmap like this does not add another BitmapData, it uses existing one as reference.
Well your choice is to reuse the BitmapData. And because you need multiple instances of the same image, you need to create new one for each object that you would like to add. And this would be a Bitmap with the old BitmapData:
var originalBMD:BitmapData = instantiateItSomehow();
var first:Bitmap = new Bitmap(originalBMD);
var second:Bitmap = new Bitmap(originalBMD);
This way you would reuse the BitmapData and I think the memory will increase only because of the Bitmap objects, but the data itself should not be duplicated.

How do I change the hitbox of a movie clip in actionscript 3 to not use the bounding box?

We have an object (mc_robert) that collides with another object (mc_left). When the collision occurs, we have an action take place. What we're trying to figure out is how to change the collision box of the object (mc_robert) so that the objects overlap when the collision detects. We don't want the default collision box of the object (mc_robert). Any help that can be offered would be greatly appreciated.
This is what we have for our code currently:
var numX:Number = 0;
var numY:Number = -2;
addEventListener(Event.ENTER_FRAME,loop);
function loop(e:Event)
{
mc_robert.y += numY;
mc_robert.x += numX;
if (mc_robert.hitTestObject(mc_left))
{
numX = -2;
numY = 0;
mc_robert.rotation = -90;
}
}
For pixel perfect collision convert the shapes into BitmapData and use the BitmapData's Hit-test method.
If you don't want to do the above approach you can use hitTestPoint and create multiple points in a for loop to do the collision.
eg: Haven't tested the following code but the concept is there. This will create a bounding box, or you can use some trig to collide from a circle or custom create the points.
for(var y:int = 0;y<2;y++){
for(var x:int=0;x<2;x++){
var w:int = mc2.width*x;
var h:int = mc2.height*y;
if(mc1.hitTestPoint(mc2.x+w, mc2.y+h, true)){
trace("collides");
}
}
}
How ever the solution I would use for the best accuracy would be a BitmapData collision.

Actionscript 3 - How To Check A Random Pixel Of A Sprite

I have a sprite that is drawn in random and complicated way. Pixels would be either transparent or not. And now I need to check if pixel new Point(10, -5) is transparent or not.
How can I do that ?
This is not for collision detection.
I also draw in the negative area of the sprite graphics. It is not centered.
Solution:
The main problem was the drawing in negative area. I figured it out myself:
var bitmapData: BitmapData = new BitmapData(sprite.width, sprite.height, true, 0x0);
var rect: Rectangle = sprite.getBounds(sprite);
var mat: Matrix = new Matrix();
mat.translate(-rect.left, -rect.top);
bitmapData.draw(sprite, mat);
bitmapData.getPixel32(xCoordToTest - rect.left, yCoordToTest - rect.top);
// etc
Create new BitmapData object and draw your sprite onto it. Then check desired BitmapData pixel.
var bitmapData:BitmapData = new BitmapData(mySprite.width,mySprite.height,true,0x00000000);
bitmapData.draw(mySprite);
bitmapData.getPixel32(10,5);
Just like SzRaPnEL says, draw your sprite into a BitmapData object with the 3rd parameter set to true (enabling transparency).
Then...
var pixelValue:uint = bitmapData.getPixel32(xCoordToTest, yCoordToTest);
var alphaValue:uint = pixelValue >> 24 & 0xFF;
According to the BitmapData online docs, that should work...
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html#getPixel32()

Drawing a bitmap based on a transformed movieclip

I'm drawing bitmaps of movieclips which I then feed into my hittest function to test for collisions. However, I'm not quite sure how i would add to the code below to take into account and draw bitmaps for movieclips which have been scaled and/or rotated. The below code obviously only works for non-transformed movieclips. I've included in comments code which i've already tried but to no success.
When adding the drawn bitmap to the stage, no matter whether the movieclip in question is transformed or not, the drawn bitmap is "cut off" and incorrectly drawn - it appears to only draw a section of it. However, this does not particularly affect the collision testing for the non-transformed movieclips, but has an adverse effect on transformed movieclips.
All of the movieclips I want to be drawn have been created through the graphics property.
//for example:
var mymc:MovieClip = new MovieClip();
var g:Graphics = mymc.graphics;
g.moveTo(0,0);
g.lineTo(17.5,0);
g.lineTo(8.75,17.5);
g.lineTo(-8.75,17.5);
g.lineTo(0,0);
main code:
for each(var mc:MovieClip in impassable) {
//var bMatrix:Matrix = new Matrix();
//bMatrix.scale(mc.scaleX, mc.scaleY);
//bMatrix.rotate(mc.rotation * (Math.PI/180));
var bData:BitmapData = new BitmapData(mc.width, mc.height, true, 0);
//bData.draw(mc, bMatrix);
bData.draw(mc);
var bitmap:Bitmap = new Bitmap(bData);
bitmap.x = mc.x;
bitmap.y = mc.y;
var HitTest:Number = newCollision(bitmap, centerX, centerY, 13.7);
Any thoughts? thanks
This function will create a BitmapData clone of a DisplayObject, taking into account its transform matrix, though it doesn't take into account bitmap filters. (Based on this answer.)
function createBitmapClone(target:DisplayObject):BitmapData {
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);
return cloneData;
}
When you call successive transforms on a Matrix, the ordering is very important and can really mess things up.
Luckily there is a helper method that allows you to specify translation, rotation and scaling in one go and avoid those issues - createBox
For your case, something like this:
var matrix:Matrix = new Matrix();
matrix.createBox(mc.scaleX, mc.scaleY, mc.rotation*Math.PI/180, 0, 0);
(the two zeros are for x and y translation)