如何将两个带有Alpha通道的不透明位图合并为一个?

3

我有一个带透明度的PNG文件,用作OpenGL纹理。我使用BitmapFactory.decodeResource将其加载到Bitmap中,然后上传到GPU。

PNG文件非常大,为了减小APK大小,我尝试使用两个JPG文件代替--一个包含RGB数据,另一个包含Alpha通道(灰度)。

如何在一个带Alpha通道的Bitmap对象中将这两个JPG文件合并在一起?我尝试将Alpha通道加载为Bitmap.Config.ALPHA_8,然后使用Canvas将它们绘制在一起,但目前没有成功。


啊,我怀念那些简单的 BitBlt 日子 :) - Basic
我不确定https://tinypng.com/是否适合您...有时可以将您的图像缩小超过60%。问候 - Dusty
嗨@Dusty,感谢您的评论,今天我一定会尝试使用tinypng、pngquant等工具。不过将PNG拆分为两个不透明的JPG的好处是,我可以单独和精确地控制两个图层的压缩/质量。 - Pēteris Caune
3个回答

5
这是一个完整的例子:
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;
    }

}

5
请查看Kevin Dion的回答,了解如何合并四张不同的图像(R、G、B和A通道),但你应该能够将其调整为适用于两张图像。此问题相关,请参考此链接

谢谢,这很有帮助。通过稍微调整一下,我能够从一个RGB位图和一个仅带Alpha通道的PNG中生成我的纹理。我也想将Alpha通道存储在JPG中,所以还有一个问题--如何将灰度位图转换为ALPHA_8位图。 - Pēteris Caune
发布了一个关于将灰度位图转换为Alpha掩码的后续问题:http://stackoverflow.com/questions/5106122/how-to-convert-grayscale-bitmap-into-alpha-mask - Pēteris Caune
找到了解决灰度转alpha问题的方法:http://stackoverflow.com/questions/5106122/how-to-convert-grayscale-bitmap-into-alpha-mask/5121922#5121922 - Pēteris Caune

-1
尝试以下操作:迭代您两个图像的宽度*高度,并在每个图像上使用Bitmap.getPixel(x,y)。
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[]

然后使用Bitmap.createBitmap(int[] colors, int width, int height, Bitmap.Config config)来创建您的新位图。


现在我有点烦恼。 getPixel应该是O(1),因为它只是一个数组索引方法。双重循环仍然是O(n),其中n是像素数。你们真的认为会有比O(n)更好的算法吗?仅仅因为你调用了一些其他高级传输方法,并不意味着你不再遍历像素。我宁愿听到“我尝试时速度不够快”而不是“我没有尝试,但我敢打赌它对我来说太慢了”。 - Micah Hainline
另外,我忘了提到,你建议的逐像素方法更短,更容易理解。因此,它可能非常适合像48x48图标这样的小型图形。 - Pēteris Caune
很好,感谢跟进。使用另一种方法增加代码复杂性的7秒绝对是值得的。 - Micah Hainline
@Pēteris 我非常确定在蜂巢版本之前没有任何 Canvas 相关的 GPU 加速。在循环中使用 getPixel 会涉及大量 VM 和本地代码之间的跳转,而使用 drawBitmap 则只需要进行一次调用(或至少进行一小部分常数次的调用)即可完成整个操作的本地代码。 - Laurence Gonsalves
@Laurence 哦,太酷了,那我一定要尝试在GPU上合成 - 可能会比当前的450毫秒获得显着更好的性能。 - Pēteris Caune
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接