I'm trying to create a 'glow' effect using the Android Path class. However, the gradient is not being warped to fit around the path. Instead, it is simply being display 'above' it and clipped to the path's stroke. Using a square path, the image below shows what I mean:
Instead, that should look more like this:
In other words, the gradient follows the path, and in particular wraps around the corners according to the radius set in the CornerPathEffect.
Here is the relevant part of the code:
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(20);
paint.setAntiAlias(true);
LinearGradient gradient = new LinearGradient(30, 0, 50, 0,
new int[] {0x00000000, 0xFF0000FF, 0x00000000}, null, Shader.TileMode.MIRROR);
paint.setShader(gradient);
PathEffect cornerEffect = new CornerPathEffect(10);
paint.setPathEffect(cornerEffect);
canvas.drawPath(boxPath, paint);
Any ideas?
Another alternative is to get a 'soft-edged brush' effect when defining the stroke width. I've experimented with BlurMaskFilters, but those give a uniform blur rather than a transition from opaque to transparent. Does anyone know if that's possible?
How about drawing with a soft brush bitmap? Make a soft circular brush with opacity decreasing radially outward using image editing software like Photoshop. Save as drawable, load it in a bitmap and draw it evenly spaced along your path. Make the bitmap with white coloured brush. This way you can simply multiply the given colour(Here blue) to your bitmap using PorterDuffColorFilter.
brush1=BitmapFactory.decodeResource(getResources(), R.drawable.brush_custom_one);
//This contains radially decreasing opacity brush
porter_paint.setColorFilter(new PorterDuffColorFilter(paint.getColor(), Mode.MULTIPLY));
for (int i=1;i<matrix.size();i++) {
//matrix contains evenly spaced points along path
Point point = matrix.get(matrix.get(i));
canvas.drawBitmap(brush1, point.x,point.y, porter_paint);}
The brush used is (It's there):
The final result is:
Turns out there was a stupidly obvious way of doing this. Simply re-use the same path, and adjust the stroke width and alpha on each drawing pass. Example code:
float numberOfPasses = 20;
float maxWidth = 15;
for (float i = 0; i <= numberOfPasses; i++){
int alpha = (int) (i / numberOfPasses * 255f);
float width = maxWidth * (1 - i / numberOfPasses);
paint.setARGB(alpha, 0, 0, 255);
paint.setStrokeWidth(width);
canvas.drawPath(path, paint);
}
See below for an example of the result. The left path was drawn using this method, the right path, for comparison, is drawn in a single stroke with maxWidth and 255 alpha.
This mainly works. There are two problems:
The gradient isn't as smooth as it could be. This is because each pass being drawn over the previous one results in the alpha building up too quickly, reaching 255 before the final strokes. Experimenting a bit with the line int alpha = (int) (i / numberOfPasses * 125f); (note the change to 125f rather than 255f) helps.
The path looks like it has been 'cut' on the insides of the corners. Probably some result of the CornerPathEffect applied.
What you're wanting to do, if I understand it right, is to have the gradient effectively form a "brush" for the stroke.
This is exactly what I also was trying to achieve recently, but as far as I can tell the API doesn't provide any straightforward means to do it. I have recently created an SVG to Android Canvas converter class and so I am working a lot in Inkscape lately, too. So, when I was looking into it, I wondered if it's even possible to do it in Inkscape. However, even in Inkscape it's a very non-trivial thing to do. After some searching I eventually came across this image of a gradient being applied along the course of a path, together with a download link for a tutorial beneath:
http://www.flickr.com/photos/35772571#N03/3312087295/
What I was personally trying to do at the time was to create some semi-circles where the path is a kind of neon glow as opposed to a flat colour. Talking in terms of both the Android API and the SVG standard, it seems that the only way to to do this is to create a radial gradient that's centred perfectly on the circle, and position a series of color stops in exactly the right places. Pretty tricky to do, and I certainly don't know how you'd do it to a shape like a square.
Sorry that this is a bit of a 'I couldn't do it either' rather than a useful answer! I'll follow this with interest as I'm eager to know a solution for a kind of 'soft brush' effect too.
Can be very complicated to draw a gradient than follow a path.
So I suggest you to use some library already done than make it for you.
One can be Sc-Gauges.
Have some usefully classe than you can use for your goal.
For first include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
After create an image or what you want with a canvas where draw:
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now the code:
// Dimensions
int padding = 24;
Rect drawArea = new Rect(padding, padding, 700 - padding, 500 - padding);
// Get the main layout
ImageView imageContainer = (ImageView) this.findViewById(R.id.image);
assert imageContainer != null;
// Create a bitmap and link a canvas
Bitmap bitmap = Bitmap.createBitmap(
drawArea.width() + padding * 2, drawArea.height() + padding * 2,
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.parseColor("#f5f5f5"));
// Create the path building a bezier curve from the left-top to the right-bottom angles of
// the drawing area.
Path path = new Path();
path.moveTo(drawArea.left, drawArea.top);
path.quadTo(drawArea.centerX(), drawArea.top, drawArea.centerX(), drawArea.centerY());
path.quadTo(drawArea.centerX(), drawArea.bottom, drawArea.right, drawArea.bottom);
// Feature
ScCopier copier = new ScCopier();
copier.setPath(path);
copier.setColors(Color.RED, Color.GREEN, Color.BLUE);
copier.setWidths(20);
copier.draw(canvas);
// Add the bitmap to the container
imageContainer.setImageBitmap(bitmap);
And this the result:
The first part of the code is just for create a bitmap where draw.
What you interest is the second part where use ScCopier.
Just give the path, the color and the with.
Note than is you are inside a view you can use onDraw for draw directly on the view canvas.
This library can used to create gauge of every kind.
If you want take a look to this site ScComponents have some free and not gauges components.
Related
I am trying to draw a heart shaped Canvas using Path in Android. The code is as follows :
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Fill the canvas with background color
canvas.drawColor(Color.WHITE);
// paint.setShader(null);
// Defining of the heart path starts
path.moveTo(left + WIDTH / 2, top + HEIGHT / 4); // Starting point
// Create a cubic Bezier cubic left path
path.cubicTo(left+WIDTH/5,top,
left+WIDTH/4,top+4*HEIGHT/5,
left+WIDTH/2, top+HEIGHT);
// This is right Bezier cubic path
path.cubicTo(left + 3 * WIDTH / 4, top + 4 * HEIGHT / 5,
left + 4 * WIDTH / 5, top,
left + WIDTH / 2, top + HEIGHT / 4);
paint.setShader(new LinearGradient(0, canvas.getHeight()/4, canvas.getWidth(), canvas.getHeight()/4, new int[]{Color.RED, Color.YELLOW, Color.GREEN}, new float[]{0, 0.6f, 1}, Shader.TileMode.CLAMP));
canvas.drawPath(path, paint);
heart_outline_paint.setColor(getResources().getColor(R.color.heart_outline_color)); // Change the boundary color
heart_outline_paint.setStrokeWidth(4);
heart_outline_paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, heart_outline_paint);
}
I am able to draw heart without any issue and I am able to fill color inside the heart using the Fill option in Paint. But I should be able to fill the heart dynamically according to some data and it cannot be filled fully all the time. What I have achieved so far is as follows :
I have made an extensive search and came across a lot of things similar to this. Some of which includes :
Android fill in part of a path?
filling a circle gradually from bottom to top android
I also came across the concept of converting the canvas to bitmap and filling color inside the bitmap using Flood Fill Algorithm which lets users to fill colors inside the bitmap. However, I do not want the bitmap to fill the color while touching inside the heart but to fill while a button click action.
I thought that filling a circle gradually from bottom to top android
would give help me but it makes use of a circle and I am not well-versed in Canvas which makes me very weak in adapting the circle code to such a shape.
If anybody has some ideas or any insights on how to achieve this, it will be really helpful. Cheers. Thanks in advance.
P.S : I also tried some tricks using setShader in Paint but nothing would give me what I want.
EDIT :
I just stumbled upon a idea of drawing a rectangle over the heart with another color same as the background of the canvas so that it will look like its half filled !! I am still working on the idea and not sure how accurate this is gonna be for me. If someone has a better idea, you're most welcome.
I used clipPath function available in Canvas to achieve what I needed. I draw the heart by above method and draw a rectangle over it, and I use the clipPathfunction to clip out the region that is outside the heart.
public static double filled_amount = .90;
path.moveTo(left_x_moveto, left_y_moveto);
path.cubicTo(left_x1, left_y1, left_x2, left_y2, left_x3, left_y3);
path.cubicTo(right_x2, right_y2, right_x1, right_y1, left_x_moveto, left_y_moveto);
path.close();
Rect rect = new Rect((int)(canvas.getWidth()*.10),(int)(canvas.getHeight()*filled_amount),(int) canvas.getWidth(), (int) canvas.getHeight());
canvas.clipPath(path);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
canvas.drawRect(rect, rect_paint);
heart_outline_paint.setColor(getResources().getColor(R.color.heart_outline_color)); // Change the boundary color
heart_outline_paint.setStrokeWidth(15);
heart_outline_paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, heart_outline_paint);
This will give me the desired result of filling the heart dynamically. Changing the value of filled_amount dynamically and calling invalidate() will make it look like the heart is being filled dynamically.
#Henry's answer might be a better one but this did the trick for me and I dont look deeply in to the edges so a bit of zig-zags here and there is all right.
You could use Bitmap Masking to get a partially filled Heart. What you ideally do here is use one bitmap to mask the other.
In your case you could have a filled rectangle in the canvas and you have then have the heart shape in a new bitmap to act as the mask. You could then dynamically change the filling of the heart by changing the height of the background rectangle.
Refer this: https://stackoverflow.com/a/33483600/4747587. This contains the implementation of partially filling a Star. The idea is the same.
I have an 8x8 Image. (bitmap - can be changed)
What I want to do is be able to draw a shape, given a Path and Paint object onto my SurfaceView.
At the moment all I can do is fill the shape with solid colour. How can I draw it with a pattern.
In the image you can see the brush pattern (The cross). It can be anything from a cross to a donut or an elf.
How would I go about drawing this pattern background.
I also eventually want to apply colours to it.
So far, my theory is to create a clip area of the shape, and tile the bitmaps until area is covered, but this is extreme overkill in processing. Nor sound ideal.
In terms of colouring, I can edit the brushes to be alpha, fill the same with background colour, then draw the images on top. The real issue it the tiling of such patterns.
Ive found a few questions of a similar nature, all unanswered, and/or not applicable to my situation. (use of xmls on views etc)
Did you checked this blog. Its using BitmapShader
Example:
//Initialize the bitmap object by loading an image from the resources folder
fillBMP = BitmapFactory.decodeResource(m_context.getResources(), R.drawable.cross);
//Initialize the BitmapShader with the Bitmap object and set the texture tile mode
fillBMPshader = new BitmapShader(fillBMP, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
fillPaint.setStyle(Paint.Style.FILL);
//Assign the 'fillBMPshader' to this paint
fillPaint.setShader(fillBMPshader);
//Draw the fill of any shape you want, using the paint object.
canvas.drawCircle(posX, posY, 100, fillPaint);
I have this image that comes back from an API, which represents the users avatar:
However, my graphics department has designed the app to mask the image to make it look like this at runtime (to match our existing design of sharp edges, etc):
Notice the small edge cutout on the bottom left?
I'd love to be able to create a custom ImageView that handled this for me. Unfortunately I'm not sure how to go about doing that. How can I create the bottom image in a custom ImageView. Is this possible? Do I mask it? If so, how?
Thanks!
Using Path and xfer modes to draw on canvas can do the trick. Check this answer how to draw pic to Closed curve area
I think the easiest way to do is to use 2 ImageViews, one with the photo and other above it with a mask for the photo, in your case it would be all transparent except the bottom left to create the cutout with the background color.
You may be able to use android.graphics.Path to draw the complex shape you want. I found this very helpful for a simple custom View, but it seems like you can do a lot with it:
http://developer.android.com/reference/android/graphics/Path.html
Simple code sample for a shaded rectangle:
private Path mRectanglePath;
...
// draw the path
mRectanglePath = new Path();
mRectanglePath.addRect(mLeft, mTop, mRight, mBottom, Path.Direction.CW);
// draw the fill
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setAlpha(64);
canvas.drawPath(mRectanglePath, paint);
I want a cylindrical, spider web like layout:
I know that I can use canvas to draw this but I also need all portions to be clickable, and canvas is very hard to handle touch for all portion.
Ideas?
can i want layout like spider...
Yes you can want it. But if you want to actually create that layout then you cannot do it with the standard android widgets.
If you want to make it then I would suggest drawing it on a Canvas manually and using the onTouchListener to catch the key presses.
I am not sure but i hope this can help you ...
The Path class holds a set of vector-drawing commands such as lines,
rectangles, and curves. Here’s an example that defines a circular path:
circle = new Path();
circle.addCircle(150, 150, 100, Direction.CW);
This defines a circle at position x=150, y=150, with a radius of 100
pixels. Now that we’ve defined the path, let’s use it to draw the circle’s
outline plus some text around the inside:
private static final String QUOTE = "Now is the time for all " +
"good men to come to the aid of their country." ;
canvas.drawPath(circle, cPaint);
canvas.drawTextOnPath(QUOTE, circle, 0, 20, tPaint);
You can see the result in this Figure
If you want to get really fancy, Android provides a number of PathEffect
classes that let you do things such as apply a random permutation to a
path, cause all the line segments along a path to be smoothed out with
curves or broken up into segments, and create other effects.
I want to create a speech balloon type shape, where there is a rectangle or ellipse with a triangular shape jutting out of it.
How I'm attempting to do this is to create a Path object that combines the triangle with the other shape (round rect).
I'm doing it as follows:
Path path = new Path();
// Create triangular segment
Point drawOffset = getAttributes().getDrawOffset();
int leaderGap = getAttributes().getLeaderGapWidth();
// Recall that we have a coordinate system where (0,0) is the
// bottom midpoint of the annotation rectangle.
// the point to left of gap
int x1 = -leaderGap/2;
int y1 = 0;
// the point to right of gap
int x2 = leaderGap/2;
int y2 = 0;
// The point where we're drawing to; the end of the pointy segment of triangle
int x3 = -drawOffset.x;
int y3 = drawOffset.y;
path.moveTo(x2, y2);
path.lineTo(x3, y3);
path.lineTo(x1, y1);
// path.close();
// Add the rectangular portion to the path
path.addRoundRect(backgroundShape, 5, 5, Path.Direction.CW);
The problem is that the roundRect is a closed path, so its edge shows through underneath the triangular section.
A picture's worth a thousand words, so here you go:
What I want is for the line segment between those two endpoints of the triangle to disappear, so that it looks like one seamless path.
If all I were doing were a straight rectangle, I could create the whole path myself from scratch. But I'd like to do the rounded corners, and it'd be a bit of a paint to do that with the Path (yes I know you can do quad to and arcTo but it's still not as clean a solution as I'd like).
So in general, is it possible to combine two paths and create a single union object that traces the perimeter of both?
Yes this is possible as of API 19. You can perform logical operations between 2 paths. As in your case, you can create a UNION of the two paths, using Path.op(). For a complete list of the operations that can be performed refer here.
I can't see a convenient way to combine paths in this manner. I would normally achieve this by manually drawing the rounded rectangular parts with Path.arcTo() and Path.lineTo(), it's a little extra effort but it will achieve the result you are looking for.
Another option which is perhaps more flexible if you decide to change the theme is to use a ninepatch drawable there's even an editor to let you create them called Draw 9-patch