您的位置:首页 > 移动开发 > Android开发

《Android群英传》图像处理之画笔特效处理--PorterDuffXfermode篇

2016-06-02 17:19 465 查看
不管是在我们的世界里,还是在Android的世界里,要想向神笔马良一样画出各种精彩绝伦的画,就必须得有一个前提–要有一支神奇的画笔。下面我们就来看看画笔的一些高级的一些高级属性,帮助我们实现更丰富的绘图效果。

PorterDuffXfermode

在学习这个东西之前,先来看看一张非常经典的图,出自 API Demo.



图中列举了16种PorterDuffXfermode,有点像数学中集合的交集、并集这样的概念,相信配合图例应该很好理解,它控制的是两个图象之间的混合显示模式。

这里要注意的是,PorterDuffXfermode设置的是两个图层交集区域的显示方式,dst 是先画的图形,而src 是后画的图形。当然,这些模式也不是经常使用的,用的最多的是,使用一张图片作为另一张图片的遮罩层,通过控制遮罩层的图形,来控制下面被遮罩图形的显示效果。其中最常用的就是通过DST_INSRC_IN 模式来实现将一个矩形图片变成圆角图片或者圆形图片的效果。

下面让我们来将一张图片处理成圆角图片,先上图:



我们先用一个普通画笔画一个圆角矩形遮罩层,再用带PorterDuffXfermode的画笔将图像画在遮罩层上,这样就可以通过上面所说的效果来混合两个图像了,完整的代码如下所示:

package com.ajun.canvas.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;

import com.ajun.canvas.R;

public class CircleCornerImageView extends View {
private Paint mPaint;
private Bitmap newBitmap;
private Bitmap srcBitmap;

public CircleCornerImageView(Context context) {
super(context);
init();
}

public CircleCornerImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public CircleCornerImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init() {
srcBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test1);
//创建一个和原图大小相同的图片
newBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(newBitmap);

mPaint = new Paint();
mPaint.setAntiAlias(true);

canvas.drawRoundRect(0,0, srcBitmap.getWidth(), srcBitmap.getHeight(),
30,30,mPaint);

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

canvas.drawBitmap(srcBitmap,0,0,mPaint);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(newBitmap,0,0,null);
}
}


下面我们再来看一个稍微复杂点的刮刮卡效果。我们都知道,刮刮卡一般有两个图层,即上面的用来刮掉的图层和下面隐藏的图层。在初始状态下,上面的图层会将下面整个图层覆盖,当你用手刮上面的图层的时候,下面的图层会慢慢显示出来,这也类似很多画图工具中的橡皮擦效果。这个效果可以使用PorterDuffXfermode来实现。

首先开始做一些初始化工作,准备好图片,设置好Paint的一些属性,代码如下所示:

mPaint = new Paint();
//设置Paint的颜色为透明
mPaint.setAlpha(0);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);//圆状
mPaint.setStrokeWidth(50f);
mPaint.setStrokeCap(Paint.Cap.ROUND);

//初始化 Path
mPath = new Path();

mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test1);

//创建一张和原图一样大小的图片
mNewBitmap = Bitmap.createBitmap(mSrcBitmap.getWidth(), mSrcBitmap.getHeight(), Bitmap.Config.ARGB_8888);

//设置上层图片颜色为灰色
mCanvas = new Canvas(mNewBitmap);
mCanvas.drawColor(Color.GRAY);


在上面的代码中,给 Paint 设置了一些属性,让它的笔触和连接外能更加圆滑一些,即Paint.Join.ROUNDPaint.Cap.ROUND属性。

接下来看一下如何获取用户手指滑动所产生的路径,代码如下所示。使用Path保存用户手指划过的路径。

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//清除Path设置的所有属性
mPath.reset();
//坐标的起始位置
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
//开始划路径
mPath.lineTo(event.getX(), event.getY());
break;
}


最后,只需要使用 DST_IN 模式将路径绘制到前面覆盖的图层上面即可。不过,还需要做关键的一步,那就是将画笔的透明度设置为0,这样才能显示出擦出后的效果。完整代码如下:

package com.ajun.canvas.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.ajun.canvas.R;

public class ScratchCardView extends View {
private Bitmap mSrcBitmap, mNewBitmap;
private Paint mPaint;
private Path mPath;
private Canvas mCanvas;

public ScratchCardView(Context context) {
super(context);
init();
}

public ScratchCardView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public ScratchCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

private void init() {
mPaint = new Paint();
//设置Paint的颜色为透明
mPaint.setAlpha(0);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);//圆状
mPaint.setStrokeWidth(50f);
mPaint.setStrokeCap(Paint.Cap.ROUND);

//初始化 Path
mPath = new Path();

mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test1);

mNewBitmap = Bitmap.createBitmap(mSrcBitmap.getWidth(), mSrcBitmap.getHeight(), Bitmap.Config.ARGB_8888);

mCanvas = new Canvas(mNewBitmap);
mCanvas.drawColor(Color.GRAY);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(mSrcBitmap, 0, 0, null);
canvas.drawBitmap(mNewBitmap, 0, 0, null);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//清除Path设置的所有属性
mPath.reset();
//坐标的起始位置
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
//开始划路径,(笔是透明的)
mPath.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(mPath, mPaint);//将划过的路径设置为透明
invalidate();//通知系统重绘
return true;
}
}


程序运行效果看下面gif图,当用户用手指滑动时,就会擦出上面的图层,形成刮刮卡的效果。

Tips:在使用PorterDuffXfermode时还有一点需要注意,那就是最好在绘图时,将硬件加速关闭,因为有些模式并不支持硬件加速。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 图像处理