Scaling and cropping an image with fixed dimensions - actionscript-3

I want to display an image, and it should be transformed like this:
If for example, my original image is 200x300 pixels in size, I want it to have a width of 150, and then scale the height accordingly. Then I want to crop the image, so that the result has a dimension of 150x150 pixels.
I've tried several ways, but haven't figured out how to do it.

You can calculate the scale factor by dividing new width by old width:
var scale : Number = 150 / myImage.width;
myImage.scaleX = myImage.scaleY = scale;
To crop, either use a mask on the scaled clip, or draw to a new Bitmap:
var myBitmapData : BitmapData = new BitmapData ( 150, 150 );
// use concatenated matrix of the image to scale the new bitmap data
var matrix : Matrix = myImage.transform.concatenatedMatrix;
myBitmapData.draw ( myImage, matrix );
var myBitmap : Bitmap = new Bitmap ( myBitmapData );
addChild ( myBitmap );
Depending on how many images there are, and what you are going to do with them later, you should always test both possibilities for performance.

Related

Libgdx Rendering Bitmap font to pixmap texture causes extremely slow rendering

I'm using libgdx scene2d to render 2d actors. Some of these actors originally included scene2d Label actors for rendering static text. The Labels work fine but drawing ~20 of them on the screen at once drops the frame rate by 10-15 frames, resulting in noticeably poor rendering while dragging.
I'm attempting to avoid the Labels by pre-drawing the text to textures, and rendering the textures as scene2d Image actors. I'm creating the texture using the code below:
BitmapFont font = manager.get(baseNameFont,BitmapFont.class);
GlyphLayout gl = new GlyphLayout(font,"Test Text");
int textWidth = (int)gl.width;
int textHeight = (int)gl.height;
LOGGER.info("textHeight: {}",textHeight);
//int width = Gdx.graphics.getWidth();
int width = textWidth;
//int height = 500;
int height = textHeight;
SpriteBatch spriteBatch = new SpriteBatch();
FrameBuffer m_fbo = new FrameBuffer(Pixmap.Format.RGB565, width,height, false);
m_fbo.begin();
Gdx.gl.glClearColor(1f,1f,1f,0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
Matrix4 normalProjection = new Matrix4()
.setToOrtho2D(0, 0, width, height);
spriteBatch.setProjectionMatrix(normalProjection);
spriteBatch.begin();
font.draw(spriteBatch,gl,0,height);
spriteBatch.end();//finish write to buffer
Pixmap pm = ScreenUtils.getFrameBufferPixmap(0, 0, (int) width, (int) height);//write frame buffer to Pixmap
m_fbo.end();
m_fbo.dispose();
m_fbo = null;
spriteBatch.dispose();
Texture texture = new Texture(pm);
textTexture = new TextureRegion(texture);
textTexture.flip(false,true);
manager.add(texture);
I assumed, and have read, that textures are often faster. However when I replaced the Labels with the texture, it had the same, if not worse, affect on the frame rate. Oddly, I'm not experiencing this when adding textures from a file, which makes me think I'm doing something wrong in my code. Is there a different way I should be pre-rendering these pieces of text?
I have not tested this, but I think you can enable culling for the whole Stage by setting its root view to use a cullingArea matching the world width and height of the viewport. I would do this in resize after updating the Stage Viewport just in case the update affects the world width and height of the viewport.
#Override
public void resize(int width, int height) {
//...
stage.getViewport().update(width, height, true);
stage.getRoot().setCullingArea(
new Rectangle(0f, 0f, stage.getViewport().getWorldWidth(), stage.getViewport().getWorldHeight())
);
}
It will only be able to cull Actors that have their x, y, width, and height set properly. This is true I think of anything in the scene2d UI package, but for your own custom Actors you will need to do it yourself.
If a child of your root view is a Group that encompasses more than the screen and many actors, you might want to cull its children, too, such that even if the group as a whole is not culled by the root view, the group can still cull a portion of its own children. To figure out the culling rectangle for this, I think you would intersect a rectangle of the group's size with the viewport's world size rectangle offset by -x and -y of the group's position (since the culling rectangle is relative to the position of the group it's set on).

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.

libGDX texture a 2d terrain

I work at a truck game with libgdx and box2d.
In my game 1 meter = 100 pixels.
My 2d terrain is generated by me, and is made by points.
What I did, is made a polygonregion for the whole polygon and used texturewrap.repeat.
The problem is that, my game size is scaled down by 100 times, to fit the box2d units.
So my camera width is 800 / 100 and height 480 / 100. (8x4.8 pixels)
How I created my polygon region
box = new Texture(Gdx.files.internal("box.png"));
box.setFilter(TextureFilter.Linear, TextureFilter.Linear);
box.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
TextureRegion region = new TextureRegion(box);
psb = new PolygonSpriteBatch();
float[] vertices = new float[paul.size];
for (int i = 0; i < paul.size; i++) {
vertices[i] = paul.get(i);
if (i % 2 == 1)
vertices[i] += 1f;
}
EarClippingTriangulator a = new EarClippingTriangulator();
ShortArray sar = a.computeTriangles(vertices);
short[] shortarray = new short[sar.size];
for (int i = 0; i < sar.size; i++)
shortarray[i] = sar.get(i);
PolygonRegion pr = new PolygonRegion(region, vertices, shortarray);
System.out.println(vertices.length + " " + shortarray.length);
ps = new PolygonSprite(pr);
Now i'll just draw the polygonsprite to my polygonsprite batch.
This will render the texture on the polygon repeatedly, but the picture will be 100 times bigger and is very streched.
The left example is the one that i want to make, and the right one is the way that my game looks..
This PR was merged which looks like it does what you want:
https://github.com/libgdx/libgdx/pull/3799
See RepeatablePolygonSprite.
I am not completely sure if this will solve your problem (can't test it right now), but you need to set the texture coordinates of your TextureRegion to a higher value, probably your factor of 100.
So you could try to use region.setU2(100) and region.setV2(100). Since Texture Coordinates usually go from [0,1], the values higher than that will be outside. And because you set the TextureWrap to repeat, this will then repeat your texture over and over.
This way, the TextureRegion will alredy show your one texture repeated 100 times in x and y direction. If you then tell the PolygonSprite to use that region, it should show it as in the image you posted.
Hope that helps... :)
You could create a new texture by code. Take your level size and fill it with your texture then delete to background the top side. Look at pixelmap. Maybe this will help you.
Edit:
TextureRegion doesn't repeat to fit the size you even use texture, or you use TiledDrawable.

AS3 - make a new bitmapData with non integar width and height

Hey guys hoping someone can help here. I am cropping a bitmap image into tiles using the following function:
function crop( _x:Number, _y:Number, _width:Number, _height:Number, callingScope:MovieClip, displayObject:DisplayObject = null, pixelSnapping:Boolean = false):Bitmap
{
var cropArea:Rectangle = new Rectangle( 0, 0, _width, _height );
var croppedBitmap:Bitmap;
if(pixelSnapping == true)
{
croppedBitmap = new Bitmap( new BitmapData( _width, _height ), PixelSnapping.ALWAYS, true );
}
else
{
croppedBitmap = new Bitmap( new BitmapData( _width, _height ), PixelSnapping.NEVER, true );
}
croppedBitmap.bitmapData.draw( (displayObject!=null) ? displayObject : callingScope.stage, new Matrix(1, 0, 0, 1, -_x, -_y) , null, null, cropArea, true );
return croppedBitmap;
}
The width and height being passed in is a non integer value, for instance 18.75. When the new BitmapData is created it always rounds down the value to an integer, since the arguments for BitmapData are typed as such. In the case of my needs here, the width and height will not likely ever be integers. Is there a way to create these bitmap pieces of another image at the exact width and height I need or can a new bitmapData only be created with integer values for height and width?
Thanks for any insight.
EDIT: I realize you can't have a fraction of a pixel, but... What I am trying to achieve is dividing an image into tiles. I want the amount of tiles to be variable, say 4 rows by 4 columns, or 6 rows by 8 columns. the division of an image into X number of parts results in widths and heights in most cases to be non integar values like 18.75 for example. The goal is to divide an image up into tiles, and have that image appear, assembled seamlessly, above the source image, where I would then manipulate the individual tiles for various purposes (puzzle game, tiled animation to new scene, etc). I need the image, when assembled from all the tile pieces, to be an exact copy of the original image, with no lines between tiles, or white edges anywhere, and I need this to happen with non integer widths and heights for the bitmapData pieces that comprise the tiles. Does that makes sense? O_o
A BitmapData can only be created with integer values for the dimensions.
And to be honest, I'm trying to think what a BitmapData object with floating number values for dimensions would be like, but my brain starts to scream in pain: DOES NOT MAKE SENSE!
My brain's a bit melodramatic sometimes.
-- EDIT --
Answer to your edited question:
2 options:
1/ Just copy the original full bitmap data as many times as you have tiles and then use masks on bitmap objects to show the appropriate parts of the tiles
2/make sure no fractional widths or heights are generated by the slicing. For instance if you got an image of width 213 you want to split in 2 rows and 2 columns, then set the width of the left tiles to 106 and the width of the right tiles to 107
Maybe there are other options, but those two seem to me to be the easiest with most chance of success.
If you need to create tiles with preferred size you can use something like that:
private function CreateTilesBySize( bigImage:Bitmap, preferredTileWidth:int, preferredTileHeight:int ):Tiles
{
var result:Tiles = new Tiles();
var y:int = 0;
while ( y < bigImage.height ) {
result.NewRow();
const tileHeight:int = Math.min( preferredTileHeight, bigImage.height - y );
var x:int = 0;
while ( x < bigImage.width ) {
const tileWidth:int = Math.min( preferredTileWidth, bigImage.width - x );
const tile:Bitmap = Crop( x, y, tileWidth, tileHeight, bigImage );
result.AddTile( tile );
x += tileWidth;
}
y += tileHeight;
}
return result;
}
If you need to create exact amount of rows and calls. Then you can use:
private function CreateTilesByNum( bigImage:Bitmap, cols:int, rows:int ):Tiles
{
const preferredTileWidth:int = Math.ceil( bigImage.width / cols );
const preferredTileHeight:int = Math.ceil( bigImage.height / rows );
return CreateTilesBySize( bigImage, preferredTileWidth, preferredTileHeight );
}
But remember that tiles will have diferent sizes (last tiles in a row and last tiles in a column)

Resizing BufferedImage

i would like to resize a BufferedImage that is draw using the Graphics drawing method with AffineTransform object.
AffineTransform at = new AffineTransform();
at.scale(1, -1);
at.translate((int) xPos - xIncr, -(int)yPos);
((Graphics2D) g).drawImage(line, at, null);
How could i resize the image, that is smaller than the area i want it to be draw in ?
Thanks for help.
Given that you're already drawing the image with an AffineTransform, why not just apply another scale to expand your image by the desired amount?
AffineTransform at = new AffineTransform();
at.scale(1, -1);
at.scale(2.5, 3.0); // replace these with more appropriate scale factors
at.translate((int) xPos - xIncr, -(int)yPos);
((Graphics2D) g).drawImage(line, at, null);