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

Posted 黄飞_hf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android绘图机制与处理技巧——Android图像处理之画笔特效处理相关的知识,希望对你有一定的参考价值。

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

PorterDuffXfermode

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

这里写图片描述

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

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

圆角矩形

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

这里写图片描述

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

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test1);
        mBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(mBitmap);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        canvas.drawRoundRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), 80, 80, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, 0, 0, paint);

刮刮卡效果

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

  • 首先要做一些初始化工作,例如准备好图片,设置好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 onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(mBgBitmap, 0, 0, null);
        canvas.drawBitmap(mFgBitmap, 0, 0, null);
    }

效果图如下所示:

这里写图片描述

在使用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的方式,从上到下依次是:

这里写图片描述

  • 没效果
  • 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);
        }
    }
}

代码地址

以上是关于Android绘图机制与处理技巧——Android图像处理之画笔特效处理的主要内容,如果未能解决你的问题,请参考以下文章

Android绘图机制与处理技巧

最新Android高级面试知识点--绘图机制

最新Android高级面试知识点--绘图机制

Android 高级UI解密 :Canvas裁剪 与 二维三维Camera几何变换(图层Layer原理)

Android 高级UI解密 :Canvas裁剪 与 二维三维Camera几何变换(图层Layer原理)

Android动画机制与使用技巧——Android 5.X SVG 矢量动画机制