I'm newbee in android-game developing and I would appreciate your help. I'd like to paint some graphics on the canvas which is supposed to be way larger than screen. So some scaling and moving would be great. I've read some questions but they usually answers only some details - not the whole concept.
I've tried use Camera
cam.save();
cam.translate(0f, 0f, -8f);
cam.applyToCanvas(canvas);
cam.restore();
This scales perfectly, but I am unable to decode the real touch coord.
I don't want to use openGL (it's overkill and also I'd like to start with sth simple)
Anyway, I tried canvas.scale(int, int) as well, but didn't work. I believe the Camera is the right way, but I'm lost.
So the question is: how to determinate real coord? Furthermore, It would be nice if some could share a piece of tutorial etc. or some concept of using canvas transformation. (Or maybe there more appropriate ways how to solve it)
Thanks in advance
you have to override the onTouchEvent
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private 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;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
mCanvas.drawPath(mPath, mPaint);
// kill this so we don't double draw
mPath.reset();
}
Related
I am creating an Android app where useres can draw and write using an active pen on a tablet.
The user can choose between different modes (e.g. pen, eraser, line, circle...) which offers them different tools.
The line and circle tools let the user draw a line/circle with the length/radius and direction where the user draws it. This is working quite well, but every time the user moves the pen another line/circle is drawn and it fills up the screen.
Image:
Code:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
canvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas c){
c.drawBitmap(mBitmap, 0, 0, bitmapPaint);
}
private float mX, mY, firstX, firstY;
private void touch_start(float x, float y) { //called from MotionEvent.ACTION_DOWN
path.moveTo(x, y);
firstX = x;
firstY = y;
mX = x;
mY = y;
}
private void touch_move(float x, float y) { //called from MotionEvent.ACTION_MOVE
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
switch(mode){
case "line":
canvas.drawLine(firstX, firstY, x, y, paint);
break;
case "circle":
canvas.drawCircle(firstX, firstY, (Math.abs(firstX-x) + Math.abs(firstY-y))/1.5f, paint); // divisor 1.5 just a rough value
break;
default:
canvas.drawPath(path, paint);
break;
}
mX = x;
mY = y;
}
Has anyone an idea how I could fix this?
Thanks in advance.
I found a solution by myself.
I created a new Canvas animationCanvas with its own animationBitmap for showing the circle while it is drawn.
The final circle is drawn in the method at MotionEvent.ACTION_UP.
This is the new touch_move:
private void touch_move(float x, float y) { //called from MotionEvent.ACTION_MOVE
path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
switch(mode){
case "line":
animationCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // clear previously drawn line (for animation)
animationCanvas.drawLine(firstX, firstY, x, y, paint);
break;
case "circle":
animationCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // clear previously drawn circle (for animation)
animationCanvas.drawCircle(firstX, firstY, (Math.abs(firstX-x) + Math.abs(firstY-y))/1.5f, paint); // divisor 1.5 just a rough value
break;
default:
canvas.drawPath(path, paint);
break;
}
mX = x;
mY = y;
}
Maybe not the best solution but it works :)
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 want to implement eraser for my painting app . I am able to erase with the following code
paint.setColor(0x00000000);
paint.setAlpha(0x00);
But after erasing when you start painting again it does not paint properly so any idea to erase paint please suggest.
I had same issue.
Need to set view's setLayerType.
yourView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
You can set it in constructor or by view's object.
Its done.
Try the below code.
paint.setAlpha(0xFF);//transperent color
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));//clear the draw
Also have a look at the sample FingerPaint.java in the api demos under the folder graphics.
setAlpha(int a)
Helper to setColor(), that only assigns the color's alpha value, leaving its r,g,b values unchanged.
http://developer.android.com/reference/android/graphics/Paint.html. Have a look a the
documentation.
Edit:
Also check this
https://code.google.com/p/android/issues/detail?id=54105#c1
This is should resolve this issue
private 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);
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
mPath.moveTo(mX, mY);
mX = x;
mY = y;
}
}
in touch_up() change it to:
private void touch_up() {
mPath.reset();
}
I want to draw a line using Canvas in Android. I tired this task in the ontouchevent using touchesmove, touchesup and touchesdown. The line appears when I touch end the mouse button. I want to draw a line like freehand drawing.
Here is my code,
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
invalidate();
break;
case MotionEvent.ACTION_MOVE:
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;
}
invalidate();
break;
case MotionEvent.ACTION_UP:
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
mPath = new Path();
paths.add(mPath);
mPath.reset();
invalidate();
break;
}
As far as I can see the drawPath() method is executed upon ACTION_UP. You'd need to call this method in ACTION_MOVE as well so your Path gets drawn on every move you make. For this to work you'd have to copy mPath.lineTo(mX, mY) to ACTION_MOVE as well of course.
I'm trying to get the canvas coordinates for an android app that I'm creating. It works great until I add the code to use a scale focus point (following two lines):
scalePoint.setX((int) detector.getFocusX());
scalePoint.setY((int) detector.getFocusY());
Here's my source code for my view class:
package us.kniffin.Jigsaw;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
public class TestView extends View {
private float mLastTouchX;
private float mLastTouchY;
private float mPosX;
private float mPosY;
private Rect rect;
private float cX, cY; // circle coords
// Scaling objects
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
// The focus point for the scaling
private float scalePointX;
private float scalePointY;
public TestView(Context context) {
super(context);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = new Paint();
p.setColor(Color.RED);
rect = canvas.getClipBounds();
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor, scalePointX, scalePointY);
canvas.translate(mPosX, mPosY);
canvas.drawCircle(cX, cY, 10, p);
canvas.restore();
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch(action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX()/mScaleFactor;// screen X position
final float y = ev.getY()/mScaleFactor;// screen Y position
cX = x - (rect.left * mScaleFactor) - mPosX; // canvas X
cY = y - (rect.top * mScaleFactor) - mPosY; // canvas Y
// Remember where we started
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_MOVE: {
final float x = ev.getX()/mScaleFactor;
final float y = ev.getY()/mScaleFactor;
cX = x - (rect.left * mScaleFactor) - mPosX; // canvas X
cY = y - (rect.top * mScaleFactor) - mPosY; // canvas Y
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX; // change in X
final float dy = y - mLastTouchY; // change in Y
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mLastTouchX = 0;
mLastTouchY = 0;
invalidate();
}
}
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
scalePointX = detector.getFocusX();
scalePointY = detector.getFocusY();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
invalidate();
return true;
}
}
}
Any ideas what I need to do to get this working?
Update: I replaced the code sample with another that has the same issue, but is simplified to the essentials
Update Again: The issue occurs after scaling. Before scaling, the coordinates are correct, but afterwards, the coordinates are too far to the right and below where you click. It appears that the more you zoom out, the more wrong they get.
Haha! Success! It took me almost all day, but I figured it out with a bunch of guess and checks.
Here's the bit of code that I needed:
case MotionEvent.ACTION_DOWN: {
final float x = (ev.getX() - scalePointX)/mScaleFactor;
final float y = (ev.getY() - scalePointY)/mScaleFactor;
cX = x - mPosX + scalePointX; // canvas X
cY = y - mPosY + scalePointY; // canvas Y
[snip]
}
And a similar bit of code for ACTION_MOVE
Whenever you use translate, scale, rotate (etc.) you are changing the canvas' matrix.
Generally, this matrix is used to tell you for each (x,y) where it is on the canvas.
When you create a canvas the matrix is the identity matrix:
(1 )
( 1 )
( 1)
But whenever you make one of these changes this matrix also changes.
If you want to know the right coordinates then before scaling/rotating you should use canvas.save() and after scaling use canvas.restore()
I recently came upon this problem while doing something very similiar and after some trial and error and lots of googling I ended up adapting this answer (https://stackoverflow.com/a/9945896/1131180) :
(e is a MotionEvent, so the best place to use this code would be in onTouch or onLongPress)
mClickCoords = new float[2];
//e is the motionevent that contains the screen touch we
//want to translate into a canvas coordinate
mClickCoords[0] = e.getX();
mClickCoords[1] = e.getY();
Matrix matrix = new Matrix();
matrix.set(getMatrix());
//this is where you apply any translations/scaling/rotation etc.
//Typically you want to apply the same adjustments that you apply
//in your onDraw().
matrix.preTranslate(mVirtualX, mVirtualY);
matrix.preScale(mScaleFactor, mScaleFactor, mPivotX, mPivotY);
// invert the matrix, creating the mapping from screen
//coordinates to canvas coordinates
matrix.invert(matrix);
//apply the mapping
matrix.mapPoints(mClickCoords);
//mClickCoords[0] is the canvas x coordinate and
//mClickCoords[1] is the y coordinate.
There are some obvious optimizations that can be applied here but I think it is more clear this way.