1

I started my android app using png files for objects that required alpha, but I quickly realized that the space required was simply too much. So, I wrote a program that took a png with alpha and created a b&w alpha mask png file and a jpeg. This gives me a tremendous amount of space savings, but the speed is not great.

The following is the code in my Android app which combines the jpg image (the origImgId in the code) and the png mask (the alphaImgId in the code).

It works, but it's not fast. I already cache the result and I'm working on code which will load these images in the menu screen before the game starts, but it would be nice if there was a way to speed this up.

Does anyone have any suggestions? Note that I modified the code slightly so as to make it easily understandable. In the game this is actually a sprite which loads the image on demand and caches the result. Here you just see the code for loading the images and applying the alpha.

public class BitmapDrawableAlpha
{
    public BitmapDrawableAlpha(int origImgId, int alphaImgId) {
        this.origImgId = origImgId;
        this.alphaImgId = alphaImgId;
    }

    protected BitmapDrawable loadDrawable(Activity a) {
        Drawable d = a.getResources().getDrawable(origImgId);
        Drawable da = a.getResources().getDrawable(alphaImgId);

        Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(),d.getIntrinsicHeight(),Bitmap.Config.ARGB_8888);
        {
            Canvas c = new Canvas(b);
            d.setBounds(0,0,d.getIntrinsicWidth()-1,d.getIntrinsicHeight()-1);
            d.draw(c);
        }

        Bitmap ba = Bitmap.createBitmap(d.getIntrinsicWidth(),d.getIntrinsicHeight(),Bitmap.Config.ARGB_8888);
        {
            Canvas c = new Canvas(ba);
            da.setBounds(0,0,d.getIntrinsicWidth()-1,d.getIntrinsicHeight()-1);
            da.draw(c);
        }

        applyAlpha(b,ba);

        return new BitmapDrawable(b);
    }

    /** Apply alpha to the specified bitmap b. */
    public static void applyAlpha(Bitmap b, Bitmap bAlpha) {
        int w = b.getWidth();
        int h = b.getHeight();
        for(int y=0; y < h; ++y) {
            for(int x=0; x < w; ++x) {
                int pixel = b.getPixel(x,y);
                int finalPixel = Color.argb(Color.alpha(bAlpha.getPixel(x,y)), Color.red(pixel), Color.green(pixel), Color.blue(pixel));
                b.setPixel(x,y,finalPixel);
            }
        }
    }

    private int origImgId;
    private int alphaImgId;
}
HappyEngineer
  • 4,017
  • 9
  • 46
  • 60

1 Answers1

2

If you are going to be manipulating every multiple pixels you could call getPixels() and setPixels() to get them all at once. This will prevent additional method calls and memory references in your loop.

Another thing you could do is do the pixel addition with bitwise or instead of the helper methods. Preventing method calls should increase efficiency:

public static void applyAlpha(Bitmap b, Bitmap bAlpha) {
    int w = b.getWidth();
    int h = b.getHeight();
    int[] colorPixels = new int[w*h];
    int[] alphaPixels = new int[w*h];
    b.getPixels(colorPixels, 0, w, 0, 0, w, h);
    bAlpha.getPixels(alphaPixels, 0, w, 0, 0, w, h);
    for(int j = 0; j < colorPixels.length;j++){
        colorPixels[j] = alphaPixels[j] | (0x00FFFFFF & colorPixels[j]);
    }
    b.setPixels(colorPixels, 0, w, 0, 0, w, h);
}

That being said the process you are trying to undertake is fairly simple, I can't imagine these will provide a huge performance boost. From this point the only suggestion I can provide would be to goto a native implementation with the NDK.

EDIT: Also since a Bitmap doesn't have to be mutable to use getPixels() or getPixel() you can get the alpha bitmap with BitmapFactory.decodeResource():

Bitmap ba = BitmapFactory.decodeResource(a.getResources(), alphaImgId);
Fr33dan
  • 4,227
  • 3
  • 35
  • 62
  • Wow! I didn't expect to get such a perfect answer! I don't know which part of your answer made the difference, but the game levels start up perceptibly faster. I haven't timed the difference, but I'm thinking you took it from a 3 or 4 second startup down to a 1 or 2 second startup. Thanks! – HappyEngineer Sep 02 '12 at 06:46
  • 1
    Interesting, I was not expecting a major improvement so I'd be curious which part did the most. I think it's the use of `getPixels()` because it is implemented natively. That or possibly because the loop runs without any method calls the JIT compiler can perform more optimizations on it. If I get the time later I'll test it and try and figure it out. – Fr33dan Sep 02 '12 at 16:14
  • Well given there is definitely some wonkyness happening with the optimization I don't think I'll be able to determine which contributed to the change the most, but it spawned an interesting disscussion here: http://stackoverflow.com/questions/12240472/why-does-adding-an-if-statement-inside-a-loop-slow-it-down-so-drastically – Fr33dan Sep 03 '12 at 00:03
  • @Fr33dan May be you can answer me here. When I think about such solutions, I get the feeling "this should be already provided by framework". Reading the comment from here http://developer.android.com/reference/android/graphics/Bitmap.html#extractAlpha(), "... This may be drawn with Canvas.drawBitmap(), where the color(s) will be taken from the paint that is passed to the draw call." Instead of looping yourself to add alpha to bitmap, don't you think there should be a framework method to do this. May be image composition methods? which can take advantage of GPU where available? – auselen Sep 03 '12 at 19:18
  • @auselen I seriously doubt there is a framework method to manipulate a Bitmap like this. There may be a method for adding alpha to an image as it's drawn, but this would have to be done each time, caching the images with transparency would be faster. That being said I think it would be better form to simply leave the files in a format that supports transparency. In my experience PNG's are smaller than JPG, but I guess the OP had a different experience. `extractAlpha()` is used to to draw only the non transparent pixels a single color and would not be useful in this situation. – Fr33dan Sep 04 '12 at 00:10
  • Romain Guy from Google claims there are a couple ways to do it on the fly that use the GPU, so wouldn't be much of a hit if done every draw: http://corner.squareup.com/2013/01/transparent-jpegs.html#comment-772109252 Blending modes, BitmapShader etc.. – Lance Nanek Mar 09 '13 at 19:34