Android绘图机制与处理技巧——Android图像处理之画笔特效处理

除了常用的画笔属性,比如普通的画笔(Paint),带边框、填充的style,颜色(Color),宽度(StrokeWidth),抗锯齿(ANTI_ALIAS_FLAG)等,Android还提供了各种各样专业的画笔工具,如记号笔、毛笔、蜡笔等,使用它们可以实现更加丰富的效果。

PorterDuffXfermode

下图中列举了16种PorterDuffXfermode,有点像数学中集合的交集、并集这样的概念,它控制的是两个图像间的混合显示模式。


7A323AB4-C43C-4313-B964-5934C81EE7F0.png

PorterDuffXfermode设置的是两个图层交集区域的显示方式,dst是先画的图形,而src是后画的图形。

这些模式也不是经常使用,用的最多的是,使用一张图片作为另一张图片的遮罩层,通过控制遮罩层的图形,来控制下面被遮罩图形的显示效果。其中最常用的就是通过DST_IN、SRC_IN模式来实现将一个矩形图片变成圆角图片或者圆形图片的效果。

  • 圆角矩形

要使用PorterDuffXfermode非常简单,只需要让画面拥有这个属性就可以了。比如要实现下面圆角矩形的效果:

device-2017-04-01-131542.png

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

    mPaint=new Paint();

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.iu);

    mBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);

    Canvas canvas=new Canvas(mBitmap);

    mPaint.setAntiAlias(true);

    canvas.drawRoundRect(0,0,bitmap.getWidth(),bitmap.getHeight(),80,80,mPaint);

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

    canvas.drawBitmap(bitmap,0,0,mPaint);

    mImageView= (ImageView) findViewById(R.id.img);
    mImageView.setImageBitmap(mBitmap);
  • 刮刮卡效果

若要实现刮刮卡效果(刮刮卡一般有两个图层,即上面的用来被刮掉的图层和下面隐藏的图层)。在初始状态下,上面的图层会将下面整个图层覆盖,当你用手刮上面的图层的时候,下面的图层会慢慢显示出来,类似于画图工具中的橡皮擦效果。

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

     mPaint = new Paint();
     /**
     * 将画笔的透明度设置为0,这样才能显示出擦除效果。
     * 在使用PorterDuffXfermode进行图层混合时,并不是简单地只进行图层的计算,同时也会计算透明通道的值。
     * 正是由于混合了透明通道,才形成了这样的效果。
     */
    mPaint.setAlpha(0);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    mPaint.setStyle(Paint.Style.STROKE);
    //使Paint的笔触更加圆滑一点
    mPaint.setStrokeJoin(Paint.Join.ROUND);
    mPaint.setStrokeWidth(50);
    //使Paint的连接处更加圆滑一点
    mPaint.setStrokeCap(Paint.Cap.ROUND);

    mPath = new Path();
    mBgBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
    mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);

    mCanvas = new Canvas(mFgBitmap);
    mCanvas.drawColor(Color.GRAY);

获取用户手指滑动所产生的路径并将使用DST_IN模式将路径绘制到前面覆盖的图层上,代码如下所示。使用Path保存用户手指划过的痕迹。当然,如果使用贝塞尔曲线来做优化则会得到更好的显示效果,这里为了简化功能,就不使用贝塞尔曲线了。

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mPath.reset();
            mPath.moveTo(event.getX(), event.getY());
            break;
        case MotionEvent.ACTION_MOVE:
            mPath.lineTo(event.getX(), event.getY());
            break;
    }
    //使用DST_IN模式将路径绘制到前面覆盖的图层上
    mCanvas.drawPath(mPath, mPaint);
    invalidate();
    return true;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(600,400);
}

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

效果图如下所示:

4月-01-2017 13-53-23.gif

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

Shader

Shader又被称之为着色器、渲染器,它用来实现一系列的渐变、渲染效果。Android中的Shader包括以下几种。

BitmapShader——位图Shader
LinearGradient——线性Shader
RadialGradient——光束Shader
SweepGradient——梯度Shader
ComposeGradient——混合Shader

  • BitmapShader

与其他的Shader所产生的渐变不同,BitmapShader产生的是一个图像,这有点像Photoshop中的图像填充渐变。它的作用就是通过Paint对画布进行指定Bitmap的填充,填充时有以下几种模式可以选择。

CLAMP拉伸——拉伸的是图片最后的那一个像素,不断重复
REPEAT重复——横向、纵向不断重复
MIRROR镜像——横向不断翻转重复,纵向不断翻转重复
这里最常用的就是CLAMP拉伸模式,虽然它会拉伸最后一个像素,但是只要将图像设置为一定的大小,就可以避免这种拉伸。下面将一个矩形的图片变成一张圆形的图片,效果如下:

代码如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
    BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    Paint paint = new Paint();
    //用一张图片创建一支具有图像填充功能的画笔,并使用这只画笔绘制一个圆形
    paint.setShader(bitmapShader);
    canvas.drawCircle(500, 250, 200, paint);
}

如果把TileMode改为REPEAT,并将Bitmap改为较小的ic_launcher图标,就可以看到几种模式的区别了,代码如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
    Paint paint = new Paint();
    paint.setShader(bitmapShader);
    canvas.drawCircle(500, 250, 200, paint);
}

运行程序,效果图如下:

  • LinearGradient

要使用LinearGradient非常简单,只需要指定渐变的起始颜色即可,代码如下:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint();
    paint.setShader(new LinearGradient(0, 0, 400, 400, Color.BLUE, Color.YELLOW, Shader.TileMode.REPEAT));
    canvas.drawRect(0, 0, 400, 400, paint);
}

效果图如下图,他是一个从(0, 0)到(400, 400)的由蓝色到黄色的渐变效果。

LinearGradient方法参数中的TileMode与在BitmapShader中的含义基本相同,这里将绘制矩形的大小设置为与渐变图像大小相同,所以没有看见REPEAT的效果。如果将图形扩大,REPEAT的效果就出来了,如下图所示:

倒影效果

这些渐变效果通常不会直接使用在程序里。通常情况下,把这种渐变效果作为一个遮罩层来使用,同时结合PorterDuffXfermode。这样处理后,遮罩层就不再是一个生硬的图形,而是一个具有渐变效果的图层。这样处理的效果会更加柔和、更加自然。下面用LinearGradient和PorterDuffXfermode来创建一个具有倒影效果的图片,效果图如下:

代码如下:


    public class ReflectView extends View {

    private Bitmap mSrcBitmap, mRefBitmap;
    private Paint mPaint;
    private PorterDuffXfermode mXfermode;

    public ReflectView(Context context) {
        super(context);
        initRes(context);
    }

    public ReflectView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initRes(context);
    }

    public ReflectView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initRes(context);
    }

    private void initRes(Context context) {
        mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
        Matrix matrix = new Matrix();
        //实现图片的垂直翻转,避免使用旋转变换的复杂计算
        matrix.setScale(1, -1);
        mRefBitmap = Bitmap.createBitmap(mSrcBitmap, 0, 0, mSrcBitmap.getWidth(),
                mSrcBitmap.getHeight(), matrix, true);

        mPaint = new Paint();
        mPaint.setShader(new LinearGradient(0, mSrcBitmap.getHeight(), 0,
                mSrcBitmap.getHeight() + mSrcBitmap.getHeight() / 2, 0XDD000000, 0X10000000,
                Shader.TileMode.CLAMP));
        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLACK);
        //绘制原图
        canvas.drawBitmap(mSrcBitmap, 0, 0, null);
        //绘制倒影图
        canvas.drawBitmap(mRefBitmap, 0, mSrcBitmap.getHeight(), null);
        mPaint.setXfermode(mXfermode);
        //绘制渐变效果矩形
        canvas.drawRect(0, mSrcBitmap.getHeight(), mSrcBitmap.getWidth(), mSrcBitmap.getHeight() * 2, mPaint);
        mPaint.setXfermode(null);
    }
}

PathEffect

PathEffect就是指用用各种笔触效果来绘制路径。Android系统提供了如下中展示的几种绘制PathEffect的方式,从上到下依次是:


59E4185D-8D67-4F98-AF65-D8F33EBF2E0D.png

没效果
CornerPathEffect——就是将拐角处变得圆滑,具体圆滑的程度,则由参数决定
DiscretePathEffect——使用这个效果之后,线段上就会产生许多杂点
DashPathEffect——这个效果可以用来绘制虚线,用一个数组来设置各个点之间的间隔。此后绘制虚线时就重复这样的间隔进行绘制,另一个参数phase则用来控制绘制时数组的一个偏移量,通常可以通过设置值来实现路径的动态效果。
PathDashPathEffect——这个效果与DashPathEffect类似,只不过它的功能更加强大,可以设置显示点的图形,即方形点的虚线、圆形点的虚线。
ComposePathEffect——通过ComposePathEffect来组合PathEffect,就是将任意两种路径特性组合起来形成一个新的效果。
上图实现代码如下:


public class PathEffectView extends View {

    private Paint mPaint;
    private Path mPath;
    private PathEffect[] mEffects;

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

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

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

    private void init() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.DKGRAY);

        //使用随机数来生成一些随机的点形成一条路径
        mPath = new Path();
        mPath.moveTo(0, 0);
        for (int i = 0; i < 30; i++) {
            mPath.lineTo(i * 35, (float) (Math.random() * 100));
        }

        mEffects = new PathEffect[6];
        mEffects[0] = null;
        mEffects[1] = new CornerPathEffect(30);
        mEffects[2] = new DiscretePathEffect(3.0f, 5.0f);
        mEffects[3] = new DashPathEffect(new float[]{20, 10, 5, 10}, 0);
        Path path = new Path();
        path.addRect(0, 0, 8, 8, Path.Direction.CCW);
        mEffects[4] = new PathDashPathEffect(path, 12, 0, PathDashPathEffect.Style.ROTATE);
        mEffects[5] = new ComposePathEffect(mEffects[3], mEffects[1]);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mEffects.length; i++) {
            mPaint.setPathEffect(mEffects[i]);
            canvas.drawPath(mPath, mPaint);
            canvas.translate(0, 200);
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容