3

I have a PNG file with transparency that I'm using as OpenGL texture. I load it in Bitmap with BitmapFactory.decodeResource, then upload it to GPU.

The PNG file is quite big and in order to cut down on APK size, I'm trying to use two JPGs instead--one with RGB data, and the other with alpha channel (grayscale).

How do I combine the two JPGs together in one Bitmap object with alpha channel? I tried loading alpha channel as Bitmap.Config.ALPHA_8, then drawing them on top of each other using Canvas but no luck so far.

Pēteris Caune
  • 43,578
  • 6
  • 59
  • 81
  • Ah, I miss the days of a simple BitBlt :) – Basic Feb 24 '11 at 00:01
  • I don't now if https://tinypng.com/ is a solution for you... Sometimes is shrink your images with more than 60%. Greets – Dusty Feb 18 '14 at 15:49
  • Hi @Dusty, thanks for comment, today I would definitely give tinypng, pngquant and such a shot. Great thing about splitting PNG into two opaque JPGs though is, I can control compression/quality of the two layers individually and precisely. – Pēteris Caune Feb 19 '14 at 08:39

3 Answers3

5

Have a look at Kevin Dion's answer to this related question. He explains how to combine 4 separate images (R, G, B and A channels) but you should be able to adapt it to work with two images.

Community
  • 1
  • 1
Dan Dyer
  • 53,737
  • 19
  • 129
  • 165
  • Thanks, this helps. By shuffling things around a bit, I was able to produce my texture from a RGB bitmap and a PNG with just the alpha channel. I'd like alpha channel also to be stored in JPG, so one remaining issue--how to convert grayscale bitmap into ALPHA_8 bitmap. – Pēteris Caune Feb 24 '11 at 14:33
  • Posted a followup question about converting grayscale bitmap to alpha mask: http://stackoverflow.com/questions/5106122/how-to-convert-grayscale-bitmap-into-alpha-mask – Pēteris Caune Feb 24 '11 at 14:42
  • Found a solution for remaining grayscale->alpha problem: http://stackoverflow.com/questions/5106122/how-to-convert-grayscale-bitmap-into-alpha-mask/5121922#5121922 – Pēteris Caune Feb 25 '11 at 19:52
5

Here's a complete example:

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;

public class ImageOps {

    private static final ColorMatrix sRedToAlphaMatrix = new ColorMatrix(new float[] {
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        0, 0, 0, 0, 0,
        1, 0, 0, 0, 0});

    private static final ColorMatrixColorFilter sRedToAlphaFilter = new ColorMatrixColorFilter(sRedToAlphaMatrix);

    public static Bitmap composeAlpha(Bitmap target, Resources resources, int rgbDrawableId, int alphaDrawableId) {
        final BitmapFactory.Options options = new BitmapFactory.Options();          
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inScaled = false;       

        // Load RGB data
        Bitmap rgb = BitmapFactory.decodeResource(resources, rgbDrawableId, options);

        if (target == null) {
            // Prepare result Bitmap
            target = Bitmap.createBitmap(rgb.getWidth(), rgb.getHeight(), Bitmap.Config.ARGB_8888);
        }
        Canvas c = new Canvas(target);
        c.setDensity(Bitmap.DENSITY_NONE);

        // Draw RGB data on our result bitmap
        c.drawBitmap(rgb, 0, 0, null);

        // At this point, we don't need rgb data any more: discard!
        rgb.recycle();
        rgb = null;

        // Load Alpha data
        Bitmap alpha = BitmapFactory.decodeResource(resources, alphaDrawableId, options);

        // Draw alpha data on our result bitmap
        final Paint grayToAlpha = new Paint();
        grayToAlpha.setColorFilter(sRedToAlphaFilter);
        grayToAlpha.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
        c.drawBitmap(alpha, 0, 0, grayToAlpha); 

        // Don't need alpha data any more: discard!
        alpha.recycle();
        alpha = null;

        return target;
    }

}
Pēteris Caune
  • 43,578
  • 6
  • 59
  • 81
-1

Try the following: Iterate through width * height of your two images, and use Bitmap.getPixel(x,y) on each one.

int alpha = Color.red(grayscaleBitmap.getPixel(x, y)); // grayscale, so any color will do
int red = Color.red(colorBitmap.getPixel(x, y));
int green = Color.green(colorBitmap.getPixel(x, y));
int blue = Color.blue(colorBitmap.getPixel(x, y));
int mergedColor = Color.argb(alpha, red, green, blue);
// save mergedColor in an int[]

Then use Bitmap.createBitmap(int[] colors, int width, int height, Bitmap.Config config) to create your new bitmap.

Micah Hainline
  • 14,367
  • 9
  • 52
  • 85
  • Absolutely. :) CPU cheap would be the PNG. I don't know that it's particularly more expensive than anything else though. – Micah Hainline Feb 23 '11 at 23:59
  • This would indeed choke CPU quite a bit. I've got some 1024x1024 textures. – Pēteris Caune Feb 24 '11 at 14:34
  • I'm slightly annoyed about this now. getPixel should be O(1) as it's just an array indexing method. The double loop is still O(n) where n is the number of pixels. Do you guys really think there is going to be an algorithm better than O(n)? Just because you're calling some other higher-level transfer method doesn't mean you're not still iterating through the pixels. I'd rather hear "that wasn't fast enough when I tried it" than "I didn't try it, but I bet it's too slow to work for me". – Micah Hainline Feb 24 '11 at 14:55
  • Sorry Micah, I should have checked my assumptions, after all it's not that hard. So on my Nexus One, the Canvas approach takes ~450ms with 1024x1024 bitmap, the getPixel approach takes ~7000ms. I've read somewhere that Canvas operations can sometimes be performed on GPU, maybe that's a part of what's causing such dramatic speed difference. – Pēteris Caune Feb 25 '11 at 20:04
  • Also, forgot to mention, pixel-by-pixel aproach you suggested is way shorter and easier to understand. As such it might be a good fit for smaller graphics like 48x48 icons. – Pēteris Caune Feb 25 '11 at 20:11
  • Cool. Thanks for the followup. 7 secs is definitely worth the increased code complexity of the other method. – Micah Hainline Feb 25 '11 at 20:34
  • @Pēteris I'm pretty sure there isn't any GPU acceleration of Canvas stuff pre-Honeycomb. getPixel in a loop involves a lot of bouncing between VM and native code while doing a drawBitmap is going to make a single call (or at least a small constant number of calls) to native code for the entire operation. – Laurence Gonsalves May 01 '11 at 07:07
  • @Laurence Oh cool, then I'll definitely have to try compositing on GPU--might get significantly better performance than current 450ms – Pēteris Caune May 01 '11 at 18:42