When I start painting , it colors the whole background , I mean it should only paint the white spots.
Application screenshot is as follows.
Using Android Paint ,I want to paint only white spots on background-drawable[Panda] and skip any other color.
onDraw() function is:
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
canvas.drawPath(circlePath, circlePaint);
for (Pair<Path,Integer> path_clr : path_color_list ){
paint.setColor(path_clr.second);
canvas.drawPath( path_clr.first, paint);
}
for (Pair<Path,Integer> path_clr : circular_path_color_list ){
circlePaint.setColor(path_clr.second);
canvas.drawPath( path_clr.first, paint);
}
}
and onTouchEvent function is:
public boolean onTouchEvent(MotionEvent event) {
float pointX = event.getX();
float pointY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
circlePath.reset();
path.moveTo(pointX, pointY);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(pointX, pointY);
circlePath.reset();
circlePath.addCircle(pointX, pointY, 10, Path.Direction.CW);
break;
case MotionEvent.ACTION_UP:
circlePath.reset();
break;
default:
return false;
}
postInvalidate();
return true;
}
The thing you're describing is called masking. You need a mask (white areas) and a masked image (your strokes). When drawing, you have to use the mask to cut your strokes to a shape of the mask. It can be done using PorterDuff modes. See the pseudocode:
Bitmap panda;
Bitmap whiteAreas;
Bitmap strokes;
Canvas strokesCanvas;
Paint paint;
private void init() {
strokesCanvas = new Canvas(strokes);
paint = new Paint();
}
private void addStroke(Path stroke){
paint.setXfermode(null);
strokesCanvas.drawPath(stroke,paint);
invalidate();
}
#Override
public void draw(Canvas canvas) {
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
strokesCanvas.drawBitmap(whiteAreas,0,0,paint);
paint.setXfermode(null);
canvas.drawBitmap(panda,0,0,paint);
canvas.drawBitmap(strokes,0,0,paint);
}
See the link for more info: http://ssp.impulsetrain.com/porterduff.html
EDIT: Here's an image how it works. Blue areas should be transparent. Multiplication between the mask and the strokes is what's called masking.
Related
I am developing an App that need to detect the stylus's size, for instance, if I use my hand(relatively wide) to draw, return null, if I use the stylus(relatively small) to draw, execute draw.Point method.
I have no idea how to detect this.
Please for help thanks.
My code list as below.
public PaintView(Context context) {
super(context);
paint=new Paint(Paint.DITHER_FLAG);
bitmap = Bitmap.createBitmap(MainActivity.widthPixels, MainActivity.heightPixels, Bitmap.Config.ARGB_8888);
canvas=new Canvas();
canvas.setBitmap(bitmap);
paint.setStyle(Paint.Style.STROKE);
//float size = paint.getStrokeWidth();
paint.setStrokeWidth(5);
paint.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap,0,0,null);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction()==MotionEvent.ACTION_MOVE) {
canvas.drawLine(mov_x, mov_y, event.getX(), event.getY(), paint);
invalidate();
}
if (event.getAction()==MotionEvent.ACTION_DOWN) {
mov_x=(int) event.getX();
mov_y=(int) event.getY();
canvas.drawPoint(mov_x, mov_y, paint);
invalidate();
}
mov_x=(int) event.getX();
mov_y=(int) event.getY();
return true;
}
You can use MotionEvent.getSize() method to detect sizes of finger and stylus touch and then create rule how to determine what caused the touch. Also MotionEvent.getPressure() may be useful.
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 am creating a coaching board. I aim to draw a line between two points. Initial position is in the image below:
If I drag the chip away from the initial position, it should draw a line. When I move the chip, it should always draw a line.
This is what I tried:
case MotionEvent.ACTION_UP:
Toast.makeText(this, "here", Toast.LENGTH_SHORT).show();
Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(10);
int startx = 50;
int starty = 100;
int endx = 150;
int endy = 210;
canvas.drawLine(startx, starty, endx, endy, paint);
break;
I put that code inside the ontouchlistener of the chip.
You can download the project here: https://www.dropbox.com/s/ggfbsbkaokj9vxi/CoachingBoard.rar?dl=0
After further examining your code, I believe I have achieved what you'd like.
We're going to go to DrawingView and define a getter for drawCanvas, so we can access our canvas outside of the DrawingView class.
Next we're going to head to Basketball and do the following:
float startX;
float startY;
public boolean onTouch(View view, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getRawX();
startY = event.getRawY();
...
break;
case MotionEvent.ACTION_UP:
Paint paint = new Paint(); //set this as a field in drawView with another getter to avoid garbage collection penalties
paint.setStrokeWidth(15f);
paint.setColor(Color.BLACK);
drawView.getCanvas().drawLine(startX, startY, event.getRawX(), event.getRawY(), paint);
drawView.invalidate();
break;
}
}
What this does: when you pick up the chip it will save the starting coordinates, and when you drop the chip it will draw a line in your drawView canvas from start to end.
You can even draw the lines as a continuous Path so the lines always touch, but that is outside the context of this answer.
the best solution to draw a line by dragging is to get x and y positions from ACTION_DOWN and then get ever next x and y position from ACTION_MOVE, outside the switch statement, invalidate the canvas. draw the line with this starting x,y and ending x,y positions.
Code of these all explanation is below:
public class TouchEventView extends View {
float downxpos;
float downypos;
float upxpos;
float upypos;
private Paint paint = new Paint();
private Path path = new Path()
public TouchEventView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setColor(Color.GREEN);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawLine(downxpos, downypos, upxpos, upypos, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downxpos = event.getX();
downypos = event.getY();
case MotionEvent.ACTION_MOVE:
upxpos = event.getX();
upypos = event.getY();
break;
default:
return false;
}
invalidate();
return true;
}
}
hope this will help you and other community if you need more description feel free to ask.
I am trying to allow the user to touch the image and then basically a cirular magnifier will show that will allow the user to better select a certain area on the image. When the user releases the touch the magnified portion will dissapear. This is used on several photo editing apps and I am trying to implement my own version of it. The code I have below does magnify a circular portion of the imageview but does not delete or clear the zoom once I release my finger. I currently set a bitmap to a canvas using canvas = new Canvas(bitMap); and then set the imageview using takenPhoto.setImageBitmap(bitMap); I am not sure if I am going about it the right way. The onTouch code is below:
zoomPos = new PointF(0,0);
takenPhoto.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
zoomPos.x = event.getX();
zoomPos.y = event.getY();
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
shader.setLocalMatrix(matrix);
canvas.drawCircle(zoomPos.x, zoomPos.y, 20, shaderPaint);
takenPhoto.invalidate();
break;
case MotionEvent.ACTION_MOVE:
zoomPos.x = event.getX();
zoomPos.y = event.getY();
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
canvas.drawCircle(zoomPos.x, zoomPos.y, 20, shaderPaint);
takenPhoto.invalidate();
break;
case MotionEvent.ACTION_UP:
//clear zoom here?
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
});
Adapting your code, I was able to get the following approach working.
In the onTouch function, set a global point for determining where the user has touched, and set a boolean to indicate whether zooming is currently active or not:
#Override
public boolean onTouch(View view, MotionEvent event) {
int action = event.getAction();
zoomPos.x = event.getX();
zoomPos.y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
zooming = true;
this.invalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
zooming = false;
this.invalidate();
break;
default:
break;
}
return true;
}
Then, in the onDraw method, you use your code for drawing the zoomed in portion:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (zooming) {
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
mPaint.getShader().setLocalMatrix(matrix);
canvas.drawCircle(zoomPos.x, zoomPos.y, 100, mPaint);
}
}
Note that for the shader, I used a bitmap shader as described here, which was created with:
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
mShader = new BitmapShader(mBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mShader);
The best way to revert any changes made to the image will be to reload the image from the source file. Or alternatively, keep the a copy original matrix variable before transformations begun, during MotionEvent.ACTION_UP load the original matrix.
Some people asked for a fixed place magnifier position, I experimented it and came up with the solution:
// bitmapWidth is the width of bitmap used for BitmapShader
// bitmapHeight is the height of bitmap used for BitmapShader
// canvasWidth is the width of canvas where the zoom touch events are tracked (usually has the same image as shader but can be different size)
// canvasHeight is the height of canvas where the zoom touch events are tracked
// touchPoint is the point on the canvas which area should be shown in zoom circle
// fixedZoomPoint is the center of the zoom circle (different from touch point)
// ZOOM_SCALE is the zooming ratio (e.g.: 2f)
// ZOOM_RADIUS is the radius of the zoom circle
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (zooming) {
val widthRatio = bitmapWidth / canvasWidth // This can be omitted if 1.0
val heightRatio = bitmapHeight / canvasHeight // This can be omitted if 1.0
matrix.reset()
matrix.postScale(ZOOM_SCALE, ZOOM_SCALE, touchPoint.x * widthRatio, touchPoint.y * heightRatio)
matrix.postTranslate(fixedZoomPoint.x - touchPoint.x * widthRatio, fixedZoomPoint.y - touchPoint.y * heightRatio)
paint.getShader().setLocalMatrix(matrix)
drawCircle(fixedZoomPoint.x, fixedZoomPoint.y, ZOOM_RADIUS, paint)
}
}
How will I delete the image drawn on my canvas if my code is this? Where will I put the delete process here? I've tried using the canvas.drawColor(Color.BLACK); but it is not working.
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(eventX, eventY);
break;
case MotionEvent.ACTION_UP:
// nothing to do
break;
default:
return false;
}
// Schedules a repaint.
invalidate();
return true;
}
}
Old thread I know, but I was mucking around with API fingerpaint demo and wanted to clear canvas but not fill with solid colour (I had a background). Building on #coder_For_Life22 answer above I included following method:
protected void clear(){
Xfermode x = mPaint.getXfermode();
mPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
mCanvas.drawPaint(mPaint);
mPaint.setXfermode(x);
//Schedule redraw()
invalidate();
}
Try this with your Paint object..
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
canvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC));
When invalidate() (or postInvalidate() from another thread) is called, onDraw() is subsequently called to redraw the entire area of the image. The Canvas object that is passed to onDraw() is backed with a bitmap that is already blank.
I realise that this doesn’t directly answer your question, but from reading your question I wonder if you’re misunderstanding the sequence of events that happen with invalidate() and onDraw(), together with the fact that you’re given a blank Canvas each time meaning you shouldn’t have a need to erase it.
It seems to me that what you're doing is you're trying to build up a Path vector representing the screen MotionEvents. Looking at your code as it stands, it seems to me that you may want to erase all drawn graphics by clearing all segments from your Path object.
canvas.drawColor(0xff000000); // i can't see why it should not work except the clip rect mentioned below
or
Paint paint = new Paint();
paint.setStyle(Style.FILL);
paint.setColor(0xff000000); // Specify the drawing color here
canvas.drawRect(0,0,w,h, paint);
always make sure that you did not set a clip that would influence the drawing behaviour.