I have a SurfaceView canvas I'm drawing a scene into.
The canvas is transformed to accomodate the logical scene size which is 1024x768 (while the actual screen is 800x480), and also to support zoom/scroll navigation on it.
Since I did not want black stripes on the sides (fitting 4:3 to 16:9) I'm using a different background bitmap which has "stretched" sides to cover the ratio difference.
Another twist in the plot, is that I need to support both phones and tables. For tablets I'm loading a higher quality (size) image and for phones I'm scaling down the background bitmap when loading it from disk.
Below is code that does what I want (although probably not the best way), and in particular, I have a feeling the background calculations can be cached into a matrix to be used later in canvas.drawBitmap(Bitmap, Matrix, Paint);
(Also, I couldn't get the equivalent positioning of drawBitmap(Bitmap, left, top, Paint) by translating the canvas with these offsets, I'm also curious why).
This is the draw routine:
public void draw(Canvas canvas) {
canvas.save();
float midX = width/2;
float midY = height/2;
// zoom out to fit logical view, and translate for future
// sprite drawing with logical coordinates
// x,y and zoom are calculated in on init and updated in touch events
canvas.translate(x, y);
canvas.scale(zoom, zoom, midX, midY);
// background part
if(back != null && !back.isRecycled()) {
// todo: these can probably be pre-calculated for optimization
// todo: but so far I couldn't get it right..
if(bgMatrix != null) { // this is where I'm thinking of using the cached matrix
canvas.drawBitmap(back, bgMatrix, paint);
}
else {
float offsetW = (width-back.getWidth())/2;
float offsetH = (height-back.getHeight())/2;
canvas.save();
// bgScaleFactor is calculated upon setting the bg bitmap
// it says by how much we need to scale the image to fill the canvas
// taking into account the image (possible) downscale
canvas.scale(bgScaleFactor, bgScaleFactor, midX, midY);
// this doesn't work: canvas.postTranslate(offsetW, offsetH) and use 0,0 for next draw
canvas.drawBitmap(back, offsetW, offsetH, paint);
// todo: here I would like to save a matrix which represents
// how the back bitmap was drawn onto the canvas
// so that next time these calculations can be avoided
// this fails: bgMatrix = canvas.getMatrix();
canvas.restore();
}
// draw scene shapes on transformed canvas
if(shapes != null){
shapes.onDraw(canvas);
}
canvas.restore();
}
Related
I am developing an application for technical drawing and I need to add a few tools to draw lines, circles, rectangles and corners. Now I can draw lines and free hand drawing but I cannot draw circles, rectangles and corners. I found in lot of websites how to draw it but static, I mean, draw the shape in the position you pre-set or the position where you touch but I need to know how to draw, for example, a circle in the position I touch and make it bigger than I separate my fingers. I hope you understand what I mean.
You can have two variables x and y, then every time you touch the screen set x and y to that value, while drawing draw the circle with coordinates x and y.
If you are drawing and you just want to keep a painted circle, you can paint the circle and add it inside your canvas on x and y, then the next time you touch the screen a new circle will be painted on x and y and the old one will remain painted.
Are you using Canvas ? if so you can find out how to do this here (Canvas documentation) and here (Bitmap documentation). Depending on your situation you can create a new Bitmap and assign it to Canvas then draw on the Canvasand inside your bitmap you will have your desired circles and other shapes, on the next drawing frame, draw new shapes and the changes will remain.
Edit: In order to have dynamic radius follow this logic, when you touch the screen, set x and y to that point (the center of the circle), while moving the finger on the screen, calculate the radius comparing to x and y, when lifting your finger apply the drawing on the bitmap as told above.
Some code:
public boolean onTouchEvent(MotionEvent e)
{
switch (e.getAction())
{
case MotionEvent.ACTION_DOWN:
x = (int) event.getX();
y = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
//If I'm not wrong this is how to calculate radius,
//I'm at work and can't test this now, you can use your way
int distanceX = (int) event.getX() -x;
int distanceY = (int) event.getY() -y;
radius = sqrt(distanceX *distanceX + distanceY *distanceY);
break;
case MotionEvent.ACTION_UP:
//Draw circle inside your bitmap here
//This is like a flag to notify the program that no new shape is being drawn
x = -1;
break;
}
public void draw(Canvas canvas)
{
canvas.drawBitmap(myBitmap, 0, 0, null);
//A new shape is being drawn
if (x != -1)
//In here you place a switch to decide which shape you are drawing
//for this example i assume circle
canvas.drawCircle(radius, x, y, paint);
}
When you are lifting your finger the new circle should be painted on your bitmap so you don't have to add extra code for each new circle.
Edit2: I will add more code with the Bitmap and Canvas method i described.
Bitmap myBitmap;
Canvas myCanvas;
//Constructor
public myActivity(Bundle bundle) //or whatever your constructor is
{
myBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas(myBitmap);
}
Now anything you draw on "myCanvas" will be applied to "myBitmap", when ACTION_UP activates draw circle on "myCanvas" which is drawn on the draw function.
case MotionEvent.ACTION_UP:
myCanvas.drawCircle(radius, x, y, paint);
x = -1;
break;
I'm trying to overlay a bitmap onto another, placing it at the location the user touches. Here's the code:
public static Bitmap mergeImage(Bitmap base, Bitmap overlay, float x, float y)
{
Bitmap mBitmap = Bitmap.createBitmap(base.getWidth(), base.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(mBitmap);
canvas.drawBitmap(base, 0, 0, null);
canvas.drawBitmap(overlay, x, y, null);
return mBitmap;
}
The issue here is, even though the x & y coordinates are obtained correctly (I checked), the overlay bitmap does not place correctly.
When around the top left portion of the image, placement is correct. However, as I move right and down, the location seems to scale differently (i.e. if I touch the bottom right corner of the screen, the overlay places somewhere near the middle of the image, if I touch bottom left, it places near the middle left of the image and so on)
Both images have the same density (320).
Edit: New issue, i reduced the sizes of both images and now placement is roughly accurate. But saving the image to SD card skews the overlay image to a different (and quite random) location
I found the solution using Matrix for set location and scale x,y
public static Bitmap mergeImage(Bitmap base, Bitmap overlay, float x, float y)
{
Bitmap mBitmap = Bitmap.createBitmap(base.getWidth(), base.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(mBitmap);
Matrix matrix = new Matrix ();
matrix.postTranslate( x,y);
canvas.drawBitmap(base, 0, 0, null);
canvas.drawBitmap(overlay, matrix, null);
return mBitmap;
}
I spend something similar and I found the solution, you can see my post in the siguiete link
canvas.drawBitmap bad Location and size (not placing image correctly)
I want to rotate a bitmap image based on user click by 10 deg. Following numerous stackoverflow and google answers, I tried various combinations of Matrix rotation.
However the image doesn't really rotate as expected and gives a jittery view of rotation + oscillation about canvas center. To test I am increasing rotation angle by 10 deg (instead of clicks) each time object's draw method is called. The image is a symmetrical circle [64x64 enclosing rectangle] and I expect it to rotate at center of screen about it's own center like a wheel, but it rotates and moves diagonally towards right-down and moves back upto center of screen in an oscillatory fashion.
public void draw(Canvas canvas) {
Matrix matrix = new Matrix();
rotation += 10;
float px = this.viewWidth/2;
float py = this.viewHeight/2;
matrix.setRotate(rotation, bitmap.getWidth()/2, bitmap.getHeight()/2);
Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, getImgWidth(), getImgHeight(), matrix, true);
canvas.drawBitmap(newbmp, px - (getImgWidth()/2), py - (getImgHeight()/2), null);
}
Here is an example.
I broke it to 3 steps.
The first translate moves the bitmap so that it's center is at 0,0
Then a rotation,
and finally move the bitmap center to where you want it on the canvas.
You don't need the second bitmap.
Matrix matrix = new Matrix();
rotation += 10;
float px = this.viewWidth/2;
float py = this.viewHeight/2;
matrix.postTranslate(-bitmap.getWidth()/2, -bitmap.getHeight()/2);
matrix.postRotate(rotation);
matrix.postTranslate(px, py);
canvas.drawBitmap(bitmap, matrix, null);
As an optimization, create the Matrix once outside this method and replace the creation with a call to matrix.reset()
You need to translate the bitmap to the 0,0 point (or draw it at 0,0) and rotate it there, then translate it back, as such:
canvas.save();
canvas.translate(this.viewWidth, this.viewHeight);
canvas.rotate(rotation);
canvas.drawBitmap(newbmp, -(getImgWidth()/2), -(getImgHeight()/2), null);
canvas.restore();
Here I draw it with the center at 0,0 (I think), because when you rotate, it's about 0,0 and not the center of the screen as one would think. If you draw the center at 0,0 then it will rotate about the center of the bitmap.
If my code does not accomplish drawing the bitmap center at 0,0 then you can change my code to draw it at the center and it will work as you want.
Hope this helps!
// x : x coordinate of image position
// y : y coordinate of image position
// w : width of canvas
// h : height of canvas
canvas.save();
canvas.rotate(angle, x + (w/2), y + (h/2));
canvas.drawBitmap(image, x, y, null);
canvas.restore();
The steps are
Save the existing canvas
Rotate the canvas about the center of the bitmap, that you would draw on canvas with an angle of rotation
Draw the image
Restore the image
In my custom view I have to draw on a base Bitmap when the user selects a flag. Suppose I have a base face bitmap and the user turns the mustache flag on. Since this custom View has to be scalable, I want to work with relative coordinates (in the range [0,1]) instead of bitmap width and height. The following doesn't work
Bitmap orig, face;
public void onDraw(Canvas c) {
c.drawBitmap(face);
}
public void onMustacheFlag() {
face = Bitmap.create(orig);
Canvas c = new Canvas(face);
c.save();
c.scale(1f / face.getWidth(), 1f / face.getHeight());
// Draw lines, circle, rectangles with all vertices in the range [0.0f, 1.0f]
c.restore();
}
Also, this custom view forces height to be equal to width (it's a square form factor)
The parameters to Canvas.scale(float, float) serve as coefficients for x e and y coordinates in drawing routines. Passing 1f / width means multiplying values assumed in the range (0, 1) by a number less then 1, thus the result of all drawing routines end up in the top left corner and is likely not visible. Instead, if I pass width and height, the code works as intended, because the range (0, 1) is mapped to (0, width):
public void onMustacheFlag() {
face = Bitmap.create(orig);
Canvas c = new Canvas(face);
c.save();
c.scale(face.getWidth(), face.getHeight()); // instead of 1 / width
// Draw lines, circle, rectangles with all vertices in the range [0.0f, 1.0f]
c.restore();
}
I have this sprite rotating algorithm (its poorly named and just used for testing). It is so close, sprites drawn with it do rotate. Everyframe I can add +5 degrees to it and see my nice little sprite rotate around. The problem is, the other stuff drawn to the canvas now flickers. If I don't do the rotation the regular drawn sprites work great. I think I am close but I just don't know what piece I am missing. Below is my two "Draw_Sprite" methods, one just draws the previously resource loaded bitmap to the canvas passed in. The other one, does some rotation the best I know how to rotate the sprite by so x many degrees..and then draw it. If I have a nice game loop that draws several objects, one type is the rotated kind. Then the non-rotated sprites flicker and yet the rotated sprite never does. Though if I draw the non-rotated sprites first, all is well, but then the Z-Ordering could be messed up (sprites on top of UI elements etc)... The method definitions:
/*************************************************
* rotated sprite, ignore the whatever, its for ease of use and testing to have this argument list
* #param c canvas to draw on.
* #param whatever ignore
* #param rot degrees to rotate
* #return
*/
public int Draw_Sprite(Canvas c, int whatever, int rot) {
//rotating sprite
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
Matrix orig = c.getMatrix();
mMatrix = orig;
orig.setTranslate(0, 0);
orig.postRotate(rot, x+width/2, y+height/2);
c.setMatrix(orig);
c.drawBitmap(images[curr_frame], src, dst, null);
c.setMatrix(mMatrix); //set it back so all things afterwards are displayed correctly.
isScaled=false;
return 1;
}
/********************************************************
* draw a regular sprite to canvas c
* #param c
* #return
*/
public int Draw_Sprite(Canvas c) {
Rect src = new Rect(0, 0, width, height);
Rect dst = new Rect(x, y, x + width, y + height);
c.drawBitmap(images[curr_frame], src, dst, null);
isScaled=false;
return 1;
}
And now the usage:
void onDraw(Canvas c)
{
canvas.drawRect( bgRect, bgPaint); //draw the background
//draw all game objects
// draw the normal items
for (GameEntity graphic : _graphics) {
graphic.toScreenCoords((int)player_x, (int)player_y);
if(graphic.getType().equals("planet")) //draw planets
graphic.Draw_Sprite(canvas); //before the rotation call draws fine
else
{
//rotate all space ships every frame so i see them spinning
//test rotation
mRot +=5;
if(mRot>=360)
mRot=0;
graphic.Draw_Sprite(canvas, 0, mRot); //yes function name will be better in future. this rotates spins draws fine
}
}
thePlayer.Draw_Sprite(canvas); //FLICKERS
drawUI(canvas);//all things here flickr
}
So it does do it, things after a call to a rotational draw are drawn correctly. But the problem is it flickrs. Now One could say I should just do all my non rotational stuff and save that last, but the zordering would be off.... suggestions as to how to tackle this issue of zordering or the flickering?
Just for the next guy who may read this. You can do this with only a few lines of code:
canvas.save();
canvas.rotate(rotation_angle, x + (widthofimage / 2), y + (heightofimage / 2));
canvas.drawBitmap(bitmap, x, y, null);
canvas.restore();
Try using canvas.save() before the rotation and canvas.restore() after manipulation is complete.
When performing manipulations on the canvas in order to change the way an object is drawn you have to remember the manipulations set how the canvas handles origins etc... So if you translate or rotate the canvas, that will be set for the lifetime of that canvas. In order to avoid this you first call save, which saves a snapshot of the canvas matrix before you manipulate it, then you run all your changes, then call restore which will restore the canvas back to the last saved point. Otherwise all your changes build up and you get unintended results.