Undo Redo functionality is not working. - android

I am working on a drawing app for android users. I want to implement undo redo functionality on my app. I am referring questions posted on stackoverflow to solve my problem but I could not be able to find the right solution for my code. Below I am posting my code, please help me to get rid of this issue.Any help would be highly appreciated.
Thank you in advance.
public class DrawingView extends View {
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
this.context = context;
setupDrawing();
}
public void setupDrawing(){
drawPath = new Path();
drawPaint = new Paint();
canvasPaint = new Paint(Paint.DITHER_FLAG);
brushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = brushSize;
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
paths.add(drawPath);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//view given size
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) {
//draw view
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
for(Path p : paths){
canvas.drawPath(p, drawPaint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// undonePaths.clear();
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
// drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawPath = new Path();
paths.add(drawPath);
break;
default:
return false;
}
invalidate();
return true;
}
public void onClickUndo(){
if(paths.size()>0){
undonePaths.add(paths.remove(paths.size() - 1));
Toast.makeText(getContext(), "Undo is working", Toast.LENGTH_LONG).show();
invalidate();
}
else{
Toast.makeText(getContext(), "Undo is not working", Toast.LENGTH_LONG).show();
}
}
public void onClickRedo(){
if(undonePaths.size()>0){
paths.add(undonePaths.remove(undonePaths.size() - 1));
Toast.makeText(getContext(), "Redo is working", Toast.LENGTH_LONG).show();
invalidate();
}
else{
Toast.makeText(getContext(), "Redo is not working", Toast.LENGTH_LONG).show();
}
}
}

Just a few minor changes.
There's an extra Path in your lists that will cause an Undo/Redo step to essentially do nothing when the rest is corrected. So in setupDrawing() remove:
paths.add(drawPath);
In the onDraw() method, remove:
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
This drawBitmap() call was redrawing the member Bitmap that is drawn to on every ACTION_UP, making it appear as though your Path lists weren't being updated.
Then, in the onTouchEvent() method, in MotionEvent.ACTION_DOWN un-comment:
undonePaths.clear();
and change the MotionEvent.ACTION_UP case to:
case MotionEvent.ACTION_UP:
drawCanvas.drawPath(drawPath, drawPaint);
paths.add(drawPath);
drawPath = new Path();
break;
The reset() call was clearing the Path, which you don't want. Instead, here we add the recently completed Path to the list, and then create a new one to continue.
NB: The way this is currently working, the Undo/Redo functionality will not affect the canvasBitmap. As it is unclear what this Bitmap is for, I left it as is. If you want this Bitmap to mirror the View's, you can simply move the drawCanvas.drawPath() calls to onDraw(), in parallel to the canvas.drawPath() calls there.

var undoRedo = new Array();
var unStep = -1;
function historySave() {
unStep++;
while (undoRedo.length > 20) {
undoRedo.shift();
unStep--;
}
if (unStep !== 0 && unStep < undoRedo.length) {
undoRedo.length = unStep;
unStep++;
} else {
undoRedo.length = unStep;
}
undoRedo.push(document.getElementById('drawingCanvas').toDataURL());
}
function Undo() {
if (unStep > -1) {
unStep--;
var canvasPic = new Image();
canvasPic.src = undoRedo[unStep];
drawingContext.clearRect(0,0,drawingCanvas.width,drawingCanvas.height);
canvasPic.onload = function () {
drawingContext.drawImage(canvasPic, 0, 0);
}
}
}
function Redo() {
if (unStep < undoRedo.length - 1) {
unStep++;
var canvasPic = new Image();
canvasPic.src = undoRedo[unStep];
canvasPic.onload = function () {
drawingContext.drawImage(canvasPic, 0, 0);
}
}
}

Related

speed issues android wear

My situation is rather simple. I have a mobile and wear app. My mobile app is a "drawing" app. It connects to a piece of hardware that has a magnetic pencil. Drawing on the hardware sends three functions to my app - touchStart, touchMove, touchStop. I can draw the path on my mobile app at a reasonable rate. But, if I send these 3 touch detections to my watch via SendMessage, the image is drawn slowly. I'm trying to speed it up so the drawing is in a real time, like the mobile device.
Here's the code to the Paint class of my watch app:
class SimpleDrawingView extends View {
// setup initial color
private final int paintColor = Color.WHITE;
// defines paint and canvas
private Paint drawPaint;
// Store circles to draw each time the user touches down
public int xwidth, xheight;
private Path bPath = new Path();
private Path path = new Path();
public SimpleDrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setFocusableInTouchMode(true);
setupPaint();
}
public Path redraw() {
Matrix m = new Matrix();
RectF innerBounds = new RectF();
bPath = new Path(path);
bPath.computeBounds(innerBounds, true);
int width = getWidth();
int height = getHeight();
RectF intoBounds = new RectF(0, 0, width, height);
m.setRectToRect(innerBounds, intoBounds, Matrix.ScaleToFit.CENTER);
bPath.transform(m);
return bPath;
}
// Draw each circle onto the view
#Override
protected void onDraw(Canvas canvas) {
Log.d("MEx", " drawing ");
redraw();
canvas.drawPath(path, drawPaint);
}
public void actionDown(Point a)
{
path.moveTo(a.x, a.y);
invalidate();
}
public void clear()
{
path.reset();
invalidate();
}
public void actionEnd(Point a)
{
// path.lineTo(a.x, a.y);
}
public void actionMove(Point a)
{
path.lineTo(a.x, a.y);
invalidate();
}
// Append new circle each time user presses on screen
private void setupPaint() {
// Setup paint with color and stroke styles
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(23);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
}
}
Here's my message receiver:
public class Receiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
for(String s : intent.getExtras().keySet()) {
String first = intent.getExtras().get(s).toString();
String label, x, y;
if (first.contains("charge+")) {
textLabel.setText(first.split("7")[1]);
} else if (first.contains("battery+")) {
String color = first.substring(8);
Log.d("TESTTWO", color + " <- ");
switch(color)
{
case "RED":
dotView.setImageResource(R.drawable.red);
textLabel.setText("");
break;
case "GREEN":
dotView.setImageResource(R.drawable.green);
textLabel.setTextColor(Color.GREEN);
break;
case "YELLOW":
textLabel.setTextColor(Color.YELLOW);
dotView.setImageResource(R.drawable.yellow);
break;
}
} else {
label = first.substring(0, 1);
String[] toSplit;
toSplit = first.substring(2).split(",");
Point point = new Point(Integer.parseInt(toSplit[0]), Integer.parseInt(toSplit[1]));
switch (label) {
case "S":
imageView.actionDown(point);
break;
case "M":
imageView.actionMove(point);
break;
case "E":
imageView.actionEnd(point);
break;
}
}
}
}
}
Points are sent to my watch in the format of either
S-x,y
M-x,y
E-x,y
S = start, M = move, E = end.
If someone can help me optimize this functionality it would be amazing!
I tried send the data as an asset as well, that's even slower.

Android: Canvas drawBitmap doesn't work

I am trying to create a canvas that you can draw over a bitmap with. I have loaded the bitmap and there's no problem with it. But when I use drawBitmap I only get a blank screen, while the painting and drawPath still works. I have tried passing the bitmap directly into the constructor as well. Pls help
public class DrawView extends View implements OnTouchListener {
private static final String TAG = "DrawView" ;
private Canvas mCanvas;
private Path mPath;
private Paint mPaint;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
final int defaultBrushSize = 10;
private Bitmap im;
public DrawView(Context context, Intent intent,String fileroot)
{
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setColor(Color.parseColor("#000000"));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.BEVEL);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setAlpha(150);
mPaint.setStrokeWidth(defaultBrushSize);
FileInputStream in = null;
try {
in = new FileInputStream(fileroot+"/"+intent.getStringExtra("image")+".jpg");
BufferedInputStream buf = new BufferedInputStream(in);
byte[] bMapArray= new byte[buf.available()];
Log.d(TAG, "onCreate: bMap array: " + bMapArray.toString());
buf.read(bMapArray);
im = BitmapFactory.decodeByteArray(bMapArray, 0, bMapArray.length).copy(Bitmap.Config.ARGB_8888, true);
Log.d(TAG, "onCreate: bmap " + im);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
mPath = new Path();
mCanvas = new Canvas();
mCanvas.drawBitmap(im,null,new Rect(0,0,im.getWidth(),im.getHeight()),null);
}
#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);
}
canvas.drawPath(mPath, mPaint);
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
undonePaths.clear();
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
paths.add(mPath);
mPath = new Path();
}
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
}
public void setSize(int s){
mPaint.setStrokeWidth(s);
invalidate();
}
#Override
public boolean onTouch(View arg0, 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;
}
You aren't backing the Canvas anywhere. Just calling new Canvas, without backing it somewhere, sends the draw commands into a black hole. Generally you back them to an in memory Bitmap object you have to create first. So here you'd want to use canvas = new Canvas(bitmap);
Also note that to get anything to appear on screen, you must draw it to the Canvas passed into onDraw during the onDraw function. So just drawing to a random Canvas, backed or not, will not draw it to the screen.

Change the color of already drawn polygonal path

I have drawn a polygonal path based on user finger movement using the code given below.
paint = new Paint();
strokePaint = new Paint();
//paint.setColor(Color.RED);
paint.setARGB(125, 255, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
wallpath = new Path();
and on onDraw i am using this code to draw the figure
wallpath.lineTo(endPoint.x, endPoint.y);
canvas.drawPath(wallpath, paint);
The above code is working fine. But on double tap i want to change the fill color. For that I am using this code
paint.setARGB(125, 225, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
invalidate();
But it doesnt seems to be working. How can I do this properly?
Complete code for reference
public class HotspotView extends View
{
private Paint paint,strokePaint;
private PointF startPoint, endPoint;
private boolean isDrawing,isFinished,isAnimating,isRecording,isRedrawing;
private Path wallpath;
private ArrayList<PointF> points;
private RectF rectF;
private CurlView curlView;
public int imageWidth;
private String fileName;
private AudioRecorder audioRecorder;
private GestureDetector gestureDetector;
public static int LONG_PRESS_TIME = 500; // Time in miliseconds
private AudioPlayer player;
final Handler _handler = new Handler();
Runnable _longPressed = new Runnable() {
public void run() {
Log.i("info","LongPress");
isRecording = true;
isDrawing = false;
isRedrawing = true;
///////////////////******************///////////////////////
//paint = new Paint();
//strokePaint = new Paint();
//paint.setColor(Color.RED);
paint.setARGB(125, 225, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
invalidate();
//////////////////*****************////////////////////////
audioRecorder = new AudioRecorder(fileName);
setFileName();
audioRecorder.startRecording(fileName);
}
};
public HotspotView(Context context)
{
super(context);
init();
gestureDetector = new GestureDetector(context, new GestureListener());
}
public HotspotView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
gestureDetector = new GestureDetector(context, new GestureListener());
}
public HotspotView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
gestureDetector = new GestureDetector(context, new GestureListener());
}
private void init()
{
isRecording = false;
isRedrawing = false;
paint = new Paint();
strokePaint = new Paint();
//paint.setColor(Color.RED);
paint.setARGB(125, 255, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
wallpath = new Path();
points = new ArrayList<PointF>();
rectF = new RectF();
rectF.set(-1.7883435f, 1.0f, 1.7883435f, -1.0f);
}
#Override
protected void onDraw(Canvas canvas)
{
if(isAnimating)
{
PointF point = this.translate(points.get(0));
if(wallpath == null)
{
wallpath = new Path();
}
wallpath.moveTo(point.x, point.y);
isDrawing = false;
isFinished = false;
for(int i=1;i<points.size();i++)
{
if(isRedrawing)
{
point = points.get(i);
}
else
{
point = this.translate(points.get(i));
}
wallpath.lineTo(point.x, point.y);
//Log.d("Points", "X = "+point.x);
//Log.d("Points", "Y = "+point.y);
canvas.drawPath(wallpath, paint);
}
if(isRedrawing)
{
point = points.get(0);
}
else
{
point = this.translate(points.get(0));
}
wallpath.lineTo(point.x, point.y);
canvas.drawPath(wallpath, paint);
isFinished = true;
}
else if(isDrawing)
{
//canvas.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, paint);
wallpath.lineTo(endPoint.x, endPoint.y);
canvas.drawPath(wallpath, paint);
}
if(isFinished)
{
//wallpath.lineTo(endPoint.x, endPoint.y);
//canvas.drawPath(wallpath, strokePaint);
wallpath.close();
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
boolean result = gestureDetector.onTouchEvent(event);//return the double tap events
if(!isAnimating)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
isDrawing = true;
//wallpath.reset();
_handler.postDelayed(_longPressed, LONG_PRESS_TIME);
startPoint = new PointF(event.getX(), event.getY());
endPoint = new PointF();
endPoint.x = event.getX();
endPoint.y = event.getY();
wallpath.moveTo(endPoint.x,endPoint.y);
points.add(startPoint);
//invalidate();
break;
case MotionEvent.ACTION_MOVE:
PointF point = new PointF(event.getX(),event.getY());
endPoint.x = event.getX();
endPoint.y = event.getY();
double distance = Math.sqrt(Math.pow((endPoint.x - startPoint.x), 2)+ Math.pow(endPoint.y - startPoint.y,2));
if(distance >2)
{
_handler.removeCallbacks(_longPressed);
if(!isRecording)
{
if(isDrawing)
{
Log.d("Point", "X = "+(event.getX() - this.getLeft()));
Log.d("Point", "Y = "+(event.getY() - this.getTop()));
points.add(point);
invalidate();
}
}
}
break;
case MotionEvent.ACTION_UP:
_handler.removeCallbacks(_longPressed);
if(isRecording)
{
audioRecorder.stopRecording();
isRecording = false;
}
if(isDrawing)
{
endPoint.x = startPoint.x;//event.getX();
endPoint.y = startPoint.y;//event.getY();
strokePaint.setARGB(255, 255, 0, 0);
strokePaint.setStyle(Style.STROKE);
strokePaint.setPathEffect(new DashPathEffect(new float[] {5,10}, 0));
strokePaint.setStrokeWidth(5);
strokePaint.setAntiAlias(true);
isFinished = true;
invalidate();
//isDrawing = false;
}
break;
default:
break;
}
}
return result;
}
It would be helpful if you could post some more code from your onDraw method, because it's hard to say what is called when.
My guess is that invalidate works fine, but each time when onDraw is called, you reset your paint settings (paint = new Paint()), so paint with different color is simply not used.
EDIT:
I can't tell you in which exact line you have a bug, however in my opinion it's not related to path or paint settings. There are too many state flags (isDrawing, isFinished, isAnimating, isRecording, isRedrawing) in your code and you can't control it anymore.
OnDraw method should simply draw points:
#Override
protected void onDraw(Canvas canvas) {
if (points.size() > 0) {
PointF point = points.get(0);
wallpath.rewind();
wallpath.moveTo(point.x, point.y);
for (int i = 1; i < points.size(); i++) {
point = points.get(i);
wallpath.lineTo(point.x, point.y);
}
canvas.drawPath(wallpath, paint);
}
}
Simplify your code. Just for a test: replace your onDraw method with my proposition and you'll see that path will change color on long press.
To fill a Path you need to close it first. Call Path.close() to close the current contour.
If you want to change color of the lines, you need to use paint.setStyle(Paint.Style.STROKE) and use paint.setColor() with it.
... and of course you need to invalidate your view, that onDraw() - where you draw your path - is called again.

Trying to create an Eraser Paint for canvas

I am creating a drawing app that utilizes the DrawingSurfaceView class below. In that class i have a Paint Called eraserPaint that the user can toggle on and off.. When on that paint is suppose to eraser what ever is in its path. but instead its just drawing a black line..
When i save out the canvas as a transparent png the eraser is correct but on the screen it shows black..
Screenshot from phone of EraserPaint used to write "Erik" on blob
Saved out PNG from canvas
eraserPaint looks like this:
eraserPaint = new Paint();
eraserPaint.setAlpha(0);
eraserPaint.setColor(Color.TRANSPARENT);
eraserPaint.setStrokeWidth(60);
eraserPaint.setStyle(Style.STROKE);
eraserPaint.setMaskFilter(null);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraserPaint.setAntiAlias(true);
the WHOLE class
public KNDrawingSurfaceView(Context c, float width, float height, KNSketchBookActivity parent) {
super(c);
myWidth = width;
myHeight = height;
mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
_parent = parent;
mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);
tile = new Paint();
tileImage = BitmapFactory.decodeResource(getResources(), R.drawable.checkerpattern);
shader = new BitmapShader(tileImage, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
tile.setShader(shader);
mPath = new Path();
eraserPaint = new Paint();
eraserPaint.setAlpha(0x00);
eraserPaint.setColor(Color.TRANSPARENT);
eraserPaint.setStrokeWidth(60);
eraserPaint.setStyle(Style.STROKE);
//eraserPaint.setMaskFilter(null);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraserPaint.setAntiAlias(true);
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mCanvas.drawRect(0, 0, myWidth, myHeight, tile);
mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
if (!_parent.isDrawerOpen()&&mPaint!=null) {
Log.v("onDraw:", "curent paths size:" + paths.size());
//mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//canvas.drawPath(mPath, mPaint);
for (int i=0;i< paths.size();i++) {
tempPaint = paints.get(i);
eraserPaint.setStrokeWidth(tempPaint.getStrokeWidth());
if(fills.get(i)){
tempPaint.setStyle(Style.FILL_AND_STROKE);
eraserPaint.setStyle(Style.FILL_AND_STROKE);
}else{
tempPaint.setStyle(Style.STROKE);
eraserPaint.setStyle(Style.STROKE);
}
if(erasers.get(i)){
//tempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPath(paths.get(i), eraserPaint);
}else{
//tempPaint.setXfermode(null);
canvas.drawPath(paths.get(i), tempPaint);
}
//canvas.drawPath(paths.get(i), tempPaint);
}
if(_parent.toggleFill.isChecked()){
mPaint.setStyle(Style.FILL_AND_STROKE);
eraserPaint.setStyle(Style.FILL_AND_STROKE);
}else{
mPaint.setStyle(Style.STROKE);
eraserPaint.setStyle(Style.STROKE);
}
if(_parent.toggleErase.isChecked()){
//mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPath(mPath, eraserPaint);
}else{
//mPaint.setXfermode(null);
canvas.drawPath(mPath, mPaint);
}
//canvas.drawPath(mPath, mPaint);
}
}
public void onClickUndo() {
if (paths.size() > 0) {
undonePaths.add(paths.remove(paths.size() - 1));
undonePaints.add(paints.remove(paints.size() - 1));
undoneFills.add(fills.remove(fills.size() - 1));
undoneErasers.add(erasers.remove(erasers.size() - 1));
clearCanvasCache();
invalidate();
} else {
}
_parent.checkButtonStates();
}
public void onClickRedo() {
if (undonePaths.size() > 0) {
paths.add(undonePaths.remove(undonePaths.size() - 1));
paints.add(undonePaints.remove(undonePaints.size() - 1));
fills.add(undoneFills.remove(undoneFills.size() - 1));
erasers.add(undoneErasers.remove(undoneErasers.size() - 1));
clearCanvasCache();
invalidate();
} else {
}
_parent.checkButtonStates();
}
public void onClickClear() {
paths.clear();
paints.clear();
fills.clear();
erasers.clear();
undoneFills.clear();
undonePaths.clear();
undonePaints.clear();
undoneErasers.clear();
clearCanvasCache();
invalidate();
_parent.checkButtonStates();
}
public void saveDrawing() {
FileOutputStream outStream = null;
String fileName = "tempTag";
try {
outStream = new FileOutputStream("/sdcard/" + fileName + ".png");
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
outStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
undonePaths.clear();
undonePaints.clear();
undoneFills.clear();
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
if(_parent.toggleErase.isChecked()){
mCanvas.drawPath(mPath, eraserPaint);
erasers.add(true);
paints.add(eraserPaint);
}else{
mCanvas.drawPath(mPath, mPaint);
erasers.add(false);
paints.add(mPaint);
}
// kill this so we don't double draw
paths.add(mPath);
if(_parent.toggleFill.isChecked()){
fills.add(true);
}else{
fills.add(false);
}
if(_parent.toggleErase.isChecked()){
erasers.add(true);
}else{
erasers.add(false);
}
_parent.checkButtonStates();
mPath = new Path();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(mPaint==null &&!_parent._showingAlert){
_parent.showNoPaintAlert();
}
if (!_parent.isDrawerOpen()&&mPaint!=null) {
float x = event.getX();
float y = event.getY();
if (x > myWidth) {
x = myWidth;
}
if (y > myHeight) {
y = myHeight;
}
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;
} else {
return true;
}
}
public void clearCanvasCache() {
mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
}
I should add that i am adding this Custom View to a relative layout that has that checkered pattern as the background image..
PLEASE PLEASE PLEASE help.. i need that preview image to NOT show black after an eraser paint was used.. i need it to show the checkered pattern behind.. I know the eraser is working as those black eraser marks save out as transparent.
NEW NOTE
I was playing around and discovered something else thats curious. Experimenting, i tried switching from drawing to the canvas as passed to the onDraw method and directly to the canvas i set up in the contructor called mCanvas and noticed it did not draw as far as i could see.. so I added a log to the onDraw like so:
protected void onDraw(Canvas canvas) {
Log.v("DRAWING SURFACE", "canvas:"+canvas+" mCanvas:"+mCanvas);
which spits out
06-21 11:10:43.994: V/DRAWING SURFACE(4532): canvas:android.view.Surface$CompatibleCanvas#42a8c030 mCanvas:android.graphics.Canvas#431df180
I had this same problem with my app. I even tried the "finger paint" example code and still had the same problem. I was never able to have the eraser work as a path, but I was able to find a workaround. Rather than drawing a path when I erase, I draw a circle (It could be any shape) when the user puts his finger down or there is a "move" event:
case MotionEvent.ACTION_DOWN:
mPaint.setStrokeWidth(25);
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
mCanvas.drawCircle(x, y, 10, mPaint);
isErase = true;
invalidate();
}
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if(isErase)
{
mCanvas.drawCircle(x, y, 20, mPaint);
}
else{
touch_move(x, y);
}invalidate();
break;
It will take some time to incorporate this into your code, but I guarantee you it will take less time than the amount of time you have already spent trying to fix this problem. I can send you more of my PaintView if you think it would be helpful.
Same problem encountered, tried all other solutions found, no luck.
But I got a workaround. You can add a bitmap to store the strokes.
public void init(int width, int height) {
Log.i(TAG,"init with "+width+"x"+height);
foreground = Bitmap.createBitmap(width, height, Config.ARGB_8888);
cacheCanvas = new Canvas();
cacheCanvas.setBitmap(foreground);
}
Whenever there is any touch, record the stroke with the current paint and current stroke width. (the paint could be any color, including the eraser paint)
And then override the onDraw(Canvas) method. As the bitmap supports eraser while the canvas doesn't, we can first draw the resultant image on the bitmap first, and then draw the bitmap to the canvas.
#Override
protected void onDraw(Canvas canvas) {
// Log.i(TAG,"onDraw called");
synchronized (strokes) {
if (strokes.size() > 0) {
for (Stroke s : strokes) {
cacheCanvas.drawPath(s.path, s.color);
}
canvas.drawBitmap(foreground, 0, 0, null);
strokes.clear();
}
}
}
FYI, if the foreground bitmap is very large, the performance will be low. To solve this, we should invalidate only the area which the latest finger touches altered.
This is just a guess: it could be related to hardware acceleration. Try to disable hardware acceleration. If that helps, you can create a bitmap the size of the view, draw all your stuff to that bitmap, and then draw the bitmap into the view's canvas.
For the canvas to erase and invalidate, you have to set the setXfermode of your canvas to null. check the last line of the code.
if(view.getId()==R.id.erase_btn) {
erase_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onDraw.setErase(true);
}
}
}
public void setErase(boolean isErase){
erase=isErase;
if(erase) drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
else drawPaint.setXfermode(null);
}
You can use a boolean variable while choosing eraser (ie. isEraser = true) and in onDraw(), you can draw path if it's not eraser.
#Override
protected void onDraw(Canvas canvas) {
if(!isEraser ){
canvas.drawPath(mPath, mPaint);
}
}

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