I am able to erase image by converting it in to bitmap and setting it in canvas using following code. But i am not able to set undo and redo functionality. Following code change the source bitmap so how i save path and perform undo and redo functionality?
public class MyCustomView extends View
{
private Bitmap sourceBitmap;
ImageButton undo, redo;
private Canvas sourceCanvas = new Canvas();
private Paint destPaint = new Paint();
private Path destPath = new Path();
Boolean IsEraserSet = false;
public MyCustomView(Context context, Bitmap rawBitmap)
{
super(context);
//converting drawable resource file into bitmap
// Bitmap rawBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.attire);
//converting bitmap into mutable bitmap
this.undo = undo;
this.redo = redo;
sourceBitmap = Bitmap.createBitmap(rawBitmap.getWidth(), rawBitmap.getHeight(), Bitmap.Config.ARGB_8888);
sourceCanvas.setBitmap(sourceBitmap);
sourceCanvas.drawBitmap(rawBitmap, 0, 0, null);
destPaint.setAlpha(0);
destPaint.setAntiAlias(true);
destPaint.setStyle(Paint.Style.STROKE);
destPaint.setStrokeJoin(Paint.Join.ROUND);
destPaint.setStrokeCap(Paint.Cap.ROUND);
//change this value as per your need
destPaint.setStrokeWidth(50);
destPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
sourceCanvas.drawPath(destPath, destPaint);
canvas.drawBitmap(sourceBitmap, 0, 0, null);
// sourceCanvas.drawPath(destPath, destPaint);
// canvas.drawBitmap(sourceBitmap, 0, 0, null);
}
public void setEraser(Boolean value){
IsEraserSet = value;
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
if(!IsEraserSet){
return true;
}
float xPos = event.getX();
float yPos = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
destPath.moveTo(xPos, yPos);
break;
case MotionEvent.ACTION_MOVE:
destPath.lineTo(xPos, yPos);
break;
case MotionEvent.ACTION_UP:
upTouch();
break;
}
invalidate();
return true;
}
}
After erasing image its look like as below.I have added a background image below erased image. So actually i have to erase my top image to show the background image.But how can i add undo and redo functionality? Any help will always appreciated.
You should have a java.util.LinkedList<Path> instead of a Path. Iterate that list in onDraw and draw those paths to the canvas. When user clicks undo you just remove the last Path from the list and when user clicks redo, you add the recently removed Paths, that will be in a backup java.util.Stack<Path>.
Related
I would like to create a brush effect, which when the user drags his/her finger on the screen, the brush only changes the alpha of the image/bitmap below. For example, if the brush opacity is set to 50%, then applying brush should
Set the alpha/opacity of that region, only that region not the whole bitmap.
Leave the underlying colors of the bitmap region unchanged.
That is, the brush should only affect the alpha transparency not the color. I am starting with the following code:
public class DrawingView extends View {
//drawing path
private Path drawPath;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//initial color
private int paintColor = 0xFFFF0000, paintAlpha = 255;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
//constructor
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setupDrawing();
}
//prepare drawing
private void setupDrawing(){
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(50);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
//view assigned size
#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);
}
//draw view
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
}
//respond to touch interaction
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
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();
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
//return current alpha
public int getPaintAlpha(){
return Math.round((float)paintAlpha/255*100);
}
//set alpha
public void setPaintAlpha(int newAlpha){
paintAlpha=Math.round((float)newAlpha/100*255);
drawPaint.setColor(paintColor);
drawPaint.setAlpha(paintAlpha);
}
}
As you have noticed, this brush needs to have a fill color. That is what want to avoid. I need a brush with transparency, but not color.
If there is another method to achieve the object, that is fine.
Setting an absolute alpha value is likely going to involve some pixel-twiddling in the bitmap.
The closest Porter-Duff mode for what you want is DST_OUT, which will act like an eraser.
Try this code and see if it comes close:
drawPaint.setColor(Color.BLACK); // the color doesn't really matter
drawPaint.setAlpha(255 - alpha); // inverse of the alpha result you want
drawPaint.setXferMode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
What this is going to do is make the image pixels lighter and lighter every time you draw over the same spot, which probably isn't what you want. In which case you would have to get more complicated and draw the color+alpha paths onto a separate bitmap, then draw that bitmap over your image bitmap using PorterDuff.Mode.DST_OUT.
It's unfortunate that you can't simply implement a custom ColorFilter that will do exactly what you want. All those graphics operations happen in native space and Google doesn't want us mere mortals messing with that stuff.
I want to draw at specific coordinates of an image which is displayed in an imageview. I use the src attribute of a imageview to load the image into the view. This code is used in an custom imageview to draw on the image:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float scaleh =(float)canvas.getHeight()/(float)orginalheight();
float scalew = ((float)canvas.getWidth()/(float)orginalwidth());
canvas.drawRect(10*scaleh,9*scalew,20*scaleh,30*,scalewpaint);
}
This code draws the rectangle at the wrong location. What is wrong?
You should get the dimensions of the View from the ImageView itself rather than Canvas especially when the ulterior goal is to Draw on Image inside ImageView.
A Canvas works for you as a pretense, or interface, to the actual
surface upon which your graphics will be drawn — it holds all of your
"draw" calls. Via the Canvas, your drawing is actually performed upon
an underlying Bitmap, which is placed into the window.
Firstly, in order to get the Coordinates, implement onTouchListener for ImageView. This will give you X and Y co ordinates to draw the Canvas on.
imageView.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
xDown = event.getX();
yDown = event.getY();
break;
case MotionEvent.ACTION_MOVE:
xUp = event.getX();
uUp = event.getY();
canvas.drawLine(xDown, yDown, xUp, uUp, paint);
imageView.invalidate();
xDown = xUp;
yDown = uUp;
break;
case MotionEvent.ACTION_UP:
xUp = event.getX();
uUp = event.getY();
canvas.drawLine(xDown, yDown, xUp, uUp, paint);
imageView.invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}});
Then create a Canvas to draw on top of ImageView.
//Create a new image bitmap and attach a brand new canvas to it
Bitmap tempBitmap = Bitmap.createBitmap(myBitmap.getWidth(), myBitmap.getHeight(), Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(tempBitmap);
paint = new Paint();
paint.setColor(YOUR_DESIRED_COLOR);
paint.setStrokeWidth(INT_VALUE);
matrix = new Matrix();
canvas.drawBitmap(bmp, matrix, paint);
//Attach the canvas to the ImageView
imageViewsetImageBitmap(tempBitmap);
you can also use canvas.drawRect(left,top,right,bottom,paint); if you want to draw Rectangle simply.
Hope it helps.
In my imageview I want to draw a bitmap everytime imageview is touched without erasing the previous bitmap. Below code draws a new bitmap but also erases the previous one. How can I keep the previous bitmap while adding new one? Thanks
imageview.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
x = event.getX();
y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
Bitmap.Config config = bm.getConfig();
int width = bm.getWidth();
int height = bm.getHeight();
Bitmap bm2 = Bitmap.createBitmap(width, height, config);
Canvas c = new Canvas(bm2);
c.drawBitmap(bm, 0, 0, null);
Bitmap repeat = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
Bitmap repeat2 = Bitmap.createScaledBitmap(repeat, 50, 50, false);
c.drawBitmap(repeat2, x, y, p);
imageview.setImageBitmap(bm2);
break;
return true;
}
});
}
If you will use the same bitmap each time...
Make the Bitmap a member variable and initialize it in your onCreate system.
Create an ArrayList as a member variable.
Add a new Point each time you touch the ImageView.
Loop through the Point List and draw the same Bitmap onto your ImageView canvas.
I think you need to use an array of Bitmaps, then use the onClick to iterate through the array.
You need to have two layers for that. When you change some pixels of a bitmap, it won't be able to handle two layers. So you need to create another layer on image View and set new bitmap over there.
you have to keep track of pixels you parsed through in an arraylist, so that you can you can handle as eraser also by resetting those pixels to transparent.
Do it like this.
Bitmap bm2 = null;
Canvas c = null;
imageview.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
x = event.getX();
y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
Bitmap.Config config = bm.getConfig();
int width = bm.getWidth();
int height = bm.getHeight();
if(bm2==null){
bm2 = Bitmap.createBitmap(width, height, config);
c = new Canvas(bm2);
}
c.drawBitmap(bm, 0, 0, null);
Bitmap repeat = BitmapFactory.decodeResource(getResources(), R.drawable.pic);
Bitmap repeat2 = Bitmap.createScaledBitmap(repeat, 50, 50, false);
c.drawBitmap(repeat2, x, y, p);
imageview.setImageBitmap(bm2);
break;
return true;
}
});
}
I am working on a drawing app, but i do not know why when the picture is loaded from gallery, when further draw on it, the line just drawn will appear on Touch but will disappear when the finger is off the screen, i.e. the line drawn cannot be fixed onto the Bitmap.
Would there be anyone that know how to modify it? Many thanks!!!
coding:
public class DrawView extends View // the main screen that is painted
{
private static final float TOUCH_TOLERANCE = 10;
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, Point> previousPointMap; // current Points
public DrawView(Context context, AttributeSet attrs)
{
super(context, attrs);
paintScreen = new Paint();
// set the default settings
paintLine = new Paint();
paintLine.setColor(Color.BLACK);
paintLine.setStyle(Paint.Style.STROKE);
paintLine.setStrokeWidth(5);
pathMap = new HashMap<Integer, Path>();
previousPointMap = new HashMap<Integer, Point>();
}
// Method onSizeChanged creates BitMap and Canvas after app displays
#Override
public void onSizeChanged(int w, int h, int oldW, int oldH)
{
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmap);
bitmap.eraseColor(Color.WHITE); // erase the BitMap with white
}
public void load_pic(String picturePath) // load a picture from gallery
{
pathMap.clear(); // remove all paths
previousPointMap.clear(); // remove all previous points
bitmap = BitmapFactory.decodeFile(picturePath);
invalidate(); // refresh the screen
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(bitmap, 0, 0, paintScreen);
for (Integer key : pathMap.keySet())
canvas.drawPath(pathMap.get(key), paintLine); // draw line
}
// handle touch event
#Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getActionMasked();
int actionIndex = event.getActionIndex(); // pointer (i.e., finger)
// determine which type of action the given MotionEvent represents, then call the corresponding handling method
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN)
{
touchStarted(event.getX(actionIndex), event.getY(actionIndex), event.getPointerId(actionIndex));
} // end if
else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP)
{
touchEnded(event.getPointerId(actionIndex));
} // end else if
else
{
touchMoved(event);
} // end else
invalidate(); // redraw
return true; // consume the touch event
} // end method onTouchEvent
// called when the user touches the screen
private void touchStarted(float x, float y, int lineID)
{
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
} // end if
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
} // end else
// move to the coordinates of the touch
path.moveTo(x, y);
point.x = (int) x;
point.y = (int) y;
}
...similar for other on Touch event
Do you delete the pathMap when the Touch event ends (i.e., the touchEnded method)?
That would result in the behaviour that you describe, as you are not drawing on the Bitmap, but rather on the Canvas that you are drawing both the line and the bitmap unto (and that Canvas is being redrawn on with every onDraw event); i.e., with each onDraw, you draw over your previous drawing.
I have solved the problem using the following code.
The bitmap needed to be copy for editing purposes.
bitmap = (BitmapFactory.decodeFile(picturePath));
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
bitmapCanvas = new Canvas(bitmap);
invalidate();
So I've been struggling with this for a better part of a day. Suppose I have a custom ImageView that I want to overlay over a background View (both within a RelativeLayout), which when touched, it erases portions of the View's source bitmap like an erase tool in MS Paint, exposing the View below it. I've checked pretty much all of the threads (like this one) and they suggest to use PorterDuff SRC Mode in the Paint object as well as creating a Canvas out out the ARGB_8888 shadow copy of the source bitmap to apply the masking.
Also, I can't set the source of the overlay ahead of time since I have to download it over the network so that the ImageView's scale type takes care of the scaling for me.
Every time I override onDraw, when I apply the erase on the IV's Bitmap, it unveils a black background instead of the view below it, even though I set the background to transparent. So I'm at my last rope as to what to do in order to unveil the background view.
Here's what I have so far:
view constructor:
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setColor(Color.TRANSPARENT);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(STROKE_WIDTH);
paint.setAntiAlias(true);
overrode setImageBitmap to set my canvas from my re-configed source bitmap:
public void setImageBitmap(Bitmap bitmap){
super.setImageBitmap(bitmap);
Drawable bd = getDrawable();
if(bd == null){
return;
}
Bitmap fullSizeBitmap = ((BitmapDrawable) bd).getBitmap();
overlay = fullSizeBitmap.copy(Config.ARGB_8888, true);
c2 = new Canvas(overlay);
}
onDraw method:
protected void onDraw(Canvas canvas) {
/*
* Override paint call by re-drawing the view's Bitmap first, then overlaying our path on top of it
*/
Drawable bd = getDrawable();
if(bd == null){
return;
}
Bitmap fullSizeBitmap = ((BitmapDrawable) bd).getBitmap();
if(fullSizeBitmap != null && c2 != null){
canvas.drawColor(Color.TRANSPARENT);
c2.drawBitmap(fullSizeBitmap, 0, 0, null);
c2.drawPath(path, paint);
canvas.drawBitmap(overlay, 0, 0, null);
}
}
I know this is a really old question but here is what I did to fix a similar problem I had. Maybe it helps someone in the future.
From API level 11 and up you have to specify this piece of code for your ImageView to unveil the image in the back and not a black area.
if (Build.VERSION.SDK_INT >= 11) {
imageview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
This helped me displaying the back image successfully
Can you try this solution it will help you with the erasing images in android on touch .. Or download a demo example
public class MainActivity extends Activity {
Bitmap bp;
Canvas bitmapCanvas;
DrawView drawImg;
LinearLayout ln1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ln1 = (LinearLayout) findViewById(R.id.ln1);
drawImg = new DrawView(this);
ln1.addView(drawImg);
}
public class DrawView extends View implements View.OnTouchListener {
private int x = 0;
private int y = 0;
Bitmap bitmap;
Path circlePath;
Paint circlePaint;
private final Paint paint = new Paint();
private final Paint eraserPaint = new Paint();
public DrawView(Context context){
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
// Set background
this.setBackgroundColor(Color.CYAN);
bp = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
// Set bitmap
bitmap = Bitmap.createBitmap(320, 480, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas();
bitmapCanvas.setBitmap(bitmap);
bitmapCanvas.drawColor(Color.TRANSPARENT);
bitmapCanvas.drawBitmap(bp, 0, 0, null);
circlePath = new Path();
circlePaint = new Paint();
circlePaint.setAntiAlias(true);
circlePaint.setColor(Color.BLUE);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeJoin(Paint.Join.MITER);
circlePaint.setStrokeWidth(4f);
// Set eraser paint properties
eraserPaint.setAlpha(0);
eraserPaint.setStrokeJoin(Paint.Join.ROUND);
eraserPaint.setStrokeCap(Paint.Cap.ROUND);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraserPaint.setAntiAlias(true);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, paint);
bitmapCanvas.drawCircle(x, y, 30, eraserPaint);
canvas.drawPath(circlePath, circlePaint);
}
public boolean onTouch(View view, MotionEvent event) {
x = (int) event.getX();
y = (int) event.getY();
bitmapCanvas.drawCircle(x, y, 30, eraserPaint);
circlePath.reset();
circlePath.addCircle(x, y, 30, Path.Direction.CW);
int ac=event.getAction();
switch(ac){
case MotionEvent.ACTION_UP:
Toast.makeText(MainActivity.this, String.valueOf(x), Toast.LENGTH_SHORT).show();
circlePath.reset();
break;
}
invalidate();
return true;
}
}
}