I'm building a "navigation type" app for android.
For the navigation part I'm building an Activity where the user can move and zoom the map (which is a bitmap) using touch events, and also the map rotate around the center of the screen using the compass.
I'm using Matrix to scale, transpose and rotate the image, and than I draw it to the canvas.
Here is the code called on loading of the view, to center the image in the screen:
image = new Matrix();
image.setScale(zoom, zoom);
image_center = new PointF(bmp.getWidth() / 2, bmp.getHeight() / 2);
float centerScaledWidth = image_center.x * zoom;
float centerScaledHeigth = image_center.y * zoom;
image.postTranslate(
screen_center.x - centerScaledWidth,
screen_center.y - centerScaledHeigth);
The rotation of the image is doing using the postRotate method.
Then in the onDraw() method I only call
canvas.drawBitmap(bmp, image, drawPaint);
The problem is that, when the user touch the screen, I want to get the point touched on the image, but apparently I can't get the correct position.
I tried to invert the image matrix and translate the touched points, it isn't working.
Do somebody know how to translate the point coordinates?
EDIT
I'm using this code for traslation.
dx and dy are translation values get from the onTouch listener.
*new_center* is an array of float values in this form {x0, y0, x1, y1...}
Matrix translated = new Matrix();
Matrix inverted = new Matrix();
translated.set(image);
translated.postTranslate(dx, dy);
translated.invert(inverted);
inverted.mapPoints(new_center);
translated.mapPoints(new_center);
Log.i("new_center", new_center[0]+" "+new_center[1]);
Actually I tried using as *new_center = {0,0}*:
appling only the translated matrix, I get as espected the distance between the (0,0) point of the bmp and the (0,0) point of the screen, but it seems to not take account of the rotation.
Appling the inverted matrix to the points I get those results, moving the image in every possible way.
12-26 13:26:08.481: I/new_center(11537): 1.9073486E-6 -1.4901161E-7
12-26 13:26:08.581: I/new_center(11537): 0.0 -3.874302E-7
12-26 13:26:08.631: I/new_center(11537): 1.9073486E-6 1.2516975E-6
12-26 13:26:08.781: I/new_center(11537): -1.9073486E-6 -5.364418E-7
12-26 13:26:08.951: I/new_center(11537): 0.0 2.682209E-7
12-26 13:26:09.093: I/new_center(11537): 0.0 7.003546E-7
Instead I was especting the coordinates translated on the image.
Is it correct my line of thoughts?
Ok, I get it.
First I separated the rotation from the translation and zooming of image.
Because I created a custom ImageView, this was simple. I apply the rotation to the canvas of the ImageView, and the other transformations to the matrix of the image.
I get trace of the canva's matrix throught a global matrix variable.
Some code:
To set the correct movement for the corresponding onTouch event, first I "rotate back" the points passed from onTouch (start and stop points) using the inverse of the matrix of the canvas
Then I calculate the difference between x and y, and apply that to the image matrix.
float[] movement = {start.x, start.y, stop.x, stop.y};
Matrix c_t = new Matrix();
canvas.invert(c_t);
c_t.mapPoints(movement);
float dx = movement[2] - movement[0];
float dy = movement[3] - movement[1];
image.postTranslate(dx, dy);
If instead you want to check that the image movement don't exceed its size, before the image.postTranslate(dx, dy); you put this code:
float[] new_center = {screen_center.x, screen_center.y};
Matrix copy = new Matrix();
copy.set(image);
copy.postTranslate(dx, dy);
Matrix translated = new Matrix();
copy.invert(translated);
translated.mapPoints(new_center);
if ((new_center[0] > 0) && (new_center[0] < bmp.getWidth()) &&
(new_center[1] > 0) && (new_center[1] < bmp.getHeight())) {
// you can remove the image.postTranslate and copy the "copy" matrix instead
image.set(copy);
...
It's important to note that:
A) The center rotation of the image is the center of the screen, so it will not change coordinates during the canvas' rotation
B) You can use the coordinates of the center of the screen to get the rotation center of the image.
With this method you can also convert every touch event to image coordinates.
Related
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
I have created a canvas and draw a bitmap on it, and animate it every time I touch the canvas
i have set the bounds of the bit map on the start of drawing but after I change the rotation matrix and rotate the bit map it does not change the bounds based on the new matrix
I need to reset the bounds of my bitmap to be able to use it as a clickable object and fire some event when click on it
how can I update the bounds based on the rotation matrix
Thanks in Advance
I have used these equation to re calculate the new position after rotation
newx = (oldx * cos(angle)) + (oldy * - sin(angle))
newy = (oldy * sin(angle)) + (oldy * cos(angle))
I am facing problme in rotating image
Following code works fine
Matrix matrix = new Matrix();
matrix.postRotate(DEGREE,mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
Bitmap m = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(),mBitmap.getHeight(), matrix, true);
canvas.drawBitmap(m, mX, mY, null);
But I dont want to create a new bitmap again and again so I am using the following code
Matrix matrix = new Matrix();
matrix.postTranslate(mX, mY);
matrix.postRotate(DEGREE,mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
canvas.drawBitmap(mBitmap, matrix, null);
In that case image goes out of the view. It is not visible.
Matrix matrix = new Matrix();
canvas.translate(mX, mY);
canvas.drawBitmap(...);
canvas.translate(-mX, -mY);
Try first rotating it then translating it, because when you translate it first the center is not in the middle any more so you are rotating it with wrong pivot coordinates.
The pivot point by default when rotating is the top left corner of the image, which is why the view goes out of view. You need to add logic to make the pivot point the center of the image. Unfortunately, geometry is not my strong suit so maybe someone who somewhat enjoys geometry can give you the calculations to make this happen.
I have a bitmap flush with the bottom of my screen. When the user clicks a button I want it to rotate to the right by one degree. I am able to accomplish this but the problem is that the bottom of the item is no longer flush with the screen. I need it to appear to rotate on its bottom axis. I could use some hack to increment the x and y when its rotated (using trial and error I suppose) but is there a formula or something more elegant I can use?
public void rotate(int degrees)
{
Matrix mat = new Matrix();
mat.postRotate(degrees);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mat, true);
}
Could you not instead use the following method that Matrix also has:
postRotate (float degrees, float px, float py)
It enables rotation on the specified point.
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.