8

I'd like to draw a Bitmap on a Canvas, with a (linear) alpha gradient applied. The important point is that I don't want to overlay the image with any other color; the background (coming from the Views behind the View that I'd be drawing this Canvas to) should just "shine through". To illustrate, my goal would be something like this (the checkerboard pattern represents the View behind)

linear alpha gradient

One would think that I could do something like this:

Bitmap bitmap = ...;
Paint paint = new Paint();
paint.setShader(new LinearGradient(0, 0, 100, 0, FROM, TO, Shader.TileMode.CLAMP));
canvas.drawBitmap(bitmap, 0, 0, paint);

but LinearGradient's FROM and TO arguments here would need to be colors, not alpha values; so there's no way that I see to specify that e.g. FROM should be fully transparent and TO should be fully opaque (without applying any color overlay).

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • check this library https://github.com/chiuki/android-graphics-demo – PN10 Nov 02 '16 at 14:58
  • @Cactus I see `LinearGradient` expects `color0` (from) and `color1` (to) to be an int. Will it allow a decimal representation of an rbga value, or does it only allow rgb, or is it expecting something else entirely? The documentation is rather unclear on what this int value is. https://developer.android.com/reference/android/graphics/LinearGradient.html – d.j.brown Nov 02 '16 at 15:07
  • @d.j.brown: it's a 32-bit ARGB value. – Cactus Nov 02 '16 at 15:08

2 Answers2

12

use a ComposeShader, like this:

class V extends View {
    Bitmap bitmap;
    Paint paint = new Paint();

    public V(Context context) {
        super(context);
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.chrome);
        Shader shaderA = new LinearGradient(0, 0, bitmap.getWidth(), 0, 0xffffffff, 0x00ffffff, Shader.TileMode.CLAMP);
        Shader shaderB = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        paint.setShader(new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SRC_IN));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);
    }
}
pskink
  • 23,874
  • 6
  • 66
  • 77
  • of course you would need to change `LinearGradient` constructor params to get the desired gradient – pskink Nov 03 '16 at 09:22
  • I like this better than the one I was able to come up with, on principle. But is there also a performance benefit due to not having to create an off-screen `Canvas`? – Cactus Nov 03 '16 at 12:25
  • 1
    i think it is: not only in CPU clocks but also by eaten memory connected with the creation of off-screen `Bitmap`, btw you could do what you did with `Canvas#saveLayer` (without additional `Bitmap` creation) but still i think `ComposeShader` is closer to the low-level GPU stuff – pskink Nov 03 '16 at 15:57
  • Is it possible to combine this code with Picasse / Glide? Can you plase make an example :-)? – KayD Dec 24 '19 at 21:44
6

Based on this answer about masking, I was able to do this using a secondary off-screen canvas:

Bitmap backing = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
{
    Canvas offscreen = new Canvas(backing);
    offscreen.drawBitmap(bitmap, 0, 0, null);
    Paint paint = new Paint();
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    paint.setShader(new LinearGradient(0, 0, 100, 0, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP));
    offscreen.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);
}
canvas.drawBitmap(backing, 0, 0, null);
Community
  • 1
  • 1
Cactus
  • 27,075
  • 9
  • 69
  • 149