在Android开发中,图像融合是一项常见且重要的技术,它能够将多张图像按照一定的规则组合成一张新的图像,广泛应用于图像处理、摄影后期、游戏开发等领域。普通项目中最常见的有二维码中间叠加logo、涂鸦、水印、广告图掏空部分显示出底下的内容等等。
图像融合基本概念:
图像融合是指将两个或多个图像按照一定的算法进行合成,生成一幅新的图像。在Android平台上,图像融合通常涉及到像素级别的操作,即根据一定的规则计算每个像素点的颜色值,从而得到融合后的图像。图像融合可以增强图像的视觉效果,提高图像的信息量,为用户提供更加丰富的视觉体验。
图像融合基本步骤:
1.加载所有待融合的图片资源均转成位图Bitmap
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.image2);
2.创建一个新的位图Bitmap对象作为画布Canvas,设置融合模式,并在画布上根据需求规则绘制各个需融合的位图
Bitmap resultBitmap = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), bitmap1.getConfig());
Canvas canvas = new Canvas(resultBitmap);
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
// 或者使用其他融合模式,如 PorterDuff.Mode.MULTIPLY
canvas.drawBitmap(bitmap1, 0, 0, null);
canvas.drawBitmap(bitmap2, 0, 0, paint); // 使用设置的融合模式绘制第二张图片
3.将融合后的位图进行显示或者保存到本地
//显示
imageView.setImageBitmap(resultBitmap);
//保存到本地
FileOutputStream out = new FileOutputStream(fusionFile);
// 压缩Bitmap到PNG格式并写入文件
resultBitmap.compress(Bitmap.CompressFormat.PNG, 60, out);
// 关闭输出流
out.flush();
out.close();
关于三种融合模式(Xfermode):
实践需求:
有一张大的半透底图bmp_bg和一张小的半透上层图bmp_top,现需要用小图覆盖大图的左上角部分,从而实现替换一些由第三方提供的不可控内容,由于都是半透图,直接叠加的话无法实现完全遮挡,所以需要将被覆盖的底图部分掏空成全透明的。以下是具体实现逻辑:
try {
Bitmap bmp_bg = BitmapFactory.decodeFile(bgFile.getAbsolutePath());
Bitmap bmp_top = BitmapFactory.decodeFile(topFile.getAbsolutePath());
//创建新位图做为画布
Bitmap resultBitmap = Bitmap.createBitmap(bmp_bg.getWidth(), bmp_bg.getHeight(), bmp_bg.getConfig());
Canvas canvas = new Canvas(resultBitmap);
canvas.drawBitmap(bmp_bg, 0, 0, null);
Paint transparentPaint = new Paint();
//根据小图大小,将底图左上角部分清空
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
Rect transparentArea = new Rect(0, 0,bmp_top.getWidth(), bmp_top.getHeight()); // x, y是区域左上角坐标,width和height是区域的宽和高
canvas.drawRect(transparentArea, transparentPaint);
//将小图进行叠加
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
canvas.drawBitmap(bmp_top, 0, 0, transparentPaint);
Log.i("charge", "==fusion end time=" + System.currentTimeMillis());
//加融合结果新图片保存到本地
if (!fusionFile.exists())
fusionFile.createNewFile();
// 创建文件输出流
FileOutputStream out = new FileOutputStream(fusionFile);
// 压缩Bitmap到PNG格式并写入文件
resultBitmap.compress(Bitmap.CompressFormat.PNG, 60, out);
// 关闭输出流
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
这样实现有个缺点,就是只能从左上角原点开始覆盖,如果我只想覆盖底图的底部区域就无法实现了。我们可以修改一下处理逻辑,只将地图要被覆盖的区域进行透明化处理就可以了,那么我们可用一张跟地图大小一致,上部分是全透明的,底部是目标覆盖内容的图来进行融合,判断出顶部图的非透明区域,再将底图对应区域透明化就可以了。以下是实现方法:
public static void fusion2Bitmap() {
File fusionFile = new File("/mnt/sdcard/fusion.png");
if (fusionFile.exists()) {
// Log.i("charge", "==fusion file has exist");
return;
}
File bgFile = new File("/mnt/sdcard/bg.png");
File topFile = new File("/mnt/sdcard/top.png");
if (!bgFile.exists() || !topFile.exists() || onFusion) {
// Log.i("charge", "==mask file not exist");
return;
}
onFusion = true;
new Thread(){
@Override
public void run() {
super.run();
try {
Bitmap bmp_bg = BitmapFactory.decodeFile(bgFile.getAbsolutePath());
Bitmap bmp_top = BitmapFactory.decodeFile(topFile.getAbsolutePath());
Log.i("charge", "==fusion start time=" + System.currentTimeMillis());
Bitmap resultBitmap = Bitmap.createBitmap(bmp_bg.getWidth(), bmp_bg.getHeight(), bmp_bg.getConfig());
Canvas canvas = new Canvas(resultBitmap);
canvas.drawBitmap(bmp_bg, 0, 0, null);
Paint transparentPaint = new Paint();
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
// 遍历上面图片的每个像素来确定非透明区域
for (int y = 0; y < bmp_top.getHeight(); y++) {
for (int x = 0; x < bmp_top.getWidth(); x++) {
int pixel = bmp_top.getPixel(x, y);
int alpha = Color.alpha(pixel);
if (alpha > 0) { // 如果像素不是完全透明
canvas.drawPoint(x, y, transparentPaint);
}
}
}
transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
canvas.drawBitmap(bmp_top, 0, 0, transparentPaint);
Log.i("charge", "==fusion end time=" + System.currentTimeMillis());
if (!fusionFile.exists())
fusionFile.createNewFile();
// 创建文件输出流
FileOutputStream out = new FileOutputStream(fusionFile);
// 压缩Bitmap到PNG格式并写入文件
resultBitmap.compress(Bitmap.CompressFormat.PNG, 60, out);
// 关闭输出流
out.flush();
out.close();
onFusion = false;
} catch (IOException e) {
Log.i("charge", "==save Error=" + e.toString());
e.printStackTrace();
}
Log.i("charge", "==save time=" + System.currentTimeMillis());
}
}.start();
}