Search code examples
androidandroid-canvasandroid-bitmap

How to change color of a Bitmap that has anti-aliasing?


I have a drawable that represents a white circle with anti-aliasing that needs to be coloured in runtime.

Here's a scaled image of it:

enter image description here

As you can see, there are few semi-transparent pixels.

If I try to color them the fast way (which takes roughly 6-9 ms for 192x192 px drawable), I will have troubles with semi-transparent.

public static void changeBitmapColor(@NonNull Bitmap src, @ColorInt int newColor) {
    Paint paint = new Paint();
    ColorFilter filter = new PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_IN);
    paint.setColorFilter(filter);

    Canvas canvas = new Canvas(src);
    canvas.drawBitmap(src, 0, 0, paint);
}

Here's the drawable after being coloured by setting ColorFilter:

enter image description here

If I do it using brute-force algorithm, it takes roughly 100ms to go over all pixels and apply alpha parameter to a new color:

public static void changeBitmapColor2(@NonNull Bitmap src, @ColorInt int newColor) {
    int w = src.getWidth();
    int h = src.getHeight();

    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            int color = src.getPixel(x, y);

            int alpha = color >>> 24;
            if (alpha == 0) {
                continue;
            }
            color = (newColor & 0x00ffffff) | (alpha << 24);
            src.setPixel(x, y, color);
        }
    }
}

The resulting image of 2nd algorithm:

enter image description here

Is there anything I could do with 1st algorithm that it will result in a better quality colouring without sacrificing the performance?


Solution

  • Turns out, the 2nd algorithm had the right idea, but poor implementation. The key was to batch pixel retrieval and set, i.e. using pixel array:

    public static void changeBitmapColor2(@NonNull Bitmap src, @ColorInt int newColor) {
        int width = src.getWidth();
        int height = src.getHeight();
    
        int[] pixels = new int[height * width];
        src.getPixels(pixels, 0, width, 0, 0, width, height);
    
        int newColorNoAlpha = newColor & 0x00ffffff;
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                int currentPixel = i * width + j;
    
                int color = pixels[currentPixel];
    
                int alpha = color >>> 24;
                if (alpha == 0) {
                    continue;
                }
                pixels[currentPixel] = newColorNoAlpha | (alpha << 24);
            }
        }
        src.setPixels(pixels, 0, width, 0, 0, width, height);
    }
    

    This batching has reduced time to change color of 200x200 image from 100-120ms to 1-4 ms :)