Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I was working on the drawing application, so far I am able to draw line on the image, the problem is I find it can not integrate the undo operation in the custom image view.
I apply the logic of " store the draw path one by one, and draw it on the onDraw phrase", but there seems some missing/ flaws in the code.
Welcome to ask for any question if you would like to know more detail about the code. The screenshot is what my app look like (drawing on the image)
Thanks for helping.
In the Main Activity:
// set image
bitmap = downScale(view.getTag().toString(),1280,1024);
altered_bitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), bitmap.getConfig());
draw_view.setNewImage(altered_bitmap,bitmap);
undo.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View arg0) {
if (pencil.getVisibility() == View.VISIBLE || pen.getVisibility() == View.VISIBLE) {
draw_view.onClickUndo();
}
}
});
And in the custom image view:
private ArrayList<Path> paths = new ArrayList<Path>();
private Path mPath;
public ScaleImageView(Context context) {
super(context);
sharedConstructing(context);
}
public void sharedConstructing(Context context) {
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix = new Matrix();
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(width);
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAlpha(alpha);
drawListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getDrawable() != null) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downx = getPointerCoords(event)[0];// event.getX();
downy = getPointerCoords(event)[1];// event.getY();
break;
case MotionEvent.ACTION_MOVE:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
mPath = new Path();
paths.add(mPath);
invalidate();
downx = upx;
downy = upy;
break;
case MotionEvent.ACTION_UP:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
mPath = new Path();
paths.add(mPath);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
}
return true;
}
};
setOnTouchListener(drawListener);
}
//draw view start
public void setNewImage(Bitmap alteredBitmap, Bitmap bmp) {
canvas = new Canvas(alteredBitmap);
matrix_draw = new Matrix();
canvas.drawBitmap(bmp, matrix_draw, paint);
setImageBitmap(alteredBitmap);
mPath = new Path();
paths.add(mPath);
}
public void setBrushColor(int color) {
this.color = color;
paint.setColor(color);
paint.setAlpha(alpha);
}
public void setAlpha(int alpha) {
this.alpha = alpha;
paint.setAlpha(alpha);
}
public void setWidth(float width) {
this.width = width;
paint.setStrokeWidth(width);
}
final float[] getPointerCoords(MotionEvent e) {
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
getImageMatrix().invert(matrix);
matrix.postTranslate(getScrollX(), getScrollY());
matrix.mapPoints(coords);
return coords;
}
public void setIsScale() {
isScale = !isScale;
setOnTouchListener(isScale ? zoomListener : drawListener);
}
#Override
protected void onDraw(Canvas canvas) {
for (Path p : paths){
canvas.drawPath(p, paint_line);
}
}
public void onClickUndo () {
if (paths.size()>0){
paths.remove(paths.size()-1);
invalidate();
}
}
//draw view end
Update: Test result
After tested a while, found that the app runs but selected image is not draw in the custom image view like this:
if you have some spare time , I have upload the project (<1 mb),
it is a small drawing tool , first copy a folder with some images to the folder "HistoryTool" in your device
The path , for example, like:
sd card root/ HistoryTool/ folder1 / a.jpg
, then you can draw on it, thats all, but right now the undo operation is not functioning and need to fix.
https://drive.google.com/file/d/0B9mELZtUJp0LLVh0b1Q0a3VTcG8/view?usp=sharing
Thanks a lot
Actually I think I have found the root of your problem: you add a new path each time you catch a MotionEvent.ACTION_MOVE, but touch sensor is relly noisy and you get lots of those events. You migh consider editing the existing path which you add whenever you detect MotionEvent.ACTION_DOWN. I would consider doing something like this
Path currentPath = null;
drawListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getDrawable() != null) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downx = getPointerCoords(event)[0];// event.getX();
downy = getPointerCoords(event)[1];// event.getY();
currentPath = new Path();
currentPath.moveTo(downx, downy);
paths.add(currentPath);
break;
case MotionEvent.ACTION_MOVE:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
currentPath.lineTo(upx, upy);
invalidate();
break;
case MotionEvent.ACTION_UP:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
currentPath.lineTo(upx, upy);
invalidate();
currentPath = null;
break;
case MotionEvent.ACTION_CANCEL:
currentPath = null;
break;
default:
break;
}
}
return true;
}
};
Just be sure to declare currentPath as a private field of your custom View class. Sorry I can't test the code right now, so if you have some problems with this code, please tell me and in the evening I will give you a tested code.
EDIT: Sorry I have added extra moveTo in ACTION_MOVE processing, which will prevent the correct work code
Related
I made a custom ImageView which has a canvas and gets two bitmaps so that I can draw on top of an existing image.
While the orientation changes the images is visible but the lines I've drawn disappear.
public class DrawableImageView extends ImageView implements View.OnTouchListener, DrawableImageViewControlListener {
float downx = 0;
float downy = 0;
float upx = 0;
float upy = 0;
Canvas canvas;
Paint paint;
Matrix matrix;
boolean isChanged;
public DrawableImageView(Context context) {
super(context);
setOnTouchListener(this);
}
public DrawableImageView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
setOnTouchListener(this);
}
public DrawableImageView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
}
public void setNewImage(Bitmap alteredBitmap, Bitmap bmp)
{
canvas = new Canvas(alteredBitmap);
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(5);
paint.setStrokeCap(Paint.Cap.ROUND);
matrix = new Matrix();
canvas.drawBitmap(bmp, matrix, paint);
isChanged = false;
setImageBitmap(alteredBitmap);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
isChanged = true;
switch (action)
{
case MotionEvent.ACTION_DOWN:
downx = getPointerCoords(event)[0];//event.getX();
downy = getPointerCoords(event)[1];//event.getY();
break;
case MotionEvent.ACTION_MOVE:
upx = getPointerCoords(event)[0];//event.getX();
upy = getPointerCoords(event)[1];//event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
invalidate();
downx = upx;
downy = upy;
break;
case MotionEvent.ACTION_UP:
upx = getPointerCoords(event)[0];//event.getX();
upy = getPointerCoords(event)[1];//event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
final float[] getPointerCoords(MotionEvent e)
{
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
getImageMatrix().invert(matrix);
matrix.postTranslate(getScrollX(), getScrollY());
matrix.mapPoints(coords);
return coords;
}
#Override
public void colorChanged(int color) {
paint.setColor(color);
}
#Override
public void brushChanged(int size) {
paint.setStrokeWidth(size);
}
public boolean isChanged() {
return isChanged;
}
And here is the code where I initialize the ImageView in my OnCreate:
imageView = (DrawableImageView) findViewById(R.id.editImageView);
IStorage storage = BootLoader.resolve(this).getStorage();
imageFile = storage.getImageFile(fileName);
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath());
if (alteredBitmap == null) {
alteredBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
}
imageView.setNewImage(alteredBitmap, bitmap);
Note: I am using two layout files for the two different orientations, and would like to keep it this way. So not manually handling configuration changes.
Android tears down the whole Activity when the screen orientation is changed and restarts it again, so you need to save the altered bitmap when this happens and restore it afterwards.
Different storage options are described here
When to save is described here,
I have a problem with canvas I have two options either to draw with a paint or to add an image but when I choose to add an image after drawing the drawing is gone but when I choose drawing again and start to draw the previous drawing show up and remove the image.
public class DrawCanvas extends View {
Bitmap bitmap;
private Paint paint = new Paint();
private Path path;
ArrayList<Path> paths = new ArrayList<>();
ArrayList<Path> undo = new ArrayList<>();
final Paint mBackgroundPaint;
ArrayList<Emotion> emotions;
private boolean mDrawingEnabled = true;
float currentX = 0, currentY = 0;
public DrawCanvas(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.launcher);
paint.setAntiAlias(false);
paint.setColor(Color.BLACK);
paint.setStrokeJoin(Paint.Join.MITER);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(30f);
emotions = new ArrayList<>();
path = new Path();
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(Color.WHITE);
}
#Override
protected void onDraw(Canvas canvas){
if (mDrawingEnabled ) {
canvas.drawPath(path, paint);
}
else{
canvas.drawBitmap(bitmap, currentX, currentY, null);
}
}
#Override
public boolean onTouchEvent(MotionEvent motionEvent){
int xPos =(int) motionEvent.getX(), yPos =(int) motionEvent.getY();
final int action = motionEvent.getAction();
if (mDrawingEnabled){
switch (action) {
case MotionEvent.ACTION_DOWN:
path.moveTo(xPos, yPos);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(xPos, yPos);
break;
case MotionEvent.ACTION_UP:
paths.add(path);
break;
default:
return false;
}
}
else {
final float me_x = motionEvent.getX();
final float me_y = motionEvent.getY();
switch ( action ) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
currentX = me_x;
currentY = me_y;
break;
}
}
invalidate();
return true;
}
public void undo(){
if (paths.size()>0){
undo.add(paths.remove(paths.size()-1));
}
}
public void redo(){
if (undo.size()>0){
paths.add(undo.remove(undo.size()-1));
}
}
final void enableDrawing() { mDrawingEnabled = true; }
final void disableDrawing() { mDrawingEnabled = false; }
public void changeBrushColor(int color){
paint.setColor(color);
}
public void changeBrushSize(int size){
paint.setStrokeWidth(size);
}
}
here is the drawing
and when I add image clears the drawing
I want to keep the drawn path alongside the image.
I'm trying to draw using onTouch events on a Canvas over an Imageview that is pinchable/zoomable. It works perfectly if the image is fullscreen (and proportioned to the size of the screen), but once you zoom in on the image, the drawing point is not the same place as where you touch the screen.
I am creating a canvas and an imageview like so:
canvas = new Canvas(bitmap);
image.setImageBitmap(bitmap);
and then performing a lot of pinch/zoom transformations by using setMatrix on image. By looking at bitmap.getHeight() and bitmap.getWidth(), it's evident that throughout all the transformations, neither Bitmap nor Image actually change in size. Image, for instance, stays at a constant 2560x1454 while Bitmap stays at 3264x1836 (using image.getHeight()/width() and bitmap.getHeight()/width() for those values).
For the drawing part, I'm converting MotionEvent to a series of Point objects (literally just int x, int y) and then translating that value through a formula I found somewhere on the internet. I really have no idea why it works, which is probably my first problem.
List<Point> points = new ArrayList<>();
private boolean handleDraw(View v, MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_UP) {
Point p = new Point();
p.x = event.getX();
p.y = event.getY();
convertPoint(p, v);
points.add(p);
//draw the converted points
drawOnBitmap();
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
points.clear();
return true;
}
return true;
}
and then they are converted into bitmap values here
//v here is the view passed by the ontouch listener.
private void convertPoint(Point point, View v) {
double divisor = ((double) bitmap.getWidth() / (double) v.getWidth());
point.x = (int) ((double) point.x * divisor);
point.y = (int) ((double) point.y * divisor);
}
and drawn onto the canvas here:
private void drawOnBitmap() {
Path path = new Path();
boolean first = true;
//uses quaddraw
for (int i = 0; i < points.size(); i += 2) {
Point point = points.get(i);
if (first) {
first = false;
path.moveTo(point.x, point.y);
} else if (i < points.size() - 1) {
Point next = points.get(i + 1);
path.quadTo(point.x, point.y, next.x, next.y);
} else {
path.lineTo(point.x, point.y);
}
}
canvas.drawPath(path, redPaint);
image.invalidate();
}
with Paint being
redPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
redPaint.setStyle(Paint.Style.STROKE);
redPaint.setStrokeWidth(STROKE_WIDTH);
redPaint.setColor(Color.RED);
In general, what's happening is that the touch event is being scaled from the size of the screen to the size of the bitmap (see the above image for an example). However, the answer isn't to just take the original getRaw() values; those are always incorrect.
I guess the question is, how do I get the canvas to respond to the ABSOLUTE position of the touch event instead of the proportional one? Failing that, how can I transform the base canvas with the same values as the imageview and get them both to scale proportionally?
You need to recalculate your coords using Matrix with code like this:
public class DrawableImageView extends ImageView implements OnTouchListener
{
float downx = 0;
float downy = 0;
float upx = 0;
float upy = 0;
Canvas canvas;
Paint paint;
Matrix matrix;
public DrawableImageView(Context context)
{
super(context);
setOnTouchListener(this);
}
public DrawableImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
setOnTouchListener(this);
}
public DrawableImageView(Context context, AttributeSet attrs,
int defStyleAttr)
{
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
}
public void setNewImage(Bitmap alteredBitmap, Bitmap bmp)
{
canvas = new Canvas(alteredBitmap );
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(5);
matrix = new Matrix();
canvas.drawBitmap(bmp, matrix, paint);
setImageBitmap(alteredBitmap);
}
#Override
public boolean onTouch(View v, MotionEvent event)
{
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
downx = getPointerCoords(event)[0];//event.getX();
downy = getPointerCoords(event)[1];//event.getY();
break;
case MotionEvent.ACTION_MOVE:
upx = getPointerCoords(event)[0];//event.getX();
upy = getPointerCoords(event)[1];//event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
invalidate();
downx = upx;
downy = upy;
break;
case MotionEvent.ACTION_UP:
upx = getPointerCoords(event)[0];//event.getX();
upy = getPointerCoords(event)[1];//event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
final float[] getPointerCoords(MotionEvent e)
{
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
getImageMatrix().invert(matrix);
matrix.postTranslate(getScrollX(), getScrollY());
matrix.mapPoints(coords);
return coords;
}
}
The full sample you can get here.
The problem is, I attempt to change the opacity to 100 which should be transparent, but when I try to draw the line it has some circle on the line. (ref to the screenshot) Highly appreciate if provide some sample code. Thanks a lot for helping.
Code from MainActivity
// set image
bitmap = downScale(view.getTag().toString(),1280,1024);
altered_bitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), bitmap.getConfig());
draw_view.setNewImage(altered_bitmap,bitmap);
pen.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View arg0) {
draw_view.setAlpha(100);
}
}
});
And Code from the custom imageView
public ScaleImageView(Context context) {
super(context);
sharedConstructing(context);
}
public void sharedConstructing(Context context) {
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix = new Matrix();
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(width);
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAlpha(alpha);
drawListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getDrawable() != null) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downx = getPointerCoords(event)[0];// event.getX();
downy = getPointerCoords(event)[1];// event.getY();
break;
case MotionEvent.ACTION_MOVE:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
mPath = new Path();
paths.add(mPath);
invalidate();
downx = upx;
downy = upy;
break;
case MotionEvent.ACTION_UP:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
canvas.drawLine(downx, downy, upx, upy, paint);
mPath = new Path();
paths.add(mPath);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
}
return true;
}
};
setOnTouchListener(drawListener);
}
//draw view start
public void setNewImage(Bitmap alteredBitmap, Bitmap bmp) {
canvas = new Canvas(alteredBitmap);
matrix_draw = new Matrix();
canvas.drawBitmap(bmp, matrix_draw, paint);
setImageBitmap(alteredBitmap);
mPath = new Path();
paths.add(mPath);
}
public void setBrushColor(int color) {
this.color = color;
paint.setColor(color);
paint.setAlpha(alpha);
}
public void setAlpha(int alpha) {
this.alpha = alpha;
paint.setAlpha(alpha);
}
public void setWidth(float width) {
this.width = width;
paint.setStrokeWidth(width);
}
final float[] getPointerCoords(MotionEvent e) {
final int index = e.getActionIndex();
final float[] coords = new float[] { e.getX(index), e.getY(index) };
Matrix matrix = new Matrix();
getImageMatrix().invert(matrix);
matrix.postTranslate(getScrollX(), getScrollY());
matrix.mapPoints(coords);
return coords;
}
public void setIsScale() {
isScale = !isScale;
setOnTouchListener(isScale ? zoomListener : drawListener);
}
And the screenshot
Update: Project code
Since it is quite difficult to figure out the problem though code extract , I have uplod the project (<1 mb) along with the used library:
if you have some spare time , you are welcome to take a look
it is a small drawing tool , first copy a folder with some images to the folder "HistoryTool" in your device
The path , for example, like:
sd card root/ HistoryTool/ folder1 / a.jpg
, then you can draw on it, but the draw transparent line has circle on it. thats all
https://drive.google.com/file/d/0B9mELZtUJp0LZncwQVM4alExalE/view?usp=sharing
I have complete the task by using On-draw and Path
drawListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getDrawable() != null) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downx = getPointerCoords(event)[0];// event.getX();
downy = getPointerCoords(event)[1];// event.getY();
holderList.add(new Holder(color, width, alpha));
holderList.get(holderList.size() - 1).path.moveTo(downx, downy);
break;
case MotionEvent.ACTION_MOVE:
upx = getPointerCoords(event)[0];// event.getX();
upy = getPointerCoords(event)[1];// event.getY();
holderList.get(holderList.size() - 1).path.lineTo(upx, upy);
invalidate();
downx = upx;
downy = upy;
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
}
return true;
}
};
setOnTouchListener(drawListener);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (img != null) {
tmp_canvas.drawBitmap(img, 0, 0, null);
}
for (Holder holder : holderList) {
tmp_canvas.drawPath(holder.path, holder.paint);
}
}
//draw view start
public void setNewImage(Bitmap alteredBitmap, Bitmap bmp) {
tmp_canvas = new Canvas(alteredBitmap);
img = bmp;
tmp_canvas.drawBitmap(bmp, 0, 0, null);
setImageBitmap(alteredBitmap);
}
So, your problem is circles on the line. This comes from each new segment of the line chain being drawn individually and each end point overlapping the previously drawn segment of the chain. You want to draw the path of the line before you call stroke(); This will draw the entire chain of line segments once instead of individually and prevent those circular overlaps.
What you could do is something like this: Make a line chain class that draws lines all at once:
/* Feed the line chain an array of points to draw when you construct it. */
/* points_=[{"x":0,"y":0},{"x":10,"y":10}]; */
function LineChain(points_){
this.points=points_;
}
LineChain.prototype={
constructor:LineChain,
/* Draws this line chain to the specified context. */
drawTo:function(context_){
/* Get the first point in the chain. */
var point=this.points[0];
/* Start the path by moving to the first position on the chain. */
context_.beginPath();
context_.moveTo(point.x,point.y);
/* Loop through the remaining points in the chain and draw the rest of the path. */
for (var index=1;index<this.points.length-1;index++){
point=this.points[index];
context_.lineTo(point.x,point.y);
}
}
}
Now when you actually want to draw the chain to the canvas, just do this:
/* Define a line chain. */
var line_chain=new LineChain([{"x":0,"y":0},{"x":10,"y":20},{"x":30,"y":40}]);
/* Wherever you're drawing at... */
context.strokeStyle="rgba(255,0,0,0.5)";
context.lineWidth=20;
line_chain.drawTo(context);
context.stroke();
Basically, it doesn't matter how you go about implementing this technique, the only thing you need to do is ensure that your entire path is drawn before you call the stroke function.
from the docs Paint.setAlpha(int)
set the alpha component [0..255] of the paint's color.
that means that 0 is transparent and 255 is fully opaque. 100 you're just setting to something in the middle.
Please refer to BlendMode that will help you fix this problem.
Expectation: If we draw same stroke even on itself it should not overdraw and decrease transparency.
Refer to below links.
https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/BlendMode
https://medium.com/mobile-app-development-publication/practical-image-porterduff-mode-usage-in-android-3b4b5d2e8f5f
Can someone explain me why on earth this piece of code does not draw every object?
public class A extends View {
private Paint paint = new Paint();
private Path path = new Path();
ArrayList<Pair<Path, Paint>> paths = new ArrayList<Pair<Path, Paint>>();
public A(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
for (Pair<Path, Paint> p : paths) {
canvas.drawPath(p.first, p.second);
}
canvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(3f);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int color = Color.rgb(new Random().nextInt(255),
new Random().nextInt(255),
new Random().nextInt(255));
paint.setColor(color);
path.reset(); //new stroke, get old one erased
int historySize = event.getHistorySize();
for (int i = 0; i > historySize; i++) {
path.moveTo(eventX, eventY);
}
path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(eventX, eventY);
return true;
case MotionEvent.ACTION_UP:
path.lineTo(eventX, eventY);
// End of stroke, add this to the collection
paths.add(new Pair<Path, Paint>(path, paint));
break;
default:
break;
}
// Schedules a repaint.
invalidate();
return true;
}
}
I'm catching every strokes with the onTouchEvent and i create different path/paint ojects stored in a Pair one. Sadly in my OnDraw when I try to draw them all it fails ..
I've read some topic without finding right answer. Each time some people recommend to create and work in a bitmap and draw it to the screen but i'd like to avoid this solution.
Thank's for your help !
The problem is that you always use the same Path and Paint object. You should create new Path and Paint each time MotionEvent.ACTION_DOWN is fired
Remove the path.reset in switch condition. The draw works. But color does change even for previous draw. You resetting the paths which is clearing the previous draw.
I would use a different pair (path and paint) everytime to draw.
In case you want to draw using canvas the below code work's just fine.
I would also add a color picker to allow user to pick color of his choice.
public class MyView extends View {
private static final float MINP = 0.25f;
private static final float MAXP = 0.75f;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mBitmapPaint;
public MyView(Context c) {
super(c);
mPath = new Path();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFAAAAAA);
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;
}
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);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
// 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);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
}
You need to use only one Path and Paint object.
A detailed tutorial on this whole thing can in fact be found here:
Making a basic single touch drawing app on Android - Creative Punch