android (multitouch) drawing app undone function - android

I am working on a drawing app, the onTouchEvents are standard, and would like to add Undo() function to remove the last drawn path.
Declarations:
int thelastLineId=0;
private Bitmap bitmap; // drawing area for display or saving
private Canvas bitmapCanvas; // used to draw on bitmap
private Paint paintScreen; // use to draw bitmap onto screen
private Paint paintLine; // used to draw lines onto bitmap
private HashMap<Integer, Path> pathMap; // current Paths being drawn
private HashMap<Integer, Path> reservedpathMap; // for saving the paths being undone
private HashMap<Integer, Point> previousPointMap; // current Points
Constructor:
pathMap = new HashMap<Integer, Path>();
reservedpathMap = new HashMap <Integer,Path>(); // for storing path being undone
previousPointMap = new HashMap<Integer, Point>();
onDraw:
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(bitmap, 0, 0, paintScreen);
// for each path currently being drawn
for (Integer key : pathMap.keySet())
canvas.drawPath(pathMap.get(key), paintLine); // draw line
}
onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getActionMasked(); // event type
int actionIndex = event.getActionIndex(); // pointer (i.e., finger)
if (action == MotionEvent.ACTION_DOWN)
{
touchStarted(event.getX(actionIndex), event.getY(actionIndex), event.getPointerId(actionIndex));
}
else if (action == MotionEvent.ACTION_UP)
{
touchEnded(event.getPointerId(actionIndex));
}
else
{
touchMoved(event);
}
invalidate();
return true;
}
touchStarted:
private void touchStarted(float x, float y, int lineID) // lineID represents how many fingers, 1 finger 1 line
{
Path path; // used to store the path for the given touch id
Point point; // used to store the last point in path
// if there is already a path for lineID
if (pathMap.containsKey(lineID))
{
path = pathMap.get(lineID); // get the Path
path.reset(); // reset the Path because a new touch has started
point = previousPointMap.get(lineID); // get Path's last point
}
else
{
path = new Path(); // create a new Path
pathMap.put(lineID, path); // add the Path to Map
point = new Point(); // create a new Point
previousPointMap.put(lineID, point); // add the Point to the Map
}
path.moveTo(x, y);
point.x = (int) x;
point.y = (int) y;
}
touchMoved:
private void touchMoved(MotionEvent event)
{
// for each of the pointers in the given MotionEvent
for (int i = 0; i < event.getPointerCount(); i++)
{
// get the pointer ID and pointer index
int pointerID = event.getPointerId(i);
int pointerIndex = event.findPointerIndex(pointerID);
// if there is a path associated with the pointer
if (pathMap.containsKey(pointerID))
{
float newX = event.getX(pointerIndex);
float newY = event.getY(pointerIndex);
// get the Path and previous Point associated with this pointer
Path path = pathMap.get(pointerID);
Point point = previousPointMap.get(pointerID);
float deltaX = Math.abs(newX - point.x);
float deltaY = Math.abs(newY - point.y);
if (deltaX >= TOUCH_TOLERANCE || deltaY >= TOUCH_TOLERANCE)
{
path.quadTo(point.x, point.y, ((newX + point.x)/2),((newY + point.y)/2));
point.x = (int) newX ;
point.y = (int) newY ;
}
}
}
touchEnded:
private void touchEnded(int lineID)
{
Path path = pathMap.get(lineID); // get the corresponding Path
bitmapCanvas.drawPath(path, paintLine);
path.reset();
}
UNDO:
public void undo()
{
Toast.makeText(getContext(), "undo button pressed" + thelastLineId, Toast.LENGTH_SHORT).show();
Path path = pathMap.get(thelastLineId);
reservedpathMap.put(thelastLineId, path); // add the Path to reservedpathMap for later redo
pathMap.remove(thelastLineId);
invalidate();
}
Question:
I am trying to implement the UNDO method using the code as shown above: trying to remove the thelastLindId key from the HashMap pathmap (and put to HashMap reservedpathMap for later Redo) such that when invalidate() it will invoke the OnDraw() and to redraw by
for (Integer key : pathMap.keySet())
canvas.drawPath(pathMap.get(key), paintLine);
However, pressing the undo button can initiate the toast of "undo is clicked" but the last drawn line fails to disappear.
Could anybody please give me a clue for the Undo() and Redo() ? Many thanks in advance!!

As I can understand what you want to achieve, you want to be able to draw lines on canvas and after that make UNDO functionality to your project. First of all I think the moment when you add the path to your array should be when the user lift his finger , in your touchEnded method. Second of all I don't really get the thing which you are explaining about two / three fingers? Are you supporting multi touch in your canvas? Here is an implementation which I was using before in some example for drawing on canvas with undo implementation. Hope it will help you to make the things more clear :
public void onClickUndo () {
if (paths.size()>0) {
undonePaths.add(paths.remove(paths.size()-1))
invalidate();
}
else
//toast the user
}
public void onClickRedo (){
if (undonePaths.size()>0) {
paths.add(undonePaths.remove(undonePaths.size()-1))
invalidate();
}
else
//toast the user
}
and here are the equalents of your touch methods :
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
for (Path p : paths){
canvas.drawPath(p, mPaint);
}
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 0;
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 = new Path();
paths.add(mPath);
}
As you can see from here paths is the arraylist where I am storing my paths. If you tell me why you need to put your paths in hashmap maybe I can be more helpful.

Related

canvas.drawPath() is not drawing in real-time

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.

Curves are broken while drawing path in canvas

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.

Android using Canvas to apply an overlay causes null pointer exception

this has been driving me a little crazy today. I've implimented this class which enables me to draw lines over an image file. however I want to be able to to add a template mask at creation time. Whatever I try i.e. within the class or from the calling class it causes a Null pointer exception, if however I put the call into a button in the calling class then I can get it to work?
here is the class I'm using. You can see the commented out section, if I call "coverScreen()" from here it causes the NPE. Also from the main Activity where I create the maskBoard if I call "coverScreen()" it causes NPE. But as mentioned if I add a button, and put the "coverScreen()" into this button onclick then it works?
public class MaskBoard extends View {
private String TAG = "Test APP - maskboard";
private int mx = -1;
private int my = -1;
private Bitmap mainImage = null;
private Bitmap bm = null;
private Paint mPaint = null;
public final int CLOSER = 50;
public final int CLOSE = 100;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;
public MaskBoard(Context c, AttributeSet s) {
super(c, s);
String fileToMask = "/__temppic.png";
File imagePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES + "/temp/");
File imgFile = new File(imagePath+fileToMask);
if (imgFile.exists()) {
bm = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
Log.i(TAG, "found image");
}
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mPaint = new Paint();
mPaint.setAntiAlias(false);
mPaint.setDither(true);
mPaint.setColor(0xFFFE0000); // FFFF0000
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(50);
/* coverScreen() */
setEraser();
}
public void coverScreen() {
Bitmap maskImage = BitmapFactory.decodeResource(getResources(), R.drawable.mask); //get mask image
maskImage = Bitmap.createScaledBitmap(maskImage, bm.getWidth(), bm.getHeight(), true);
mPaint.setXfermode(null);
mCanvas.drawBitmap(maskImage,0,0,mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public void setEraser() {
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
public void setPaint() {
mPaint.setXfermode(null);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.i(TAG, "onsizechanged");
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
Log.i(TAG, "onDraw");
canvas.drawRect(0,0, getWidth(), getHeight(), mPaint);
canvas.drawColor(0xFFAAAAAA);
canvas.drawBitmap(bm, mx, my, null);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
Log.i(TAG, " touchdown: X(" + x + "), Y(" + 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;
Log.i(TAG, " touchmove: X(" + x + "), Y(" + 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();
}
#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);
// coverScreen() ;
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
}

Canvas Issues — scrolling and zooming while drawing (retaining perspective)

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.

Android Signature Capture [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am working on android application. In my project I have a task regarding signature capture i.e the user should keep his/her signature on the screen of the mobile and once save button is clicked the signature has to stored in the database. I have searched and found some links but still I didn't find the exact solution.
I also tried TouchPaint.java but there I didnt find the xml file for layout.
Could you please suggest us with some sample code? I will be thankful to you....
Here is the working Java version of Has AlTaiar's C# Signature View,
Took me a while to get it to work 100% correctly
public class CaptureSignatureView extends View {
private Bitmap _Bitmap;
private Canvas _Canvas;
private Path _Path;
private Paint _BitmapPaint;
private Paint _paint;
private float _mX;
private float _mY;
private float TouchTolerance = 4;
private float LineThickness = 4;
public CaptureSignatureView(Context context, AttributeSet attr) {
super(context, attr);
_Path = new Path();
_BitmapPaint = new Paint(Paint.DITHER_FLAG);
_paint = new Paint();
_paint.setAntiAlias(true);
_paint.setDither(true);
_paint.setColor(Color.argb(255, 0, 0, 0));
_paint.setStyle(Paint.Style.STROKE);
_paint.setStrokeJoin(Paint.Join.ROUND);
_paint.setStrokeCap(Paint.Cap.ROUND);
_paint.setStrokeWidth(LineThickness);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
_Bitmap = Bitmap.createBitmap(w, (h > 0 ? h : ((View) this.getParent()).getHeight()), Bitmap.Config.ARGB_8888);
_Canvas = new Canvas(_Bitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(_Bitmap, 0, 0, _BitmapPaint);
canvas.drawPath(_Path, _paint);
}
private void TouchStart(float x, float y) {
_Path.reset();
_Path.moveTo(x, y);
_mX = x;
_mY = y;
}
private void TouchMove(float x, float y) {
float dx = Math.abs(x - _mX);
float dy = Math.abs(y - _mY);
if (dx >= TouchTolerance || dy >= TouchTolerance) {
_Path.quadTo(_mX, _mY, (x + _mX) / 2, (y + _mY) / 2);
_mX = x;
_mY = y;
}
}
private void TouchUp() {
if (!_Path.isEmpty()) {
_Path.lineTo(_mX, _mY);
_Canvas.drawPath(_Path, _paint);
} else {
_Canvas.drawPoint(_mX, _mY, _paint);
}
_Path.reset();
}
#Override
public boolean onTouchEvent(MotionEvent e) {
super.onTouchEvent(e);
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
TouchStart(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
TouchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
TouchUp();
invalidate();
break;
}
return true;
}
public void ClearCanvas() {
_Canvas.drawColor(Color.WHITE);
invalidate();
}
public byte[] getBytes() {
Bitmap b = getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
b.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
public Bitmap getBitmap() {
View v = (View) this.getParent();
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
v.draw(c);
return b;
}
}
I tried Rob Croll's suggestion, which worked good, but it is straight liney, rendering the signature not human looking. If you know what I mean :P
Here is how you append the view on an empty linear layout
LinearLayout mContent = (LinearLayout) findViewById(R.id.linearLayout);
CaptureSignatureView mSig = new CaptureSignatureView(this, null);
mContent.addView(mSig, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
Here is how to get the bytes or Bitmap of the Signature
byte[] signature = mSig.getBytes();
Bitmap signature = mSig.getBitmap();
For anyone looking for a solution you will find one at http://www.mysamplecode.com/2011/11/android-capture-signature-using-canvas.html
It actually writes the signature to file but it's easy enough to change and write to a database instead.
you probably need gesture builder.
i think this link.
http://android-developers.blogspot.com/2009/10/gestures-on-android-16.html
will be usefull to you. if you need to check the signature again.
UPDATE
are you talking about this
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/TouchPaint.html
then this example does not use xml. it has the view as an inner class (MyView)
I know this is an old question, but I needed to implement my own view to capture the signature because I am using MonoForAndroid (C# not Java). So I am adding my View code in here in case somebody needs it.
using System;
using Android.Content;
using Android.Graphics;
using Android.Views;
namespace MyApp.Views
{
public class CaptureSignatureView : View
{
public CaptureSignatureView(Context c, SignatureData signatureData) : base(c)
{
SignatureData = signatureData;
_Path = new Path();
_BitmapPaint = new Paint(PaintFlags.Dither);
_paint = new Paint
{
AntiAlias = true,
Dither = true,
Color = Color.Argb(255, 0, 0, 0)
};
_paint.SetStyle(Paint.Style.Stroke);
_paint.StrokeJoin = Paint.Join.Round;
_paint.StrokeCap = Paint.Cap.Round;
_paint.StrokeWidth = 8;
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
_Bitmap = Bitmap.CreateBitmap(w, (h > 0 ? h : ((View)this.Parent).Height), Bitmap.Config.Argb8888);
_Canvas = new Canvas(_Bitmap);
}
protected override void OnDraw(Canvas canvas)
{
canvas.DrawColor(Color.White);
canvas.DrawBitmap(_Bitmap, 0, 0, _BitmapPaint);
canvas.DrawPath(_Path, _paint);
}
private float _mX, _mY;
private const float TouchTolerance = 4;
private void TouchStart(float x, float y)
{
_Path.Reset();
_Path.MoveTo(x, y);
_mX = x;
_mY = y;
SignatureData.AddPoint(SignatureState.Start, (int)x, (int)y);
}
private void TouchMove(float x, float y)
{
float dx = Math.Abs(x - _mX);
float dy = Math.Abs(y - _mY);
if (dx >= TouchTolerance || dy >= TouchTolerance)
{
_Path.QuadTo(_mX, _mY, (x + _mX) / 2, (y + _mY) / 2);
SignatureData.AddPoint(SignatureState.Move, (int)x, (int)y);
_mX = x;
_mY = y;
}
}
private void TouchUp()
{
if (!_Path.IsEmpty)
{
_Path.LineTo(_mX, _mY);
_Canvas.DrawPath(_Path, _paint);
}
else
{
_Canvas.DrawPoint(_mX, _mY, _paint);
}
SignatureData.AddPoint(SignatureState.End, (int)_mX, (int)_mY);
_Path.Reset();
}
public override bool OnTouchEvent(MotionEvent e)
{
var x = e.GetX();
var y = e.GetY();
switch (e.Action)
{
case MotionEventActions.Down:
TouchStart(x, y);
Invalidate();
break;
case MotionEventActions.Move:
TouchMove(x, y);
Invalidate();
break;
case MotionEventActions.Up:
TouchUp();
Invalidate();
break;
}
return true;
}
public void ClearCanvas()
{
_Canvas.DrawColor(Color.White);
Invalidate();
}
public Bitmap CanvasBitmap()
{
return _Bitmap;
}
public void Clear()
{
ClearCanvas();
SignatureData = new SignatureData();
}
#region Implementation
private Bitmap _Bitmap;
private Canvas _Canvas;
private readonly Path _Path;
private readonly Paint _BitmapPaint;
private readonly Paint _paint;
public SignatureData SignatureData;
#endregion
}
}
Also, I have a blog post here that goes through the details of how to capture the signature. Basically you can do it in two different ways. Either you capture the view with the signature as an image and you save that bitmap (and send it to the server maybe). Or you could just capture a two dimensional array of points and reconstruct the signature in any way you want it.

Categories

Resources