這裏我是直接繼承的ImageView,主要是爲了能兼容之前的項目,因爲之前是直接用的ImageView。
思路
- 根據原圖片生成一個全馬賽克的圖片
- 監聽手勢,得到應該顯示的馬賽克方塊的集合
- 根據方塊的集合,刷新視圖,這裏用到了Paint的Xfermode(圖片混合模式)
- 手勢圖和全馬賽克圖混合,在相交處繪製馬賽克圖
- 將上一步的圖和原圖混合,在相交處繪製上一步的圖,在不相交處繪製原圖,搞定收工!
效果圖
圖片混合模式
下圖以黃圓爲dest,藍矩爲src,展示了各種圖片混合模式:
這裏以mosaicBitmap爲dest,以touchBitmap爲src,
設置DST_IN模式:在相交處取dest
canvas.drawBitmap(mosaicBitmap, 0, 0, paint);//dest
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));//設置DST_IN模式
canvas.drawBitmap(touchBitmap, 0, 0, paint);//src
源碼
package com.che.carcheck.support.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
/**
* 馬賽克視圖
* <p/>
* 作者:余天然 on 16/5/30 下午6:04
*/
public class MosaicView extends ImageView {
private Bitmap bitmap;//原圖
private Bitmap mosaicBitmap;//全馬賽克圖
private Bitmap mergeBitmap;//合成圖
private int strokeWidth;// 畫筆寬度px
private List<Rect> mosaicRects;//馬賽克集合
public static final int min_mosaic_block_size = 4;//馬賽克的最小粒度
private Paint paint;
public MosaicView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
mosaicRects = new ArrayList<>();
strokeWidth = 20;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int y = (int) event.getY();
//記錄應該顯示馬賽克的矩形集合
int radius = strokeWidth / 2;
int left = Math.max(x - radius, 0);
int right = Math.min(x + radius, bitmap.getWidth());
int top = Math.max(y - radius, 0);
int bottom = Math.min(y + radius, bitmap.getHeight());
Rect rect = new Rect(left, top, right, bottom);
mosaicRects.add(rect);
// FIXME: 16/5/30 這裏本來打算調用onDraw的,不知道setImageBitmap那裏怎麼出了問題
// invalidate();
updateMosaicList();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// doDraw(canvas);
}
private void updateMosaicList() {
Canvas canvas = new Canvas();
doDraw(canvas);
setImageBitmap(mergeBitmap);
}
private void doDraw(Canvas canvas) {
//手勢圖
Bitmap touchBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
canvas.setBitmap(touchBitmap);
for (Rect rect : mosaicRects) {
canvas.drawRect(rect, paint);
}
//合成圖
mergeBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
canvas.setBitmap(mergeBitmap);
canvas.drawBitmap(mosaicBitmap, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(touchBitmap, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
canvas.drawBitmap(bitmap, 0, 0, paint);
}
//生成全馬賽克的圖片
private Bitmap makeMosaic(Bitmap bitmap, Rect targetRect,
int blockSize) throws OutOfMemoryError {
if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0
|| bitmap.isRecycled()) {
throw new RuntimeException("bad bitmap to add mosaic");
}
if (blockSize < min_mosaic_block_size) {
blockSize = min_mosaic_block_size;
}
if (targetRect == null) {
targetRect = new Rect();
}
int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
if (targetRect.isEmpty()) {
targetRect.set(0, 0, bw, bh);
}
//
int rectW = targetRect.width();
int rectH = targetRect.height();
int[] bitmapPxs = new int[bw * bh];
// fetch bitmap pxs
bitmap.getPixels(bitmapPxs, 0, bw, 0, 0, bw, bh);
//
int rowCount = (int) Math.ceil((float) rectH / blockSize);
int columnCount = (int) Math.ceil((float) rectW / blockSize);
int maxX = bw;
int maxY = bh;
for (int r = 0; r < rowCount; r++) { // row loop
for (int c = 0; c < columnCount; c++) {// column loop
int startX = targetRect.left + c * blockSize + 1;
int startY = targetRect.top + r * blockSize + 1;
dimBlock(bitmapPxs, startX, startY, blockSize, maxX, maxY);
}
}
return Bitmap.createBitmap(bitmapPxs, bw, bh, Bitmap.Config.ARGB_8888);
}
//從塊內取樣,並放大,從而達到馬賽克的模糊效果
private static void dimBlock(int[] pxs, int startX, int startY,
int blockSize, int maxX, int maxY) {
int stopX = startX + blockSize - 1;
int stopY = startY + blockSize - 1;
if (stopX > maxX) {
stopX = maxX;
}
if (stopY > maxY) {
stopY = maxY;
}
//
int sampleColorX = startX + blockSize / 2;
int sampleColorY = startY + blockSize / 2;
//
if (sampleColorX > maxX) {
sampleColorX = maxX;
}
if (sampleColorY > maxY) {
sampleColorY = maxY;
}
int colorLinePosition = (sampleColorY - 1) * maxX;
int sampleColor = pxs[colorLinePosition + sampleColorX - 1];// 像素從1開始,但是數組層0開始
for (int y = startY; y <= stopY; y++) {
int p = (y - 1) * maxX;
for (int x = startX; x <= stopX; x++) {
// 像素從1開始,但是數組層0開始
pxs[p + x - 1] = sampleColor;
}
}
}
/*設置原圖*/
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
this.mosaicBitmap = makeMosaic(bitmap, null, strokeWidth);
updateMosaicList();
}
/*獲取合成圖*/
public Bitmap getMergeBitmap() {
return mergeBitmap;
}
/*恢復初始的原圖*/
public void reset() {
this.mosaicRects.clear();
updateMosaicList();
}
/*設置畫筆寬度*/
public void setStrokeWidth(int strokeWidth) {
this.strokeWidth = strokeWidth;
this.mosaicBitmap = makeMosaic(bitmap, null, strokeWidth);
updateMosaicList();
}
}