How do you add stroke to text in a GPU rendered Mobile app? [duplicate] - actionscript-3

Unfortunately the filters do not work (drop shadow, glow) in GPU mode. I'm looking for an opportunity to use these effects to text in this mode. I will welcome any advice.

As Astraport mentions, you'll need to draw the textfield out to a bitmapData every time you update the text using bitmapData.draw().
If you use textField.getBounds to determine the size of the bitmapData you need, the resulting bounds rectangle will not include the extra size due to the filter (e.g. a DropShadowFilter sticks out the side of the textbox by certain pixels depending on the 'distance' and 'blur'). To ensure that you include the filters when you draw the bitmap, you'll also need to use bitmapData.generateFilterRect() to get the correct size rect.
Code snippet (untested, but general idea):
// Remember the transform matrix of the text field
var offset : Matrix = myTextField.transform.matrix.clone();
// Get the bounds of just the textfield (does not include filters)
var tfBounds : Rectangle = myTextField.getBounds( myTextField.parent );
// Create a bitmapData that is used just to calculate the size of the filters
var tempBD : BitmpaData = new BitmapData( Math.ceil(tfBounds.width), Math.ceil(tfBounds.height) );
// Make a copy of the textField bounds. We'll adjust this with the filters
var finalBounds : rectangle = tfBounds.clone();
// Step through each filter in the textField and adjust our bounds to include them all
var filterBounds : rectangle;
for each (var filter : BitmapFilter in myTextField.filters) {
filterBounds = tempBD.generateFilterRect( tfBounds, filter );
finalBounds.left = Math.min( finalBounds.left, filterBounds.left );
finalBounds.right = Math.max( finalBounds.right, filterBounds.right );
finalBounds.top = Math.min( finalBounds.top, filterBounds.top );
finalBounds.bottom = Math.max( finalBounds.bottom, filterBounds.bottom );
}
// Now draw the textfield to a new bitmpaData
var textFieldBD : BitmpaData = new BitmapData( Math.ceil(finalBounds.width), math.ceil(finalBounds.height) );
offset.tx = -finalBounds.x;
offset.ty = -finalBounds.y;
textFieldBD.draw( myTextField.parent, offset, myTextField.transform.colorTransform );
// Create a bitmap and add the bitmap data. Note: normally you would create a
// bitmap once and just update the bitmpaData
var bitmap : Bitmap = new Bitmap();
myTextField.parent.addChild( bitmap );
// Position the bitmap in same place as textField
bitmap.bitmapData = textFieldBD;
bitmap.x = myTextField.x - finalBounds.x;
bitmap.y = myTextField.y - finalBounds.y;
myTextField.visible = false;

Here is how to convert ANY DisplayObject to a Bitmap - useful for "restoring" filter effects in AIR GPU mobile rendermode. This is Pixelthis's solution, fixed, optimized and tested:
// => 'bitmap' must belong to the same parent as 'obj'. 'obj' should be invisible.
static public function Update(obj:DisplayObject, bitmap:Bitmap):void {
//trace("CacheToBmp",obj.name);
// Remember the transform matrix of the text field
var offset:Matrix = obj.transform.matrix.clone();
// Get the bounds of just the textfield (does not include filters)
var bounds:Rectangle = obj.getBounds(obj);
// Create a bitmapData that is used just to calculate the size of the filters
var tempBD:BitmapData = new BitmapData( Math.ceil(bounds.width), Math.ceil(bounds.height), false );
bounds.width = obj.width;
bounds.height = obj.height;
// Make a copy of the textField bounds. We'll adjust this with the filters
var finalBounds:Rectangle = new Rectangle(0,0,bounds.width,bounds.height);
// Step through each filter in the textField and adjust our bounds to include them all
var filterBounds:Rectangle;
for each (var filter:BitmapFilter in obj.filters) {
filterBounds = tempBD.generateFilterRect( tempBD.rect, filter );
finalBounds = finalBounds.union(filterBounds);
}
finalBounds.offset(bounds.x,bounds.y);
finalBounds.x = Math.floor(finalBounds.x);
finalBounds.y = Math.floor(finalBounds.y);
finalBounds.width = Math.ceil(finalBounds.width);
finalBounds.height = Math.ceil(finalBounds.height);
// Now draw the textfield to a new bitmpaData
var data:BitmapData = new BitmapData( finalBounds.width, finalBounds.height, false, 0 );
offset.tx = -finalBounds.x;
offset.ty = -finalBounds.y;
data.drawWithQuality( obj, offset, obj.transform.colorTransform, obj.blendMode, null, true, StageQuality.HIGH );
bitmap.bitmapData = data;
// Position the bitmap in same place as 'obj'
bitmap.x = obj.transform.matrix.tx + finalBounds.x;
bitmap.y = obj.transform.matrix.ty + finalBounds.y;
}

The basic idea is to apply the filters as normal and then draw the display object to a bitmapdata and add the bitmap to the stage. See http://forums.adobe.com/message/3934192 for an example.
If the text that you are applying this to is static it should be easy enough to do, but if you want to apply this dynamic text (for example, a score counter that will change frequently, or text that is user-editable) I imagine it may start to get annoying, but I don't know of any other solution.

Related

ActionScript3 - Drawing video(webcam) output on BitmapData with shifted position

I'm trying to draw a video(webcam) output on a Bitmapdata, it works if the position of the video is at (0,0) but when I shift the video to (200,0), the drawn output also shifted.
The code is pretty simple
// omitted for brevity
var videoContainer:Sprite = new Sprite();
// Video is added
videoContainer.addChild(video)
// position is offsetted
videoContainer.x = 200;
videoContainer.y = 200;
this.addChild(videoContainer)
// Generate Bitmap
var bd:BitmapData = new BitmapData(video.width, video.height, false, 0x0);
bd.x = video.x;
bd.y = video.y + video.height;
this.addChild(bd);
this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(e:Event):void {
// start drawing
bd.lock()
bd.draw(video);
bd.unlock();
}
I've tried to reposition it with a matrix, but apparently the entire original source had changed.
Is there a way to ensure that the output remains at (0,0) even if the videoContainer were offsetted from (0,0)?
First of all, it looks like you have a type in your code:
var bd:Bitmap = new BitmapData(video.width, video.height, false, 0x0);
It should throw an error, because an instance of BitmapData couldn't be assigned to a variable with the type Bitmap. But, I guess, it's just a type.
Secondly, it looks like your bitmap is shifted, because you shift it =)
bd.x = video.x;
I would advice to remove/comment this code.
Upd:
Also, please, could you try the next method for getting a BitmapData instance of the DisplayObject with applied transform changes:
function getBitmapDataFromDisplayObject(
displayObject:DisplayObject,
transparent:Boolean = true,
fillColor:uint = 0x00000000,
smoothing:Boolean = true,
customRect:Rectangle = null):BitmapData
{
var bitmapData:BitmapData;
try
{
var tempRect:Rectangle = displayObject.getRect(displayObject);
if (customRect)
{
tempRect = customRect;
}
var matrix:Matrix = new Matrix();
matrix.tx = -tempRect.x;
matrix.ty = -tempRect.y;
//bitmapData = new BitmapData(tempRect.right, tempRect.bottom, transparent, fillColor);
bitmapData = new BitmapData(tempRect.width, tempRect.height, transparent, fillColor);
bitmapData.draw(displayObject, matrix, null, null, null, smoothing);
}catch (error:Error)
{
bitmapData = null;
}
return bitmapData
}

How do I stroke a knocked out object?

In HTML5 canvas I'm trying to knock out an object with another object and stroke the final product of this operation. Is that possible at all? I'm using the following code with no success:
var tcan = document.getElementById('test');
var tctx = tcan.getContext('2d');
tctx.beginPath();
tctx.fillStyle = '#F00';
tctx.fillRect(0,0,70,70);
tctx.globalCompositeOperation = "destination-out";
tctx.fillRect(20,20,70,70);
tctx.closePath();
tctx.strokeStyle = '#FF0'; // expecting the final product to have a yellow stroke
tctx.stroke();
A Demo: http://jsfiddle.net/m1erickson/x8Maf/
You can use source-atop compositing to draw your yellow stroke only atop your existing knockout:
tctx.beginPath();
tctx.fillStyle = '#F00';
tctx.fillRect(0,0,70,70);
// knock-out compositing
tctx.globalCompositeOperation = "destination-out";
tctx.fillRect(20,20,70,70);
tctx.closePath();
// composite where new drawings appear only where overlapping existing
tctx.globalCompositeOperation = "source-atop";
tctx.strokeStyle = '#FF0'; // expecting the final product to have a yellow stroke
tctx.lineWidth=2;
tctx.strokeRect(20,20,70,70);
// restore the default compositing
tctx.globalCompositeOperation = "source-over";
A couple of hints:
You don't need beginPath if you use fillRect or strokeRect (these commands automatically beginPath for you);
All canvas strokes are half-inside and half-outside the specified dimensions. So fillRect(20,20,70,70) will actually stroke from 19.50 to 20.50.
[ Addition to answer: Here's how to stroke the knockout shape. ]
var tcan = document.getElementById('test');
var tctx = tcan.getContext('2d');
tctx.lineWidth=2;
tctx.strokeStyle = '#FF0';
tctx.fillStyle = '#F00';
tctx.fillRect(50,50,70,70);
tctx.strokeRect(50,50,70,70);
// knock-out compositing
tctx.globalCompositeOperation = "destination-out";
tctx.fillRect(70,70,70,70);
tctx.closePath();
// composite where new drawings appear only where overlapping existing
tctx.globalCompositeOperation = "source-atop";
tctx.lineWidth=4;
tctx.strokeRect(70,70,70,70);
// restore the default compositing
tctx.globalCompositeOperation = "source-over";

Remove randomly generated sprites with Mouse.Event

I am making a game for my class where different chemical elements are generated as sprites at the top of the screen and then fall down. Different types are made and I want students to mouse over specific types depending on where they are in the game.
My question is how to write the function to remove them when they are correctly selected? I've tried a lot of different ways but am having a lot of trouble. An example of the code that I wrote to make each element is below and then I have a separate function to move down all of the sprites created.
var spriteArray:Array = new Array();
var halogenArray:Array = new Array("F", "Cl", "Br", "I");
var rndnum:Number = Math.random();
//Halogens
if (rndnum < 0.05)
{
var halo:Sprite = new Sprite();
halo.graphics.beginFill(0x00FF00, 1);
halo.graphics.drawCircle(7.5, 7.5, 15);
halo.graphics.endFill();
halo.addEventListener(MouseEvent.MOUSE_OVER, removeElement);
halo.x = Math.random()*500 + 50;
halo.y = -18;
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
textField.height = 30;
textField.x = (15 - textField.textWidth)/2; // center it horizontally
textField.y = (15 - textField.textHeight)/2; // center it vertically
halo.addChild(textField);
spriteArray.push(halo);
addChild(halo);
}
At what point are you struggling?
I am assuming it is in determining the types of the halogens.
In your remove function I assume you have the desired type already figured out, you would then compare it to
element.getChildAt(0).text
and you would get the element by either looping across every element in the spriteArray, or using the mouseEvent's target
My suggestion is to use a halogen Class to contain the grapics & textfield, and a vector to hold the objects. It would then be easier to get the type rather than searching the anonymous children of the sprite.
I believe you are looking for something like this:
//give your textfields a name, it isn't totally necessary as we can do getChildAt(0)
//but it's more readable, and if you decide to add more children before you
//add the text field, then this will still work
var textField = new TextField();
textField.text = halogenArray[int(Math.random()*4)];
textField.width = 30;
...
textField.name = "haloTx"; //for tracking later
//assuming you have some variable set to the correct answer
var correctAnswer:String = "F";
function removeElement( e:MouseEvent ):void {
var element:TextField = ( e.target as Sprite ).getChildByName( "haloTx" );
//if we have the correct element, remove from parent and list
if ( element && element.text == correctAnswer ) {
var index:int = spriteArray.indexOf( e.target as Sprite );
removeChild( spriteArray.splice( index, 1 )[0] );
}
}
Although #VBCPP is right, doing that in a separate class is definitely the best way organizationally. Which might look something like:
class ElementSprite extends Sprite {
public var textField:TextField;
//pass shapeArgs as string, so say a circle at x=7.5, y=7.5, and radius=15 -- shapeArgs = "7.5, 7.5, 15"
public function ElementSprite( element:String, drawShape:String="Circle", shapeArgs:String="7.5, 7.5, 15", fillColor:uint=0x00FF00 ) {
//set textfield properties etc. or textFormat
textField = new TextField();
textField.text = element;
addChild( textField );
//if you passed some arguments to draw our shape
if ( shapeArgs != "" ) {
graphics.beginFill( fillColor );
graphics[ "draw" + drawShape ].apply( this, shapeArgs.split( "," ) );
}
}
public function get currentElement():String { return textField.text }
}
Then you would use it like so in your if statement if (rndnum < 0.05):
var elementSprite:ElementSprite = new ElementSprite( "A" );
//elementSprite.x = set your x;
//elementSprite.y = set your y;
addChild(elementSprite);
That would be replacing all your current code in that if statement. This is all a working example, if you have an questions feel free to comment.

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)

Blending objects with eachother but not with background actionscript 3

I have a number of objects (represented as DisplayObjects) that i wish to blend with eachother.
However behind these objects there is a background that i do not want to involve in the blending.
So basically i want to blend these objects with eachother and afterwards use the result of this blending as a new DisplayObject (for example to put it on top of a randomly colored background).
So what i have is:
var obj1:DisplayObject = getFirstObj();
var obj2:DisplayObject = getSecObj();
var background:DisplayObject = getBackground();
obj1.blendMode = BlendMode.ADD;
obj2.blendMode = BlendMode.ADD;
A first attempt i tried was putting these objects into a common DisplayObjectContainer hoping that blending mode would only be relative to all objects contained by the same DisplayObjectContainer, but this does not seem to be the case.
var objectsPool:Sprite = new Sprite();
objectsPool.addChild( obj1 );
objectsPool.addChild( obj2 );
addChild( background );
addchild( objectsPool );
So that diddent get me anywhere.
Any help is appreciated.
EDIT: changed DisplayObjectContainer to Sprite in the last code snippet
If you put the objects into a container, and remove it from the stage, you can then draw it with the BitmapData class and create a new Bitmap object representing the combination. This will have a transparent background, and it's blendMode will be normal, allowing you to use it on the background.
var obj1:DisplayObject = getFirstObj();
var obj2:DisplayObject = getSecObj();
var background:DisplayObject = getBackground();
obj1.blendMode = BlendMode.ADD;
obj2.blendMode = BlendMode.ADD;
var objectsPool:DisplayObjectContainer = new DisplayObjectContainer();
objectsPool.addChild( obj1 );
objectsPool.addChild( obj2 );
var bmd:BitmapData = new BitmapData(objectsPool.width,objectsPool.height,true,0);
bmd.draw(objectsPool);
var drawnObject:Bitmap = new Bitmap(bmd);
addChild( background );
addchild( drawnObject );
(untested code, good luck)
Rather going to the effort of drawing a Bitmap yourself, there are options that cause Flash to rasterize a layer and it's children automatically. Try:
container.cacheAsBitmap = true;
or try:
container.blendMode = "layer";
or try:
container.filters = [new GlowFilter(0,0,0,0)];
Any of those options should cause the children to render into a Bitmap under the hood, invalidating their individual blend modes/effects on the background.