I'd like to apply a pixelate filter to a bitmap. Is there a way to do it without using pixel bender?
You should be able to achieve this with the BitmapData class and its getPixel()/setPixel methods. As for your effect specifically, you could calculate an average color for a given group of pixels, and apply the same color to all of them.
More on pixel manipulation : http://help.adobe.com/en_US/as3/dev/WS5b3ccc516d4fbf351e63e3d118a9b90204-7d64.html
A very simple way to do this is by redrawing a bitmap twice. This code assumes that blockSize is chosen so that your intermediate BitmapData (smaller) aligns with the pixel grid. For example, if your source was 100x100 pixels, a blockSize of 2 will make the intermediate BitmapData 50x50 pixels. Anything more complicated probably requires Math.round() and some fudging.
// Assumes that source.width / blockSize has no remainder
// Same with source.height / blockSize
public function getMosaic( source:BitmapData, blockSize:int ):BitmapData
{
var bitmap:Bitmap = new Bitmap( source );
bitmap.smoothing = true; // blends pixels values
var smaller:BitmapData = new BitmapData( source.width / blockSize,
source.height / blockSize );
var matrix:Matrix = new Matrix();
matrix.scale( 1 / blockSize, 1 / blockSize );
smaller.draw( bitmap, matrix );
bitmap = new Bitmap( smaller );
// Avoid "bitmap.smoothing = true" here to keep it blocky
var blocky:BitmapData = new BitmapData( source.width, source.height );
matrix.invert(); // gives the opposite effect from before
blocky.draw( bitmap, matrix );
smaller.dispose(); // always dispose BitmapData no longer needed
return blocky;
}
Related
I've created a World with earth gravity and I place an entity in the scene (contains a sprite and a Body) and it falls down slowly like a balloon.
Here's how I set the World:
world = new World(new Vector2(0, -GRAVITY_EARTH), true);
and here's the relevant Box2D code for the Body etc:
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(positionX, positionY);
// Create our body in the world
body = world.createBody(bodyDef);
// Grab the first idle sprite to use as initial
Sprite sprite = idleSprites.get(0);
// Create a box shape to represent our hit box
PolygonShape box = new PolygonShape();
box.setAsBox(sprite.getWidth() / 2f, sprite.getHeight() / 2f);
// Create a fixture definition to apply our shape
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = box;
fixtureDef.density = 1f; // Give it full density
fixtureDef.friction = 0f; // Give it no friction
fixtureDef.restitution = 0f; // Make it not bouncy
// Create our fixture and attach it to the body
fixture = body.createFixture(fixtureDef);
// Remember to dispose of any shapes after you're done with them!
// BodyDef and FixtureDef don't need disposing, but shapes do.
box.dispose();
and how I draw the sprite:
TextureRegion keyFrame = idleAnimation.getKeyFrame(stateTimeSeconds, true);
Vector2 position = body.getPosition();
batch.draw(keyFrame, position.x - keyFrame.getRegionWidth() / 2f, position.y - keyFrame.getRegionHeight() / 2f);
and the relevant code in the render() method:
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
final float deltaTime = Gdx.graphics.getDeltaTime();
camera.update();
spriteBatch.setProjectionMatrix(camera.combined);
spriteBatch.begin();
spriteBatch.draw(sky, 0, 0);
tim.animate(spriteBatch, deltaTime);
spriteBatch.draw(floor, 0, 0);
spriteBatch.end();
// Render physics for debug
debugRenderer.render(world, camera.combined);
// Run physics
doPhysicsStep(deltaTime);
}
private void doPhysicsStep(float deltaTime) {
// fixed time step
// max frame time to avoid spiral of death (on slow devices)
float frameTime = Math.min(deltaTime, 0.25f);
accumulator += frameTime;
while (accumulator >= TIME_STEP) {
world.step(TIME_STEP, VELOCITY_ITERATIONS, POSITION_ITERATIONS);
accumulator -= TIME_STEP;
}
}
I've tried changing the density of the fixture, and I've tried changing the gravity value, and I've tried changing the TIME_STEP and nothing is having an effect. The body just falls down slowly like a balloon.
It looks to me like you're using pixels as your units, box2d treats every unit as a meter and so you're hitting the internal limit of 2.0 units per time step, see http://www.iforce2d.net/b2dtut/gotchas. You can get around this by setting up your camera in world units instead of pixels, you have to scale all your sprites and positions to fit into world units instead of pixels though.
Something like this may do:
float w = (float) Gdx.graphics.getWidth();
float h = (float) Gdx.graphics.getHeight();
camera = new OrthographicCamera(30, 30 * (h / w));
the way the camera is set up here allows the height of the viewport to be variable based on the screens' aspect ratio.
Then to setup the sprite change it by a set factor
sprite.setSize(sprite.getWidth / PIX2M, sprite.getHeight / PIX2M);
where PIX2M is a static field defining how many pixels are a meter in box2d
Alternatively you can set the dimensions of the sprite explicitly to a value which makes physical sense and with the aspect ratio of the original image(my personal preference) . So an image of a person which is 100 x 500 for example could be set like this.
sprite.setSize(.4f, 2f);
meaning the person is 2 meters high and .4 meters wide. Also with this method you don't need a PIX2M conversion factor and you will always know the exact size of your body. Since you set the camera to a specific number of world units, 30 in this case, the sprite will take up the same amount of room on the screen no matter the resolution of the display.
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.
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()
I have a shape drawn using Shape.graphics.drawRoundRect() with a lineStyle applied. I'm trying to capture that shape as a Bitmap using BitmapData.draw() but I am running into an issue with the stroke. See below:
As you can see, the stroke gets clipped when using draw() (and drawWithQuality()). The line is drawn centered on the object, so a thickness of 4 (as I used in the example) has 2 pixels outside the shape's area and 2 pixels inside it. draw() captures everything from (0,0) to (BitmapData.width,BitmapData.height), it seems, so everything to the left and top of (0,0) is lost. I tried to compensate using the clipRect option, but that just evened out the clipped border, ironically.
Any idea how to capture that remaining data?
As a more general solution, you can get the bounds of the object in its own coordinate space, and use that to set the size of the BitmapData and offset the draw():
import flash.geom.Matrix;
import flash.geom.Rectangle;
const thickness:int = 4;
const radius:int = 10;
const size:int = 100;
var shape:Shape = new Shape();
shape.graphics.lineStyle( thickness, 0xaaaaaa );
shape.graphics.beginFill( 0xdddddd );
shape.graphics.drawRoundRect( 0, 0, size, size, radius, radius );
shape.graphics.endFill();
addChild( shape );
var bounds:Rectangle = shape.getBounds(shape);
var m:Matrix = new Matrix();
m.translate(-bounds.left, -bounds.top);
var bmp1:Bitmap = new Bitmap();
bmp1.bitmapData = new BitmapData( bounds.width, bounds.height, true, 0 );
bmp1.x = 310;
bmp1.y = 100;
addChild( bmp1 );
bmp1.bitmapData.draw( shape, m );
And of course, the second I post the question, I figure out the way to do it. You have to offset your shape to match the line outside of the bounds and compensate for the additional size the line adds to the shape when using draw
const thickness:int = 4;
const radius:int = 10;
const size:int = 100;
var shape:Shape = new Shape();
shape.graphics.lineStyle( thickness, 0xaaaaaa );
shape.graphics.beginFill( 0xdddddd );
shape.graphics.drawRoundRect( thickness / 2, thickness / 2, size, size, radius, radius );
shape.graphics.endFill();
addChild( shape );
var bmp1:Bitmap = new Bitmap();
bmp1.bitmapData = new BitmapData( size + thickness, size + thickness, true, 0 );
bmp1.x = 310;
bmp1.y = 100;
addChild( bmp1 );
bmp1.bitmapData.draw( shape );
See the outcome here (you can ignore the clip rect one):
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)