My issue is that I'm drawing rectangles to a screen and want to be able to scroll in a direction and continue drawing. This is meant to be a basic house plan drawing application.
I begin by drawing a square:
I then click the MOVE SCREEN button and switch to "move mode". I pinch zoom out to draw the adjacent room:
I then want to be able to draw this:
However, as soon as I click DRAW mode and start drawing the second room, this happens:
I.e. it reverts to the original zoom and draws in the wrong place. I realize its probably my code in the onDraw() method. Here's my code:
class HomerView extends View { // the custom View for drawing on
// set up Bitmap, canvas, path and paint
private Bitmap myBitmap; // the initial image we turn into our canvas
private Canvas myCanvas; // the canvas we are drawing on
private Rect myRect; // the mathematical path of the lines we draw
private Paint myBitmapPaint; // the paint we use to draw the bitmap
// get the width of the entire tablet screen
private int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
// get the height of the entire tablet screen
private int screenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
private int mX, mY, iX, iY; // current x,y and initial x,y
private static final float TOUCH_TOLERANCE = 4;
private static final int INVALID_POINTER_ID = -1;
private float mPosX;
private float mPosY;
private float mLastTouchX;
private float mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public HomerView(Context context) { // constructor of HomerView
super(context);
myBitmap = Bitmap.createBitmap(screenWidth, screenHeight,
Bitmap.Config.ARGB_8888); // set our drawable space - the bitmap
// which becomes the canvas we draw on
myCanvas = new Canvas(myBitmap); // set our canvas to our bitmap which
// we just set up
myRect = new Rect(); // make a new rect
myBitmapPaint = new Paint(Paint.DITHER_FLAG); // set dither to ON in our
// saved drawing - gives
// better color
// interaction
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
public HomerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
public HomerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
protected void onDraw(Canvas canvas) { // method used when we want to draw
// something to our canvas
super.onDraw(canvas);
if (addObjectMode == true || addApplianceMode == true) {
canvas.drawColor(Color.TRANSPARENT); // sets canvas colour
canvas.drawBitmap(myBitmap, 0, 0, myBitmapPaint); // save the canvas
// to bitmap - the
// numbers are the
// x, y coords we
// are drawing
// from
canvas.drawRect(myRect, myPaint); // draw the rectangle that the
// user has drawn using the paint
// we set up
} else if (moveMode == true) {
canvas.save();
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor);
canvas.drawBitmap(myBitmap, 0, 0, myBitmapPaint); // if not present
// - nothing is
// moved
canvas.restore();
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { // if
// screen
// size
// changes,
// alter
// the
// bitmap
// size
super.onSizeChanged(w, h, oldw, oldh);
}
private void touch_Start(float x, float y) { // on finger touchdown
// check touch mode
iX = (int) (Math.round(x));
iY = (int) (Math.round(y));
mX = (int) (Math.round(x));
mY = (int) (Math.round(y));
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
} else if (addApplianceMode == true) {
// code to draw an appliance icon at mX, mY (with offset so icon is
// centered)
if (isLamp == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.lamp);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isPC == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.pc);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isKettle == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.kettle);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isOven == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.oven);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
} else if (isTV == true) {
Resources res = getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.tv);
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
}
}
}
private void touch_Move(float x, float y) { // on finger movement
float dX = Math.abs(x - mX); // get difference between x and my X
float dY = Math.abs(y - mY);
if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { // if coordinates
// are outside
// screen? if
// touching hard
// enough?
mX = (int) (Math.round(x));
mY = (int) (Math.round(y));
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
}
}
}
#SuppressWarnings("deprecation")
private void touch_Up() { // on finger release
if (addObjectMode == true) {
myRect.set(iX, iY, mX, mY);
myCanvas.drawRect(iX, iY, mX, mY, myPaint);
if (eraseMode == false) {
dialogStarter();
}
} else if (addApplianceMode == true) {
showDialog(DIALOG_DEVICE_ENTRY);
}
}
public boolean onTouchEvent(MotionEvent event) { // on any touch event
if (addObjectMode == true || addApplianceMode == true) {
float x = event.getX(); // get current X
float y = event.getY(); // get current Y
switch (event.getAction()) { // what action is the user performing?
case MotionEvent.ACTION_DOWN: // if user is touching down
touch_Start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE: // if user is moving finger while
// touched down
touch_Move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP: // if user has released finger
touch_Up();
invalidate();
break;
}
return true;
} else if (moveMode == true) {
mScaleDetector.onTouchEvent(event);
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = event.getX();
final float y = event.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = event.getPointerId(0);
invalidate();
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = event
.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a
// gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = event.getX(newPointerIndex);
mLastTouchY = event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
}
}
invalidate();
return true;
} else {
return false;
}
}
public void drawApplianceIcon() {
myCanvas.drawBitmap(bmp, iX - 50, iY - 50, myBitmapPaint);
makeToast("BMP drawn to canvas = " + bmp);
}
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(0.1f, Math.min(mScaleFactor, 10.0f));
invalidate();
return true;
}
}
}
Can anyone pick apart my code so that I can draw straight on the zoomed or panned image? Am I barking up the wrong tree and should I just use a vertical and horizontal scroll bar? Zooming isn't strictly necessary.
Any help would be much appreciated! Thanks.
Your onDraw isn't scaling the canvas when in non-move mode. That means as soon as you go into any other mode, you lose the scaling factor permanently. You need to fix that.
Related
As building upon this drawing library here, the library didn't provide a way to change stroke color or width of a single one but only changing those properties of all of the drawen strokes at the same time. So I decided to seperate each stroke with its own property to be drawen. However now the drawing stroke is not appearing in real-time when moving the finger on the screen. The whole stroke only appears upon the touch_up() function. I assume the problem is from canvas.drawPath(myPath, myPaint); in the onDraw() function but I can not manage to find what causes the problem. Does anyone know what the problem of the strokes not drawing in real-time is? Or how to fix it?
DrawingView.java:
public class DrawingView extends View {
private Paint canvasPaint;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
private ArrayList<float[]> PointsCal = new ArrayList<float[]>();
private ArrayList<float[]> Points = new ArrayList<float[]>();
private ArrayList<Integer> id = new ArrayList<Integer>();
private int counter;
public static WebSocket ws;
private OkHttpClient client;
Context myContext;
Stroke drawnStroke;
private List<Stroke> allStrokes;
public DrawingView(Context context, AttributeSet attr) {
super(context, attr);
setupDrawing();
myContext = context;
}
private void setupDrawing() {
allStrokes = new ArrayList<Stroke>();
drawnStroke = new Stroke();
canvasPaint = new Paint(Paint.DITHER_FLAG);
drawCanvas = new Canvas();
drawnStroke.paint.setAntiAlias(true);
drawnStroke.paint.setStyle(Paint.Style.STROKE);
drawnStroke.paint.setStrokeJoin(Paint.Join.ROUND);
drawnStroke.paint.setStrokeCap(Paint.Cap.ROUND);
counter = 0;
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
if (allStrokes != null) {
for (Stroke stroke : allStrokes) {
if (stroke != null) {
Path myPath = stroke.getPath();
Paint myPaint = stroke.getPaint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.STROKE);
//this function below is not rendering in real time
canvas.drawPath(myPath, myPaint);
}
}
}
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
drawnStroke.getPaint().setColor(UserSettings.color);
drawnStroke.getPaint().setStrokeWidth(UserSettings.width);
drawnStroke.getPath().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) {
drawnStroke.path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up() {
drawnStroke.path.lineTo(mX, mY);
drawCanvas.drawPath(drawnStroke.path, drawnStroke.paint);
allStrokes.add(drawnStroke);
drawnStroke = new Stroke();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
float coordinates[] = {0f, 0f};
float coord[] = {0f, 0f};
String eventString;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
coordinates[0] = x;
coordinates[1] = y;
PointsCal.add(coordinates);
Points.add(coordinates);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
coordinates[0] = x;
coordinates[1] = y;
PointsCal.add(coordinates);
coord[0] = coordinates[0] - PointsCal.get(PointsCal.size() - 2)[0];
coord[1] = coordinates[1] - PointsCal.get(PointsCal.size() - 2)[1];
Points.add(coord);
break;
case MotionEvent.ACTION_UP:
touch_up();
coordinates[0] = x;
coordinates[1] = y;
PointsCal.add(coordinates);
coord[0] = coordinates[0] - PointsCal.get(PointsCal.size() - 2)[0];
coord[1] = coordinates[1] - PointsCal.get(PointsCal.size() - 2)[1];
Points.add(coord);
String sample = "";
if (Points.size() > 2) {
counter++;
id.add(counter);
sample += ("#" + counter);
for (int i = 0; i < Points.size(); i++) {
sample += ("#" + Arrays.toString(Points.get(i)));
}
} else {
paths.remove(paths.size() - 1);
}
Points.clear();
PointsCal.clear();
invalidate();
break;
}
return true;
}
I believe this is because allStrokes.add(drawnStroke); is not called until the private method touch_up is called, so when iterating over allStrokes for drawing purposes, the current stroke is omitted. Try moving that line of code to touch_start.
I am using the following code to draw a path based on variable width changes on a Canvas, So far everything works fine and i can easily draw path using this code.
But the path drawn is not smooth, Especially when i draw a curved path all the lines look broken, Why does this happen? Is anything wrong in my code?
public class FingerPaint extends GraphicsActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
public void colorChanged(int color)
{
}
public class MyView extends View implements OnTouchListener
{
private static final float STROKE_WIDTH = 3f;
private Paint paint = new Paint();
private Path mPath = new Path();
ArrayList<Path> mPaths = new ArrayList<Path>();
private Canvas m_CanvasView;
private Bitmap m_CanvasBitmap;
int variableWidthDelta = 1;
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
ArrayList<Point> points = new ArrayList<Point>(64);
public MyView(Context context)
{
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(STROKE_WIDTH);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
m_CanvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
m_CanvasView = new Canvas(m_CanvasBitmap);
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(m_CanvasBitmap, 0f, 0f, null);
m_CanvasView.drawPath(mPath, paint);
}
public boolean onTouch(View arg0, MotionEvent event)
{
float x = event.getX();
float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
break;
}
case MotionEvent.ACTION_MOVE:
{
if (event.getPressure()>=0.00 && event.getPressure()<0.05)
{
variableWidthDelta = 1;
}
else if (event.getPressure()>=0.05 && event.getPressure()<0.10)
{
variableWidthDelta = 1;
}
else if (event.getPressure()>=0.10 && event.getPressure()<0.15)
{
variableWidthDelta = 2;
}
else if (event.getPressure()>=0.15 && event.getPressure()<0.20)
{
variableWidthDelta = 2;
}
else if (event.getPressure()>=0.20 && event.getPressure()<0.25)
{
variableWidthDelta = 1;
}
else if (event.getPressure() >= 0.25 && event.getPressure()<0.30)
{
variableWidthDelta = 1;
}
else if (event.getPressure() >= 0.30 && event.getPressure()<0.35)
{
variableWidthDelta = 2;
}
else if (event.getPressure() >= 0.35 && event.getPressure()<0.40)
{
variableWidthDelta = 2;
}
else if (event.getPressure() >= 0.40 && event.getPressure()<0.45)
{
variableWidthDelta = 3;
}
else if (event.getPressure() >= 0.45 && event.getPressure()<0.50)
{
variableWidthDelta = 4;
}
paint.setStrokeWidth(STROKE_WIDTH + variableWidthDelta);
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
points.add(new Point(event.getX(), event.getY()));
mPath = new Path();
mPath = generatePath();
}
break;
}
case MotionEvent.ACTION_UP:
{
break;
}
}
invalidate();
return true;
}
class Point
{
public final float x;
public final float y;
public Point(float x, float y)
{
this.x = x;
this.y = y;
}
}
public Path generatePath()
{
final float tangentScale = 0.3F;
int pointcount = points.size();
mPath.moveTo(points.get(0).x, points.get(0).y);
mPath.cubicTo(
points.get(0).x + (points.get(1).x - points.get(0).x)*tangentScale,
points.get(0).y + (points.get(1).y - points.get(0).y)*tangentScale,
points.get(1).x - (points.get(2).x - points.get(0).x)*tangentScale,
points.get(1).y - (points.get(2).y - points.get(0).y)*tangentScale,
points.get(1).x, points.get(1).y
);
for(int p=2; p<pointcount-1; p++)
{
mPath.cubicTo(
points.get(p-1).x + (points.get(p).x - points.get(p-2).x)*tangentScale,
points.get(p-1).y + (points.get(p).y - points.get(p-2).y)*tangentScale,
points.get(p).x - (points.get(p+1).x - points.get(p-1).x)*tangentScale,
points.get(p ).y - (points.get(p+1).y - points.get(p-1).y)*tangentScale,
points.get(p).x, points.get(p).y
);
}
mPath.cubicTo(
points.get(pointcount-2).x + (points.get(pointcount-1).x - points.get(pointcount-3).x)*tangentScale,
points.get(pointcount-2).y + (points.get(pointcount-1).y - points.get(pointcount-3).y)*tangentScale,
points.get(pointcount-1).x - (points.get(pointcount-1).x - points.get(pointcount-2).x)*tangentScale,
points.get(pointcount-1).y - (points.get(pointcount-1).y - points.get(pointcount-2).y)*tangentScale,
points.get(pointcount-1).x, points.get(pointcount-1).y
);
return mPath;
}
}
}
The onTouch event is not fired fast enough, so if you draw curves fast enough you will see this happening.
You can improve the point resolution by getting all recorded points between this onTouch event and the last one. Use event.getHistorySize() to get the amount of available points, and get their positions with event.getHistoricalX(int) and event.getHistoricalY(int).
If you still have problems, you would probably have to implement some sort of interpolation of the points.
Here is a simple example using cubicTo() to get a smooth path:
points is an array of all points that are to be drawn.
pointcount is the amount of points
//This code is untested
public Path generatePath(){
Path path = new Path();
if( points.size() < 3 ) return path;
final float tangentScale = 0.3;
path.moveTo(points[0].x, points[0].y);
path.cubicTo(
points[0].x + (points[1].x - points[0].x)*tangentScale,
points[0].y + (points[1].y - points[0].y)*tangentScale,
points[1].x - (points[2].x - points[0].x)*tangentScale,
points[1].y - (points[2].y - points[0].y)*tangentScale,
points[1].x, points[1].y
);
for(int p=2; p<pointcount-1; p++){
path.cubicTo(
points[p-1].x + (points[p].x - points[p-2].x)*tangentScale,
points[p-1].y + (points[p].y - points[p-2].y)*tangentScale,
points[p ].x - (points[p+1].x - points[p-1].x)*tangentScale,
points[p ].y - (points[p+1].y - points[p-1].y)*tangentScale,
points[p].x, points[p].y
);
}
path.cubicTo(
points[pointcount-2].x + (points[pointcount-1].x - points[pointcount-3].x)*tangentScale,
points[pointcount-2].y + (points[pointcount-1].y - points[pointcount-3].y)*tangentScale,
points[pointcount-1].x - (points[pointcount-1].x - points[pointcount-2].x)*tangentScale,
points[pointcount-1].y - (points[pointcount-1].y - points[pointcount-2].y)*tangentScale,
points[pointcount-1].x, points[pointcount-1].y
);
return path;
}
Adapting this for your specific case:
(You will have to modify the above method to use .get() instead of array accessors, since I am using a list here)
//A class for holding x and y values:
class Point{
public final float x;
public final float y;
public Point(float x, float y){
this.x = x;
this.y = y;
}
}
//In your View:
ArrayList<Point> points = new ArrayList<Points>(64);
//In the onTouch-method if the tolerance is ok:
points.add(new Point(event.getX(), event.GetY());
mPath = generatePath();
The above will generate a rounded path that you can draw. Note that it uses all points, so they will all be redrawn every time (you might have to clear the canvas to avoid some artiefacts). You might want to move the generatePath-call to when you lift the finger, so you get a faster, but jagged preview path while drawing.
Am trying out an Android App, where am marking the points where the user does a long press.
For a touch-sensitive image view, I used the most of the code from here:
http://android-developers.blogspot.in/2010/06/making-sense-of-multitouch.html
http://developer.android.com/training/gestures/scale.html
along with some suggestions from various posts in stackoverflow.
This is the code for the touch image view :
public class SimpleImageView extends ImageView implements OnTouchListener,OnGestureListener {
public HashMap<meeCoordinates, Integer> plotPointsMap = new HashMap<meeCoordinates, Integer>();
private float mPosX = 0f;
private float mPosY = 0f;
private boolean didLongPress = false;
private float mLastTouchX;
private float mLastTouchY;
private float magicX;
private float magicY;
private static final int INVALID_POINTER_ID = -1;
Context context;
Canvas canvas;
private GestureDetector gestureScanner;
int deviceWidth, deviceHeight;
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
String savedItemClicked;
private int mActivePointerId = INVALID_POINTER_ID;
public SimpleTouchImageView(Context ctx) {
// The ‘active pointer’ is the one currently moving our object.
this(ctx, null, 0);
this.context = ctx;
}
public SimpleTouchImageView(Context ctx, AttributeSet attrs) {
this(ctx, attrs, 0);
this.context = ctx;
}
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
public SimpleTouchImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Create our ScaleGestureDetector
if (!this.isInEditMode()) {
mScaleDetector = new ScaleGestureDetector(context,new ScaleListener());
}
gestureScanner = new GestureDetector(context, this);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (mScaleDetector != null) {
mScaleDetector.onTouchEvent(ev);
}
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
Log.d(TAG, "onTouchEvent MotionEvent.ACTION_DOWN");
final float x = ev.getX();
final float y = ev.getY();
// Set them to the X, Y at the beginning of the touch event
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
// _handler.postDelayed(_longPressed, LONG_PRESS_TIME);
break;
}
case MotionEvent.ACTION_MOVE: {
// _handler.removeCallbacks(_longPressed);
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
if (mScaleDetector != null) {
if (!mScaleDetector.isInProgress()) {
// Calculate the distance moved
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
// Move the object
mPosX += dx;
mPosY += dy;
// Remember this touch position for the next move event
mLastTouchX = x;
mLastTouchY = y;
invalidate();
}
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
// _handler.removeCallbacks(_longPressed);
mActivePointerId = INVALID_POINTER_ID;
// Calculate the Actual X & Y
Drawable drawable = this.getDrawable();
Rect imageBounds = drawable.getBounds();
int intrinsicHeight = this.getDrawable().getIntrinsicHeight();
int intrinsicWidth = this.getDrawable().getIntrinsicWidth();
int imageOffsetX = (int) (ev.getX() - imageBounds.left);
int imageOffsetY = (int) (ev.getY() - imageBounds.top);
float[] f = newfloat[9];
this.getImageMatrix().getValues(f);
final float scaleX = f[Matrix.MSCALE_X];
final float scaleY = f[Matrix.MSCALE_Y];
final int actualWd = Math.round(intrinsicWidth * mScaleFactor);
final int actualHt = Math.round(intrinsicHeight * mScaleFactor);
// CALCULATE THE X,Y COORDINATES CORRESPONDING TO THE POINT OF TOUCH
// IN THE ACTUAL IMAGE,
magicX = ((ev.getX() + (-1 * mPosX)) / mScaleFactor);
magicY = ((ev.getY() + (-1 * mPosY)) / mScaleFactor);
mLastTouchX = ev.getX();
mLastTouchY = ev.getY();
if (didLongPress == true) {
// STORE THE Point where user did long press IN AN ARRAY
plotPointsMap.put ( coordinates, marker );
invalidate();
didLongPress = false;
}
break;
}
case MotionEvent.ACTION_CANCEL: {
// _handler.removeCallbacks(_longPressed);
Log.d(TAG, "onTouchEvent MotionEvent.ACTION_CANCEL");
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
// _handler.removeCallbacks(_longPressed);
Log.d(TAG, "onTouchEvent MotionEvent.ACTION_POINTER_UP");
break;
}
}
// return true;
return gestureScanner.onTouchEvent(ev);
}
/** Determine the space between the first two fingers */
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
/** Calculate the mid point of the first two fingers */
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
}
#Override
public void onDraw(Canvas canvas) {
WindowManager wm = (WindowManager) this.context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
deviceWidth = metrics.widthPixels;
deviceHeight = metrics.heightPixels;
canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
if (this.getDrawable() != null) {
canvas.save();
canvas.translate(mPosX, mPosY);
Matrix matrix = new Matrix();
matrix.postScale(mScaleFactor, mScaleFactor, pivotPointX,pivotPointY);
canvas.drawBitmap(((BitmapDrawable) this.getDrawable()).getBitmap(), matrix,null);
// ---add the marker---
if ((plotPointsMap != null) && (plotPointsMap.size() > 0)) {
for (int index = 0; index < plotPointsMap.size(); index++) {
Set<MyCoordinates> setCoordinates = plotPointsMap.keySet();
if ((setCoordinates != null) && (setCoordinates.size() > 0)) {
Iterator<MyCoordinates> setIterator =setCoordinates.iterator();
if (setIterator != null) {
while (setIterator.hasNext()) {
MyCoordinates coordinates = setIterator.next();
int resource = plotPointsMap.get(coordinates).intValue();
int resourceId = R.drawable.location_marker;
Bitmap marker = BitmapFactory.decodeResource(getResources(), resourceId);
float xCoordinate = coordinates.getCoordinateX();
float yCoordinate = coordinates.getCoordinateY();
canvas.drawBitmap(marker, xCoordinate * mScaleFactor, yCoordinate * mScaleFactor, null);
}
}
}
}
}
canvas.restore();
}
}
#Override
public void setImageDrawable(Drawable drawable) {
// Constrain to given size but keep aspect ratio
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
mLastTouchX = mPosX = 0;
mLastTouchY = mPosY = 0;
mScaleFactor = Math.min((float) getLayoutParams().width/ width, (float) getLayoutParams().height/ height);
pivotPointX = ((float) getLayoutParams().width - (int) (width * mScaleFactor)) / 2;
pivotPointY = ((float) getLayoutParams().height - (int) (height * mScaleFactor)) / 2;
super.setImageDrawable(drawable);
}
float pivotPointX = 0f;
float pivotPointY = 0f;
...
...
}
The Issue
Let's keep aside scaling for now. Say, the image is loaded in its actual size. The point where the user touches is recorded correctly. Say, if the user touches at (120, 50) with x=120 and y=50, then it detects correctly as (120, 50). Am storing the points in an array and re-drawing a marker at each of the points in
public void onDraw(Canvas canvas)
The problem is that when the marker is drawn at the point (am printing it within onDraw(canvas)), the marker is drawn about 30px away from the actual point. i.e. if actual (x,y) is (120,50), then the marker image is drawn at about (150,80). Always there is this 30px difference. Why is this? Am breaking my head on this since the last two weeks in vain. Can someone please help?
here is an image showing the touch point in blue (over the letter 'S' in 'U.S.'), and you can see that the black marker is being drawn off the touch point :
EDIT
When I pan the image, inverse.mapPoints() doesn't account for the offset of the image away from the screen.
For example, here is the image when the App begins, this is the image loaded
and when the user pans, the image might go to the left of the screen-edge as below:
In such a case, inverse.mapPoints() only returns the value from the edge of the screen, but I want the value from the edge of the original image. How do I do it?
I tried googl'ing and tried few things given in stackoverflow (like getTop(), and getLocationOnscreen()) but in vain.
can you help?
1 use getImageMatrix()
2 invert it - invert()
3 use inverted.mapPoints()
I'm developing an application in which I'm pasting images, doing drawing and painting on canvas. This app can also Scale up/down the canvas or drag it to different location.
My problem is: I can't get the correct canvas coordinates after scaling or dragging the canvas. I want to draw finger paint after the canvas is scaled or dragged but unable to retrieve the right place where i've touched..:(
Also I'm new bee. Here is the code.
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor, super.getWidth() * 0.5f,
super.getHeight() * 0.5f);
mIcon.draw(canvas);
for (Path path : listPath) {
canvas.drawPath(path, paint);
}
canvas.restore();
}
public TouchExampleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#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();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
case MotionEvent.ACTION_UP: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
float objectNewX,objectNewY;
if (mScaleFactor >= 1) {
objectNewX = ev.getX() + (ev.getX() - super.getWidth() * 0.5f) * (mScaleFactor - 1);
objectNewY = ev.getY() + (ev.getY() - super.getHeight() * 0.5f) * (mScaleFactor - 1);
} else {
objectNewX = ev.getX() - (ev.getX() - super.getWidth() * 0.5f) * (1 - mScaleFactor);
objectNewY = ev.getY() - (ev.getY() - super.getHeight() * 0.5f) * (1 - mScaleFactor);
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
path = new Path();
path.moveTo(objectNewX,objectNewY);
path.lineTo(objectNewX,objectNewY);
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
path.lineTo(objectNewX,objectNewY);
listPath.add(path);
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
path.lineTo(objectNewX,objectNewY);
listPath.add(path);
}
return true;
}
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(0.1f, Math.min(mScaleFactor, 5.0f));
invalidate();
return true;
}
}
Done it finally by myself.
Draw everything by applying this formula to (px,py) coordinates:
float px = ev.getX() / mScaleFactor + rect.left;
float py = ev.getY() / mScaleFactor + rect.top;
rect = canvas.getClipBounds();
//Get them in on Draw function and apply above formula before drawing
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
clipBounds_canvas = canvas.getClipBounds();
/////Do whatever you want to do..!!!
};
#Override
public boolean onTouchEvent(MotionEvent ev) {
int x = ev.getX() / zoomFactor + clipBounds_canvas.left;
int y = ev.getY() / zoomFactor + clipBounds_canvas.top;
//Use the above two values insted of ev.getX() and ev.getY();
}
Hope this will help.
How can I write text on an image and then save it in Android?
Basically I want to let user write something on the images which my camera app will click for them. I can write and show it to them using the onDraw method on the preview of the camera. But after the user has clicked the picture I want to write the text over the picture and then save it.
You can put an EditText and write into it, and after writing, you first convert it to Bitmap like:
Bitmap bmp = Bitmap.createBitmap(mEditText.getDrawingCache());
Now you can add created image bmp to your original image like this:
Call: Bitmap combined = combineImages(bgBitmap,bmp);
public Bitmap combineImages(Bitmap background, Bitmap foreground) {
int width = 0, height = 0;
Bitmap cs;
width = getWindowManager().getDefaultDisplay().getWidth();
height = getWindowManager().getDefaultDisplay().getHeight();
cs = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas comboImage = new Canvas(cs);
background = Bitmap.createScaledBitmap(background, width, height, true);
comboImage.drawBitmap(background, 0, 0, null);
comboImage.drawBitmap(foreground, matrix, null);
return cs;
}
You have to implement a canvas that allows the user to draw on it and then set the background of that canvas to that particular image. This is just a guess but its somewhere there abouts.
Lets help you..
First of all you must have to use canvas for drawing. Make a custom view which extends ImageView. Here is the helping code for onDraw function..:
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
mIcon.setBounds(0, 0, mIcon.getMinimumWidth(),
mIcon.getMinimumHeight());
canvas.translate(mPosX, mPosY);
canvas.scale(mScaleFactor, mScaleFactor, canvas.getWidth() * 0.5f,
canvas.getWidth() * 0.5f);
mIcon.draw(canvas);
rect = canvas.getClipBounds();
for (Path path : listPath) {
canvas.drawPath(path, paint);
}
}
Now for onTouch:
#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: {
if (drawing) {
startPoint = new Point((int) ev.getX(), (int) ev.getY());
path = new Path();
float x = ev.getX() / mScaleFactor + rect.left;
float y = ev.getY() / mScaleFactor + rect.top;
path.moveTo(x, y);
path.lineTo(x, y);
mLastTouchX_1 = x;
mLastTouchY_1 = y;
}
else {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (drawing) {
float x = ev.getX() / mScaleFactor + rect.left;
float y = ev.getY() / mScaleFactor + rect.top;
path.moveTo(mLastTouchX_1, mLastTouchY_1);
path.lineTo(x, y);
mLastTouchX_1 = x;
mLastTouchY_1 = y;
}
else {
final int pointerIndex = ev
.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a
// gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
}
break;
}
case MotionEvent.ACTION_UP: {
path_helper_1 = new Path();
path_helper_2 = new Path();
endPoint = new Point((int) ev.getX(), (int) ev.getY());
int left, top, right, bottom;
left = endPoint.x - 10;
top = endPoint.y - 10;
right = endPoint.x + ((endPoint.y / 2));
bottom = endPoint.x + ((endPoint.y / 2));
float dx, dy;
dx = endPoint.x - startPoint.x;
dy = endPoint.y - startPoint.y;
dx = dx * -1;
dy = dy * -1;
// dx = dy = 100;
double cos = 0.866 * .1;
double sin = 0.500 * .1;
PointF end1 = new PointF((float) (endPoint.x + (dx * cos + dy
* -sin)), (float) (endPoint.y + (dx * sin + dy * cos)));
PointF end2 = new PointF((float) (endPoint.x + (dx * cos + dy
* sin)), (float) (endPoint.y + (dx * -sin + dy * cos)));
// path.moveTo(endPoint.x, endPoint.y);
//
// path.lineTo(endPoint.x, endPoint.y);
//
// path.lineTo(end1.x, end1.y);
//
// path.moveTo(endPoint.x, endPoint.y);
//
// path.lineTo(end2.x, end2.y);
//
//
// listPath.add(path);
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
break;
}
}
invalidate();
return true;
}
Also make a private class if you want to implement zooming functionality like this:
private class ScaleListener extends
ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
if (zoom) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
}
invalidate();
return true;
}
}
}
Take the idea from above code and make changes according to your requirements.
NOTE Above code implements Drawing And Zooming functionality. If you want to add text over images then like Path drawing you can also draw text on canvas using canvas.drawText. Make an arraylist if you want multiple text overs image.
Add text on bitmap :
public Bitmap drawTextToBitmap(Context gContext,
int gResId,
String gText) {
Resources resources = gContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap =
BitmapFactory.decodeResource(resources, gResId);
android.graphics.Bitmap.Config bitmapConfig =
bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
// resource bitmaps are imutable,
// so we need to convert it to mutable one
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
// new antialised Paint
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// text color - #3D3D3D
paint.setColor(Color.rgb(61, 61, 61));
// text size in pixels
paint.setTextSize((int) (14 * scale));
// text shadow
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
// draw text to the Canvas center
Rect bounds = new Rect();
paint.getTextBounds(gText, 0, gText.length(), bounds);
int x = (bitmap.getWidth() - bounds.width())/2;
int y = (bitmap.getHeight() + bounds.height())/2;
canvas.drawText(gText, x, y, paint);
return bitmap;
}
Source : http://www.skoumal.net/en/android-how-draw-text-bitmap/
Add multi line text on bitmap :
public Bitmap drawMultilineTextToBitmap(Context gContext,
int gResId,
String gText) {
// prepare canvas
Resources resources = gContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
// resource bitmaps are imutable,
// so we need to convert it to mutable one
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
// new antialiased Paint
TextPaint paint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
// text color - #3D3D3D
paint.setColor(Color.rgb(61, 61, 61));
// text size in pixels
paint.setTextSize((int) (14 * scale));
// text shadow
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
// set text width to canvas width minus 16dp padding
int textWidth = canvas.getWidth() - (int) (16 * scale);
// init StaticLayout for text
StaticLayout textLayout = new StaticLayout(
gText, paint, textWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
// get height of multiline text
int textHeight = textLayout.getHeight();
// get position of text's top left corner
float x = (bitmap.getWidth() - textWidth)/2;
float y = (bitmap.getHeight() - textHeight)/2;
// draw text to the Canvas center
canvas.save();
canvas.translate(x, y);
textLayout.draw(canvas);
canvas.restore();
return bitmap;
}
Source : http://www.skoumal.net/en/android-drawing-multiline-text-on-bitmap/