I've been trying to get the mouse coordinates from scaled canvas. The canvas.scale(scale, scale) gives me the correct coordinates but if i use canvas.scale(scale, scale, pivotX, pivotY) the coordinates are wrong.
//onDraw
canvas.scale(scaleFactor, scaleFactor); //default pivot x & y = 0, 0
//canvas.scale(scaleFactor, scaleFactor, pivotX, pivotY);
private float pixelWidth = 480;
private float pixelHeight = 320;
//onTouchEvent
touchX = (int) event.getX() * pixelWidth / getWidth();
touchX = (int) event.getY() * pixelHeight / getHeight();
worldX = touchX / scaleFactor;
worldY = touchY / scaleFactor;
What do i need to do to get the correct coordinates from canvas.scale with different pivot other than default 0, 0? I've read & tried about the matrix too but no luck.
solved it using matrix and this code from https://stackoverflow.com/a/20011005/7334711
Matrix matrix = new Matrix();
public void render(Canvas canvas) {
canvas.save();
matrix.setScale(mScaleFactor, mScaleFactor, pivotX, pivotY);
canvas.setMatrix(matrix);
...
float[] mv = new float[9];
#Override
public boolean onTouchEvent(MotionEvent event) {
// Get the values from the matrix into the float array
matrix.getValues(mv);
float touchX = (event.getX()*(1/mv[4]) - (mv[2]/mv[4]));
float touchY = (event.getY()*(1/mv[4]) - (mv[5]/mv[4]));
...
Related
I have an app in which it has a main canvas and i have added another canvas of bitmap on top of it. In the main canvas I have eraser in which it will detect when the user touches the bitmap area. I want to know the x and y inside the bitmap where the eraser touches and so on while moving from main canvas since the bitmap canvas is different from the main canvas. I want the x and y of the main canvas be the same with the bitmap canvas to be move. Thanks
Here's my snippet
public void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
if(istouchingBitmap(x, y) == true){
float xRatio = (float)bitmap.getWidth() / parent.getWidth();
float yRatio = (float)bitmap.getHeight() / parent.getHeight();
float xPos = lastX * xRatio;
float yPos = lastY * yRatio;
eraseBitmap(bitmap, xPos , yPos , 5);
}
}
function to detect the bitmap when touch
/**
*
* #param x The X location of the cursor in main View.
* #param y The Y location of the cursor in main View.
* #return This is only used to detect if the cursor is touching the Bitmap Image.
*/
public boolean istouchingBitmap(float x, float y) {
matrix.invert(inverse);
float[] pts = {x, y};
inverse.mapPoints(pts);
if (!bounds.contains(pts[0], pts[1])) {
return false;
}
// copy the location
lastX = x;
lastY = y;
return Color.alpha(bitmap.getPixel((int) pts[0], (int) pts[1])) != 0;
}
You only need to get the X and Y values from the touch event.
Then sustract the top,left margin/offset values respectively.
The result is the X,Y coordinate relative to the start of the bitmap.
PD: When doing this, i was having problems handling the status bar height, since the way of measuring it changes between Android version and device model.
Hope this helps.
Finally :). This code will map the screen X and Y to the x and y inside the bitmap. Hope it help others. Good luck
float[] point = new float[] {lastX, lastY};
Matrix inverse = new Matrix();
matrix.invert(inverse);
inverse.mapPoints(point);
bitmapX = point[0];
bitmapY = point[1];
canvas.drawCircle(bitmapX, bitmapY, radius, mPaint); // punch a hole
I have an arc and i wish to draw scale marks at 0, 45, 90, 135, 180 degrees, can anyone help me with the math needed to achive the x,y of points 5 and 30 on this sketch?:
here is my code for drawing the 1 scale mark.
private void drawScale(Canvas canvas) {
//canvas.drawOval(scaleRect, scalePaint);
canvas.save();
Paint p = new Paint();
p.setColor(Color.WHITE);
p.setStrokeWidth(10f);
canvas.drawLine(rectF.left-getWidth()/20, rectF.height()/2, rectF.left, rectF.height()/2, p);
canvas.restore();
}
You can calculate its rotation using sin and cos. Lets assume that you have zero point A and want to rotate it to point B which is rotated for 30°.
Something like this:
Basically new point is at (cx+x,cy+y). In this particular case definition of sin and cos would be next:
sin = x/R
cos = y/R
It is not hard to get exact x and y. So to rotate point on particular angle in circle with know radius we need to calculate coordinates in next way:
x = cx + sin(angle) * R;
y = cy + cos(angle) * R;
Now lets get back to Android and Canvas!
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
float cx = getWidth() / 2f;
float cy = getHeight() / 2f;
float scaleMarkSize = getResources().getDisplayMetrics().density * 16; // 16dp
float radius = Math.min(getWidth(), getHeight()) / 2;
for (int i = 0; i < 360; i += 45) {
float angle = (float) Math.toRadians(i); // Need to convert to radians first
float startX = (float) (cx + radius * Math.sin(angle));
float startY = (float) (cy - radius * Math.cos(angle));
float stopX = (float) (cx + (radius - scaleMarkSize) * Math.sin(angle));
float stopY = (float) (cy - (radius - scaleMarkSize) * Math.cos(angle));
canvas.drawLine(startX, startY, stopX, stopY, scalePaint);
}
canvas.restore();
}
Code will draw marks with step of 45°. Note you need to convert angle to radians and for Y axis I used minus cause on canvas it is flipped. Here is what I have got:
If you know the point at the center of the circle and the radius of the circle it becomes pretty easy if you use vectors to your advantage.
First you're gonna need the unit vectors at each angle
0 deg -> (-1,0)
45 deg -> (-1/sqrt(2), (1/sqrt(2))
90 deg -> (0,1)
135 deg -> (1/sqrt(2), (1/sqrt(2))
180 deg -> (1,0)
You can then calculate the necessary points using the formula below
point = center + (unit vector * distance from center)
Here is a more concrete example since andrew added one.
private static final float RADIUS = 400.0f;
private static final float MARK_LENGTH = 30.0f;
private static final UnitVector[] UNIT_VECTORS = new UnitVector[] {
new UnitVector(-1,0), // 0 deg
new UnitVector((float) (-1/Math.sqrt(2)), (float) (1/Math.sqrt(2))), // 45 deg
new UnitVector(0, 1), // 90 deg
new UnitVector((float) (1/Math.sqrt(2)), (float) (1/Math.sqrt(2))), // 135 deg
new UnitVector(1, 0), // 180 deg
new UnitVector((float) (1/Math.sqrt(2)), (float) (-1/Math.sqrt(2))), // 225 deg
new UnitVector(0, -1), // 270 deg
new UnitVector((float) (-1/Math.sqrt(2)), (float) (-1/Math.sqrt(2))), // 315 deg
};
static class UnitVector {
final float x;
final float y;
UnitVector(final float x, final float y) {
this.x = x;
this.y = y;
}
}
// Call this from onDraw
public void drawMarks(final Canvas canvas) {
for (final UnitVector unitVector : UNIT_VECTORS) {
this.drawMarkWithVector(unitVector, canvas);
}
}
private void drawMarkWithVector(final UnitVector unitVector, final Canvas canvas) {
final float centerPointX = this.getWidth() / 2;
final float centerPointY = this.getHeight() / 2;
final float startX = centerPointX + (unitVector.x * RADIUS);
final float startY = centerPointY + (unitVector.y * RADIUS);
final float endX = centerPointX + (unitVector.x * (RADIUS + MARK_LENGTH));
final float endY = centerPointY + (unitVector.y * (RADIUS + MARK_LENGTH));
canvas.drawLine(startX, startY, endX, endY, this.paint);
}
Here is the result of the code above
I'm having a hard time implementing the rotation and movement of an on-screen OpenGL shape. Basically, what I want to achieve is being able to control the shape using a touch screen. Wherever I touch, it should rotate to that direction and start moving towards until it gets there.
Here's the code that sets up the frustum and the camera in onSurfaceChanged():
glViewport(0, 0, width, height);
float sizeRatio = (float) width / height;
Matrix.frustumM(
projectionMatrix, 0, -sizeRatio, sizeRatio, -1.0f, 1.0f, 1.0f, 20.0f
);
Matrix.setLookAtM(
viewMatrix, 0, 0, 0, -60, 0f, 0f, 0f, 0.0f, 1.0f, 0.0f
);
Matrix.multiplyMM(globalMvpMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
and here's how the touch input is handled in onTouchEvent() (shape is an object that stores position and rotation and then draws the shape on the screen):
lastTouchX = event.getX();
lastTouchY = event.getY();
float shapePosX = shape.getPositionX();
float shapePosY = shape.getPositionY();
int[] viewport = new int[4];
glGetIntegerv(GL_VIEWPORT, viewport, 0);
float[] unprojectedTouchCoords = new float[4];
GLU.gluUnProject(
lastTouchX, lastTouchY, 0,
viewMatrix, 0,
projectionMatrix, 0,
viewport, 0,
unprojectedTouchCoords, 0
);
float unprojectedX = unprojectedTouchCoords[0] / unprojectedTouchCoords[3];
float unprojectedY = unprojectedTouchCoords[1] / unprojectedTouchCoords[3];
float rotation = 90 - (float) Math.toDegrees(Math.atan2(
shapePosY - unprojectedY, shapePosX - unprojectedX
));
shape.setRotation(rotation);
float moveX = 2 * (float) Math.cos(Math.toRadians(rotation));
float moveY = 2 * (float) Math.sin(Math.toRadians(rotation));
shape.move(moveX, moveY);
However, it doesn't seem to work well. The shape is moving in the wrong direction, and the rotation is only correct if the position of the shape is (0, 0). In any other case, it breaks.
I guess this problem involves distance between the camera and the shape in the OpenGL space, but I have no idea where and how to fix that. I tried bringing the camera closer to the shape but with no apparent improvement.
Can you please help me with this one? I'm getting really frustrated.
I haven't looked deeply at your code but I really want to give you a suggestion: the order of transformations matters in OpenGL, because they are cumulative.
This means that rotate and translate is different than translate and rotate.
Here is a good starting point to understand what I'm saying.
Seems like you have co-ordinates calculation problem. You need to #Override the onTouchEvent method of GLSurfaceView, rest is below
private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
private float mPreviousX;
private float mPreviousY;
#Override
public boolean onTouchEvent(MotionEvent e) {
// MotionEvent reports input details from the touch screen
// and other input controls. In this case, you are only
// interested in events where the touch position changed.
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = x - mPreviousX;
float dy = y - mPreviousY;
// reverse direction of rotation above the mid-line
if (y > getHeight() / 2) {
dx = dx * -1 ;
}
// reverse direction of rotation to left of the mid-line
if (x < getWidth() / 2) {
dy = dy * -1 ;
}
mRenderer.setAngle(
mRenderer.getAngle() +
((dx + dy) * TOUCH_SCALE_FACTOR)); // = 180.0f / 320
requestRender();
}
mPreviousX = x;
mPreviousY = y;
return true;
}
Hi,
I have an application which zoom/pans the image within screen bounds.
I have custom ImageView class inside my CustomFrameLayout as defined below.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/darker_gray" >
<com.example.panzoomapplication.panzoom.CustomFrameLayout
android:id="#+id/custom_frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.example.panzoomapplication.panzoom.CustomImageView
android:id="#+id/drawing_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawingCacheQuality="high"
android:layout_gravity="center"
android:src="#drawable/image"
android:adjustViewBounds="true" />
</com.example.panzoomapplication.panzoom.CustomFrameLayout>
</FrameLayout>
I am scaling canvas in CustomFrameLayout class using,
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(MIN_ZOOM, Math.min(mScaleFactor, MAX_ZOOM));
calculatePanZoomMatrix();
return true;
}
}
private void calculatePanZoomMatrix() {
mPanZoomMatrix.reset();
// scale the view w.r.t to center of the canvas.
mPanZoomMatrix.setScale(mScaleFactor, mScaleFactor, getWidth() / 2, getHeight() / 2);
invalidate();
}
and in ondraw() of CustomFrameLayout,
#Override
protected void onDraw(Canvas canvas) {
canvas.concat(mPanZoomMatrix);
}
The canvas zooms well and takes correct touch position when mScalFactor = 1 (or in initial state). But when scaled by a factor greater than 1, the touch event coordinate shifts by a certain amount. I have tried to simulate this issue by drawing a circle at touch position in CustomImageView class.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setPathEffect(null);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setColor(getResources().getColor(R.color.red));
if (mCirclePoint != null)
canvas.drawCircle(touchEvent.x, touchEvent.y, 6, paint);
}
The circle draws well at initial state of canvas (mScalFactor = 1), but shifts its position from actual touch point when scaled by a factor greater than 1. I have tried to fix this issue using matrix as follows,
public float[] getAbsolutePosition(float touchX, float touchY) {
float[] scaledPoints = new float[2];
scaledPoints[0] = touchX ;
scaledPoints[1] = touchY;
Matrix matrix = new Matrix();
matrix.reset();
foat scaleFactor = mCustomFrameLayout.getScaleFactor();
float centerX = mCustomFrameLayout.getCenterScaleX();
float centerY = mCustomFrameLayout.getCenterScaleY();
matrix.setScale(scaleFactor, scaleFactor, centerX, centerY);
matrix.mapPoints(scaledPoints);
return scaledPoints;
}
but end up with same problem.
Could you please help me to fix this issue? I have googled a lot but with no perfect solution.*
You shouldn't use Canvas to scale, is better to use matrix
to get touch coordinates from image(not screen):
void coorImagen(MotionEvent e){
float []m = new float[9];
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X] * -1;
float transY = m[Matrix.MTRANS_Y] * -1;
float scaleX = m[Matrix.MSCALE_X];
float scaleY = m[Matrix.MSCALE_Y];
lastTouchX = (int) ((e.getX() + transX) / scaleX);
lastTouchY = (int) ((e.getY() + transY) / scaleY);
lastTouchX = Math.abs(lastTouchX);
lastTouchY = Math.abs(lastTouchY);
}
to get coordinates of screen use:
e.getX();
e.getY();
i am translating and scaling image by matrix, now i have matrix values . so from matrix value i want to convert my touch coordinates to its position which is getting by matrix. so what should i do for that ?
please help me asap.
private void drawPoint(float x, float y, float pressure, float width) {
// left is tx of matrix
// top is ty of matrix
float curX = (x - left) / (scale * scale);
float curY = (y - top) / (scale * scale);
canvas.drawPoint((curX - left), (curY - top) , mPaint);
}
I know it's an old post, but I had the same problem.
I have an image which is applied a matrix transformation, first I resize it, and then I do a translation.
image = new Matrix();
image.setScale(zoom, zoom);
Paint drawPaint = new Paint();
drawPaint.setAntiAlias(true);
drawPaint.setFilterBitmap(true);
float centerScaledWidth = image_center.x * zoom / 2;
float centerScaledHeigth = image_center.y * zoom / 2;
image.postTranslate(screen_center.x - centerScaledWidth,
screen_center.y - centerScaledHeigth);
canvas.drawBitmap(bmp, image, drawPaint);
To get the points touched on the image I get trace of the matrix image and this is the method:
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
float[] pts = {x, y};
Matrix m = new Matrix();
// This is the magic, I set the new matrix m
// as the inverse of
// the matrix applied to the image
image.invert(m);
// transform the points using the inverse matrix
m.mapPoints(pts);
// do what you have to do
.......
Log.i("transformed", pts[0] +" "+pts[1]);
break;
}
}
return super.onTouchEvent(ev);
}