Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a gradient along a path

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:

Path-gradient fail

Instead, that should look more like this:

enter image description here

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?

like image 330
Steve Haley Avatar asked Jun 24 '11 13:06

Steve Haley


4 Answers

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): Brush used The final result is: Final result

like image 153
codeslayer1 Avatar answered Nov 16 '22 20:11

codeslayer1


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.

Example of drawing result

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.

like image 28
Steve Haley Avatar answered Nov 16 '22 21:11

Steve Haley


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.

like image 2
Trevor Avatar answered Nov 16 '22 21:11

Trevor


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:

enter image description here

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.

like image 1
Samuele Avatar answered Nov 16 '22 19:11

Samuele