Simultaneous rotation, translation, and scaling of bitmap in android canvas - android

I'm trying to show a picture, which can be zoomed and panned, and that rotates with a compass reading. With the code below, all three actions kind of work, but they influence each other.
Here is what I want to achieve:
1. Rotate around the center of the screen
2. Scale leaving the same part of the picture in the center
3. Pan to the desired spot in the picture
Here is what is actually happening with the code below:
1. Rotation works as intended, around the center of the screen
2. Scaling works, but it scales around the center of the picture
3. Translation only works as intended if angle is zero, otherwise it's moving in a wrong direction
// the center of the view port
float centerX = screen.right/2;
float centerY = screen.bottom/2;
Matrix m = new Matrix();
m.preRotate(angle, centerX, centerY);
m.preScale(mScaleFactor, mScaleFactor, centerX, centerY);
// scaling the amount of translation,
// rotating the translation here gave crazy results
float x = mPosX / mScaleFactor;
float y = mPosY / mScaleFactor;
m.preTranslate(x, y);
canvas.drawBitmap(pic, m, null);
If I translate first, and later rotate, the translation works as intended but the rotation is not around the center of the view port anymore.
How can I apply all three transformations, without them influencing each other?

I'm not sure about the scaling around the centre of the image, but as for the translation being in the wrong direction is it not as a consequence of you rotating the image but not the translations? Maybe try something like this:
float x = (mPosX*(cos(angle) + sin(angle)) / mScaleFactor;
float y = (mPosY*(cos(angle) - sin(angle)) / mScaleFactor;
m.preTranslate(x, y);
Also does the matrix object have a method to apply an affine transformation directly? Because then you won't need to think about the order of operations.
The affine transformation might look something like this:
M = |mScaleFactor*cos(angle) sin(angle) x|
| -sin(angle) mScaleFactor*cos(angle) y|
| 0 0 1|
But this will rotate around the corner of the image so you need to use the preTranslate() function first like so:
Mt.preTranslate(-centerX,-centerY);
And APPLY Mt to pic before applying M and then after you need to apply -Mt

Related

how to find the biggest possible Y point given X in Region in Android?

I have a virtual ruler being drawn on the screen. I want to be able to draw a straight line like the blue line above when touch event happen within the grown rectangle. but because touch can't be 100% straight, the movement might be like the red line. that's why I set a rectangle to listen to all the nearby touch events then draw a blue line.
I currently have
mRulerRect.set(mRulerCenter.x - mRulerBitmap.getWidth() / 2,
mRulerCenter.y - mRulerBitmap.getHeight()),
mRulerCenter.x + mRulerBitmap.getWidth() / 2,
mRulerCenter.y);
mPath.addRect(mRulerRect, Path.Direction.CCW);
mRulerMatrix.setRotate(mRulerAngle, mRulerCenter.x, mRulerCenter.y);
mPath.transform(mRulerMatrix);
mRegions.setPath(mPath, new Region(mRulerRect));
then I check if the touch even happen within brown rectangle by mRegions.contains(x,y). works perfect so far for touch detection, but the problem I have now is how to draw a straight line. I tried to fix X point then calculate Y. it works fine when ruler is horizontal then starts to behave very weird when turning from horizontal to vertical. I'm out of idea how to accomplish this. Please help! thank you.
Things that you know:
The center of that brown rect is mRulerCenter.x, mRulerCenter.y
The line that you want to draw pass through that point
The angle of the line is mRulerAngle
We're missing just one element, which is, the length of the line we want to draw. That's probably going to be a portion of the ruler's width and it should be very easy to compute mRulerRect.width() * someFactor
Now, we want to know what are the start and the end of the line, we can compute that with trigonometry functions
float halfLineLength = mRulerRect.width() * someFactor;
float startAngle = (float) Math.toRadians(mRulerAngle);
float endAngle = (float) Math.toRadians(mRulerAngle + 180);
float startX = mRulerCenter.x + (float) Math.cos(startAngle) * halfLineLength;
float startY = mRulerCenter.y + (float) Math.sin(startAngle) * halfLineLength;
float endX = mRulerCenter.x + (float) Math.cos(endAngle) * halfLineLength;
float endY = mRulerCenter.y + (float) Math.sin(endAngle) * halfLineLength;
and then draw your line from (startX, startY) to (endX, endY), actually doesn't really matter which is start and which is end

Draw on custom view with zoom not working properly

When drawing on view without zoom it works fine. See the screenshot
But when zooming and then drawing on view It is slightly up or down. See the screenshot
Here is my code for Custom View http://www.paste.org/78026 and for zoom http://www.paste.org/78027 and my xml http://www.paste.org/78028
Please can you tell me where I am wrong
Finally after lot of searching I found how to get relative X,Y when View is zoomed. It may be helpful to someone
// Get the values of the matrix
float[] values = new float[9];
matrix.getValues(values);
// values[2] and values[5] are the x,y coordinates of the top left corner of the drawable image, regardless of the zoom factor.
// values[0] and values[4] are the zoom factors for the image's width and height respectively. If you zoom at the same factor, these should both be the same value.
// event is the touch event for MotionEvent.ACTION_UP
float relativeX = (event.getX() - values[2]) / values[0];
float relativeY = (event.getY() - values[5]) / values[4];

Any ways of making pinch to zoom smoother

Is there any ways of implementing smooth pinch to zoom without using matrix?
I am building drawing app and I want it to have pinch to zoom.
I get pivot point for scaling with:
centerposX = mScaleDetector.getFocusX();
centerposY = mScaleDetector.getFocusY();
and for scaling then I use:
canvas.scale(scaleFactor, scaleFactor, centerposX, centerposY);
But the problem is that it immediately centers view at the pivot point and them zooms it, rather than using it as a guide for centering.
I've seen that this problem has been solved by using matrix, but I don't want to use them as I need to keep track of offsets, ZoomTranslations which are calculated from centerposX/Y and scaleFactor to put drawings where they belong on screen.
So is there any way to solve this pivot point problem smoothly?
Thanks!
After a week I understood that I need to use matrix and get absolute coordinates on the screen, so I used Gesture detector to set matrix scale
matrix.postScale(mScaleFactor, mScaleFactor, focusX, focusY);
In my onDraw method I used canvas.concat(matrix); so not only Bitmap, but WHOLE canvas get's matrix transformation, and to get real screen coordinates I used a method i found on stack-overflow:
public float[] getAbsolutePosition(float Ax, float Ay) {
matrix.getValues(m);
float x = width - ((m[Matrix.MTRANS_X] - Ax) / m[Matrix.MSCALE_X])
- (width - getTranslationX());
float y = height - ((m[Matrix.MTRANS_Y] - Ay) / m[Matrix.MSCALE_X])
- (height - getTranslationY());
return new float[] { x, y };
}
I call this every time in my onTouchEvent() method, supplying event.getX and event.getY() as arguments.
After that, everything is easy peasy, also because of this beautiful method I've got rid of ~4-5 variables which I had to use to find real touch location.

Get New Points from Angle Android

I am trying to make a Re-sizable touch view which I made successfully. You can find code How to make view resizable on touch event.
It has 4 corners. You can re-size that rectangle by dragging one of corner. But now I want to enhance that logic and want to put rotation in that code. I successfully find angle when user touch center of one of the edge of rectangle. But now problem is I can't get new position of corners so that I can redraw that rectangle and rotation is possible.
Question is : How can I calculate 4 corners new position based on Angle?.
If you know the angle by which to rotate then you don't need to calculate the rectangle's vertices.
A simple way to do it would be as below
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.rotate(60.0f);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(10, 10, 100, 100, paint);
canvas.restore();
}
As I understood, you want to calculate new coordinates based on a rotation angle... It's probably simpler than you think:
x' = x × (cosα - sinα)
y' = y × sinα × cosα
So, you just apply this programmatically, considering where x' is the rotation result of x and the same for y' and y, and α is the rotation angle.
cosα and sinα functions are available in Java as Math.cos(α) and Math.sin(α), but attention: in Java, all trigonometric functions uses radians and not degrees as angle, so you can consider this:
rad = deg * 180 / π
Applicable as:
double deg = 45d; //Put instead your degrees
double rad = deg / 180 * Math.PI; //The radians convertion

centered zoom in custom view - calculating canvas coordinates

I am working on my first "real" Android application, a graphical workflow editor. The drawing is done in a custom class, that is a subclass of View.At the moment my elements are rectangles, which are drawn on a canvas. To detect actions on elements I compare the coordinates and check for elements on the touch location.
To implement a zoom gesture I tried http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html
With the 4 argument canvas.scale(...) function the centered zooming works well, but I lose the ability to calculate the canvas coordinates using the offset with mPosX and mPosY to detect if the touch after a zoom is on an element.
I tried to change the example in the blogpost above to center the canvas on the zoom gesture with:
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor, mScalePivotX, mScalePivotY);
//drawing ....
canvas.restore();
I did not find any examples on how this could be done without losing the reference offset to calculate the coordinates. Is there an easy workaround? I tried to calculate the offset with the gesture center and the scaling factor, but failed :/
I have seen that other examples which use an ImageView often use a Matrix to transform the image. Could this be done with a custom View and a Canvas? If yes, how can I get the x and y offset to check the coordinates?
Also, if my ideas are completely wrong, I would be very happy to see some examples on how this is done properly.
Thx! ;)
Perhaps the following code will help you to calculate coordinates with the gesture center and the scaling factor. I use this method in my class representing opengl-sprite.
void zoom(float scale, PointF midPoint) {
if (zoomFactor == MAX_ZOOM_FACTOR && scale > 1) return;
if (zoomFactor == MIN_ZOOM_FACTOR && scale < 1) return;
zoomFactor *= scale;
x = (x - midPoint.x) * scale + midPoint.x;
y = (y - height + midPoint.y) * scale + height - midPoint.y;
if (zoomFactor >= MAX_ZOOM_FACTOR) {
zoomFactor = MAX_ZOOM_FACTOR;
} else if (zoomFactor < MIN_ZOOM_FACTOR) {
zoomFactor = MIN_ZOOM_FACTOR;
x = 0;
y = 0;
}
}
X and Y coordinates are processed in different ways, because of distinction between directions of opengl coordinate system (right and up) and midPoint's coordinate system (right and down). midPoint is taken from MotionEvents coordinates.
All other operations are understandable, i think.
Hope it will help you.

Categories

Resources