Inner and outer paths of a stroke - android

Path path = new Path();
path.moveTo(0, 0);
path.quadTo(100, 0, 200, 200);
// more quadTo calls
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(20);
paint.setColor(Color.BLACK);
canvas.drawPath(path,paint);
From the path used to draw the stroke, is there a way to compute the inner and outer paths, in red and blue on the picture below ?

Hmmm...
How about:
1. draw double width stroke first, then draw fill
2. clip path, then draw double width stroke

Related

What do PorterDuff source and destination refer to when drawing on canvas?

I've been trying to figure this out all night, but answers found on Google relate to very specific problems regarding Android's canvas and I haven't found any 101 explanations on this topic. Even Android documentation uses bitmaps instead of drawing shapes.
Specific problem:
I need to draw an oval and a path on canvas. And according to documentation colour source out with one colour, destination out another colour and overlapping area, either source in or destination in, a third colour. I'm trying to do all this in an offscreen canvas. but problems arise with some of the steps above and get worse when trying to combine them in any way.
Code -
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888);
Canvas c = new Canvas (bmp);
Paint paint = new Paint ();
paint.SetARGB (255, 255, 0, 0);
c.DrawOval (200, 200, 520, 520, paint); //assumed destination
paint.SetARGB (255, 0, 0, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.*)); //replace mode here
paint.SetStyle (Paint.Style.Fill);
Path path = new Path ();
path.MoveTo (c.Width / 2f, c.Height / 2f);
foreach (var m in measurements) {
//calculations
float x = xCalculatedValue
float y = yCalculatedValue
path.LineTo (x, y);
}
path.LineTo (c.Width / 2f, c.Height / 2f);
c.DrawPath (path, paint); //assumed source
Source out -
This instead draws what XOR is supposed to draw.
Destination out -
This works as expected.
Source in -
This draws what source atop should.
Destination in -
This draws what destination should.
More general question:
What do source and destination refer to in this context? Intuitively I would assume that destination is the current state of the canvas bitmap and source is the matrix added by canvas.Draw* and Paint PortedDuff.Mode. But that doesn't seem to be the case.
EDIT: This is basically the effect I'm after, where the "star" is a dynamic path. Coloured three different colours depending on overlap.
Crude drawing
EDIT 2: York Shen did a great job answering the actual question. But for anyone wanting to get a similar effect here's the final code.
Bitmap DrawGraphBitmapOffscreen ()
{
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888);
Canvas c = new Canvas (bmp);
// Replace with calculated path
Path path = new Path ();
path.MoveTo (c.Width / 2f, c.Height / 2f);
path.LineTo (263, 288);
path.LineTo (236, 202);
path.LineTo (312, 249);
path.LineTo (331, 162);
path.LineTo (374, 240);
path.LineTo (434, 174);
path.LineTo (431, 263);
path.LineTo (517, 236);
path.LineTo (470, 312);
path.LineTo (557, 331);
path.LineTo (479, 374);
path.LineTo (545, 434);
path.LineTo (456, 431);
path.LineTo (483, 517);
path.LineTo (407, 470);
path.LineTo (388, 557);
path.LineTo (345, 479);
path.LineTo (285, 545);
path.LineTo (288, 456);
path.LineTo (202, 483);
path.LineTo (249, 407);
path.LineTo (162, 388);
path.LineTo (240, 345);
path.LineTo (174, 285);
path.LineTo (263, 288);
path.Close ();
Paint paint = new Paint ();
paint.SetARGB (255, 255, 0, 0);
paint.SetStyle (Paint.Style.Fill);
c.DrawPath (path, paint);
paint.SetARGB (255, 0, 0, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.SrcIn));
c.DrawOval (200, 200, 520, 520, paint);
paint.SetARGB (255, 255, 255, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.DstOver));
c.DrawOval (200, 200, 520, 520, paint);
return bmp;
}
What do PorterDuff source and destination refer to when drawing on canvas?
After some in-depth study, I write a few demo to explain this deeply. To help you understand what is source and destination refer to.
First, look at the following code :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Draw a blue rectangle
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
I override the OnDraw method, set a green background, then draw a yellow circle and a blue rectangle, effect :
Above is the normal procedure when we draw a Canvas, I didn't use any PorterDuffXfermode,let's analyse its process :
First, we call canvas.DrawARGB(255, 139, 197, 186) method draw the whole Canvas with a single color, every pixels in this canvas has the same ARGB value : (255, 139, 197, 186). Since the alpha value in ARGB is 255 instead of 0, so every pixels is opaque.
Second, when we execute canvas.DrawCircle(r, r, r, paint) method, Android will draw a yellow circle at the position you have defined. All pixels which ARGB value is (255,139,197,186) in this circle will be replaced with yellow pixels.
The yellow pixels is source and the pixels which ARGB value is (255,139,197,186) is destination. I will explain later.
Third, after execute the canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint) method, Android will draw a blue rectangle, all pixels in this rectangle is blue, and these blue pixels will replac other pixels in the same position. So the blue rectangle can be draw on Canvas.
Second, I use a mode of Xfermode, PorterDuff.Mode.Clear :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Use Clear as PorterDuffXfermode to draw a blue rectangle
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear));
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
paint.SetXfermode(null);
this.SetLayerType(LayerType.Software, null);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//I found that PorterDuff.Mode.Clear doesn't work with hardware acceleration, so you have add this code
}
Effect :
Let's analyse its process :
First, we call canvas.DrawARGB(255, 139, 197, 186) method to draw the whole Canvas as a single color, every pixels is opaque.
Second, we call canvas.DrawCircle(r, r, r, paint) method to draw a yellow
circle in Canvas.
Third, execute paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear)), set the paint PorterDuff model to Clear.
Forth, call canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint) to draw a blue rectangle, and finally it shows a white rectangle.
Why it display a white rectangle? Usually, when we call canvas.DrawXXX() method we will pass a Paint parameter, when Android execute draw method it will check whether the paint has a Xfermode mode. If not, then the graphics will directly covers the pixels that in Canvas at the same position. Otherwise, it will update the pixels in Canvas according to the Xfermode mode.
In my example, when execute canvas.DrawCirlce() method, Paint didn't has a Xfermode model, so the yellow circle directly cover the pixels in Canvas. But when we call canvas.DrawRect() to draw a rectangle, Paint has a Xfermode value PorterDuff.Mode.Clear. Then Android will draw a rectangle in memory, the pixels in this rectangle has a name : Source. The rectangle in memory has a corresponding rectangle in Canvas, the corresponding rectangle is called :
destination .
The value of the ARGB of the source pixel and the value of the ARGB of the destination pixel are calculated according to the rules defined by Xfermode, it will calculate the final ARGB value. Then update the ARGB value of the target pixel with the final ARGB value.
In my example, the Xfermode is PorterDuff.Mode.Clear, it require destination pixels ARGB becomes (0,0,0,0), that means it is transparent. So we use canvas.DrawRect() method draw a transparent rectangle in Canvas, since Activity itself has a white background color, so it will show an white rectangle.
EDIT :
To implement the feature you post in the picture, I write a demo :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
paint.SetARGB(255, 255, 0, 0);
RectF oval2 = new RectF(60, 100, 300, 200);
canvas.DrawOval(oval2, paint);
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.*));
Path path = new Path();
paint.SetStyle(Paint.Style.Fill);
paint.SetARGB(255, 0, 0, 255);
path.MoveTo(180, 50);
path.LineTo(95, 240);
path.LineTo(255, 240);
path.Close();
this.SetLayerType(LayerType.Software, null);
canvas.DrawPath(path, paint);
paint.SetXfermode(null);
}
When use different Xfermode, their effect :
Xor, SrcOut, Screen, Lighten, Darken, Add.
As you can see, you could use different color and different Xfermode to achieve your effect.

How to draw a curved line in android?

I am new to Android and I am developing a sample project on drawing lines. I want to draw a curved or elevated line connecting two points (x1,y1 and x2,y2). I tried canvas.drawArc() method, but the RectF values inside the drawArc method is just the x,y center points of circle. It is giving me an arc between my two points. But I want a curved line exactly connecting my two points. Can somebody help me? Thanks in advance.
Declare this method inside onDraw method:
private void drawOvalAndArrow(Canvas canvas){
Paint circlePaint = new Paint();
circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
circlePaint.setAntiAlias(true);
circlePaint.setStrokeWidth(2);
circlePaint.setColor(Color.CYAN);
float centerWidth = canvas.getWidth()/2; //get center x of display
float centerHeight = canvas.getHeight()/2; //get center y of display
float circleRadius = 20; //set radius
float circleDistance = 200; //set distance between both circles
//draw circles
canvas.drawCircle(centerWidth, centerHeight, circleRadius, circlePaint);
canvas.drawCircle(centerWidth+circleDistance, centerHeight, circleRadius, circlePaint);
//to draw an arrow, just lines needed, so style is only STROKE
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setColor(Color.RED);
//create a path to draw on
Path arrowPath = new Path();
//create an invisible oval. the oval is for "behind the scenes" ,to set the path´
//area. Imagine this is an egg behind your circles. the circles are in the middle of this egg
final RectF arrowOval = new RectF();
arrowOval.set(centerWidth,
centerHeight-80,
centerWidth + circleDistance,
centerHeight+80);
//add the oval to path
arrowPath.addArc(arrowOval,-180,180);
//draw path on canvas
canvas.drawPath(arrowPath, circlePaint);
//draw arrowhead on path start
arrowPath.moveTo(centerWidth,centerHeight ); //move to the center of first circle
arrowPath.lineTo(centerWidth-circleRadius, centerHeight-circleRadius);//draw the first arrowhead line to the left
arrowPath.moveTo(centerWidth,centerHeight );//move back to the center
arrowPath.lineTo(centerWidth+circleRadius, centerHeight-circleRadius);//draw the next arrowhead line to the right
//same as above on path end
arrowPath.moveTo(centerWidth+circleDistance,centerHeight );
arrowPath.lineTo((centerWidth+circleDistance)-circleRadius, centerHeight-circleRadius);
arrowPath.moveTo(centerWidth+circleDistance,centerHeight );
arrowPath.lineTo((centerWidth+circleDistance)+circleRadius, centerHeight-circleRadius);
//draw the path
canvas.drawPath(arrowPath,circlePaint);
}
Also this will find the two sides of the screen (Landscape mode) and will draw a perfect curve across the screen
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
PointF mPoint1 = new PointF(w/1.2F, h/1.2F);
PointF mPoint2 = new PointF(w/24, h/1.2F);
Path myPath1 = new Path();
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);
myPath1 = drawCurve(canvas, paint, mPoint1, mPoint2);
canvas.drawPath(myPath1, paint);
}
private Path drawCurve(Canvas canvas, Paint paint, PointF mPointa, PointF mPointb) {
Path myPath = new Path();
myPath.moveTo(63*w/64, h/10);
myPath.quadTo(mPointa.x, mPointa.y, mPointb.x, mPointb.y);
return myPath;
}
Useful references on painting in android:
How to draw Arcs in Android using canvas?
Basic Painting with Views
It might not be what u want, but take a look at http://developer.android.com/reference/android/graphics/Path.html more precisely at moveTo, lineTo, quadTo and cubicTo. (The last 2 methods will draw bezier curves, either quadratic or cubic. If u don't know what those are, take a look at http://en.wikipedia.org/wiki/B%C3%A9zier_curve You only need to understand the parameters of the funcion, not the math behind it). For your purpose you can do like this:
Path mPath;
Paint paint;
mPath = new Path();
mPath.moveTo(x1, y1);
mPath.cubicTo(anchor1_x, anchor1_y, anchor2_x, anchor2_y, x2, y2); /*the anchors you want, the curve will tend to reach these anchor points; look at the wikipedia article to understand more */
paint = new Paint();
paint.setColor(0xFFFFFFFF);
paint.setAntiAlias(true);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(width); //the width you want
canvas.drawPath(mPath, paint);

Drawing a filled rectangle with a border in android

Is there any way in Android to draw a filled rectangle with say a black border. My problem is that the canvas.draw() takes one paint object, and to my knowledge the paint object can't have a different color for the fill and the stroke. Is there a way around this?
Try paint.setStyle(Paint.Style.FILL) and paint.setStyle(Paint.Style.STROKE).
Paint paint = new Paint();
Rect r = new Rect(10, 10, 200, 100);
#Override
public void onDraw(Canvas canvas) {
// fill
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.MAGENTA);
canvas.drawRect(r, paint);
// border
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
canvas.drawRect(r, paint);
}
If you are drawing multiple views then you could also use two paints, one for the stroke and one for the fill. That way you don't have to keep resetting them.
Paint fillPaint = new Paint();
Paint strokePaint = new Paint();
RectF r = new RectF(30, 30, 1000, 500);
void initPaints() {
// fill
fillPaint.setStyle(Paint.Style.FILL);
fillPaint.setColor(Color.YELLOW);
// stroke
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setColor(Color.BLACK);
strokePaint.setStrokeWidth(10);
}
#Override
protected void onDraw(Canvas canvas) {
// First rectangle
canvas.drawRect(r, fillPaint); // fill
canvas.drawRect(r, strokePaint); // stroke
canvas.translate(0, 600);
// Second rectangle
int cornerRadius = 50;
canvas.drawRoundRect(r, cornerRadius, cornerRadius, fillPaint); // fill
canvas.drawRoundRect(r, cornerRadius, cornerRadius, strokePaint); // stroke
}
You draw a rectangle with the color of the border and the size of the rectangle plus the border, you change the color of the paint and draw again the rectangle with the normal size.

sharp corner for circle(draw on canvas) in android

i am drawing chart using canvas.
Bitmap image;
image= //here i get bitmap which i want to draw on canvas
Canvas canvas=new Canvas(image);
// i have draw circle as follow
canvas.drawCircle(cx, cy, radius, paint);
but circle corner is not sharp:
ii is showing something like this:
how to i make circle outer radius sharp..
Thanks in advance..
When you initialise your paint, set these properties:
paint.setAntiAlias(true);
paint.setDither(true);
paint.setFilterBitmap(true);
See the Android documentation for what each one does;
http://developer.android.com/reference/android/graphics/Paint.html
set the anti alias
paint.setAntiAlias(true);
let's say:
cx = 108.0F;
cy = 108.0F;
radius = 88.0F;
canvas.drawCircle(cx, cy, radius, paint);
Example:
Paint p = new Paint();
p.setAntiAlias(true);
p.setFilterBitmap(true);
p.setDither(true);
p.setColor(Color.WHITE);
p.setStrokeWidth(3.75F);
p.setStyle(Paint.Style.STROKE);
Bitmap bmp1 = Bitmap.createBitmap(216, 216, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp1);
canvas.drawCircle(108.0F, 108.0F, 88.0F, p); // since the bitmap size is 216
//then, the starting (x) and the end (y) points must begin from
//the center to be a nice circle, that's why I used 108 as 108*2 = 216.
//and the 88 is the radius of the desired circle

Draw a border (Paint) on current clip (created by different Region.Op)

I want to draw an image into shape of a Path and then add border on the Path. I was able to clip the image with Path but can't find a way to add border on it. I though it would be simple because the API supports Paint object on Canvas.draw* methods.
I asked another question at: Draw bitmap on current clip in canvas with border (Paint) and I accepted the answer. However, after that I found that I need to do a little bit more complicated processing. Because I use two options for clipping instead of one.
Below is my code to clip and image with two different Region.Op parameters
Bitmap srcImage = BitmapFactory.decodeStream(getAssets().open("panda.jpg"));
Bitmap bitmapResult = Bitmap.createBitmap(srcImage.getWidth(), srcImage.getHeight(), Bitmap.Config.ARGB_8888);
Path path = new Path();
// This is my border
Paint paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setColor(Color.RED);
paint.setStrokeWidth(2);
paint.setAntiAlias(true);
Canvas canvas = new Canvas(bitmapResult);
// Overlay two rectangles
path.addRect(10, 10, 70, 70, Path.Direction.CCW);
path.addRect(40, 40, 120, 120, Path.Direction.CCW);
canvas.drawPath(path , paint);
canvas.clipPath(path, Region.Op.INTERSECT);
// Draw the circle
path.reset();
path.addCircle(40, 80, 20, Path.Direction.CCW);
canvas.drawPath(path , paint);
canvas.clipPath(path, Region.Op.DIFFERENCE);
// The image is drawn within the area of two rectangles and a circle
// Although I suppose that puting Paint object into drawBitmap() method will add a red border on result image but it doesn't work
canvas.drawBitmap(srcImage, 0, 0, paint);
((ImageView)this.findViewById(R.id.imageView1)).setImageBitmap(bitmapResult);
Here is the result from my code: http://i.stack.imgur.com/8j2Kg.png
And this is what I expect: http://i.stack.imgur.com/iKhIr.png
Do I miss anything to make it work ?

Categories

Resources