不管是在我们的世界里,还是在Android的世界里,想要向神笔马良一样画出各种精彩绝伦的画,就必须得有一个前提------要有一支神奇的画笔。在前面的学习中,我们已经初步了解了一些常用的画笔属性,比如普通的画笔(Paint),带边框、填充的style,颜色(Color),宽度(StrokeWidth),抗锯齿(ANTI_ALLAS_FLAG)等,这些都是基本的画笔属性,就好像一个普通人所拥有的画笔工具。然而除此之外,还有各种各样专业的画笔工具,如记号笔、毛笔、蜡笔等,使用他们可以实现更加丰富的绘图效果。下面我们就来看看画笔的一些高级属性,帮助我们实现更丰富的绘图效果。

1.PorterDuffXfermode

在学习这个东西之前,先来看一张非常经典的图,出自API Demo,基本上所有讲PorterDuffXfermode的文章都会使用这张图做说明,图下图所示。

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

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

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

要使用PorterDuffXfermode非常简单,只需要让画面拥有这个属性就可以了,比如下面要实现的实例。如下图所示。

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

    private Bitmap handleImageRoundRect(Bitmap bm) {Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);Paint paint = new Paint();paint.setAntiAlias(true);//抗锯齿canvas.drawRoundRect(0,0,bm.getWidth(),bm.getHeight(),80,80,paint);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));canvas.drawBitmap(bm,0,0,paint);return  bitmap;}

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

首先需要做一些初始化工作,例如准备好图片,设置好Paint的一些特性,代码如下所示。

    private void init() {mPaint = new Paint();//关键的一步,这样才能显示出擦涂的效果。因为使用PorterDuffXfermode进// 行图层混合时,并不是简单的只进行图层的计算,同时也会计算透明通道的值。//正式由于混合了透明通道,才能形成了这样的效果。mPaint.setAlpha(0);mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));mPaint.setStyle(Paint.Style.STROKE);//以下代码时为了让笔触和连接处更加圆滑一点,即Paint.Join.ROUND和Paint.Cap.ROUND属性mPaint.setStrokeJoin(Paint.Join.ROUND);mPaint.setStrokeWidth(50);mPaint.setStrokeCap(Paint.Cap.ROUND);mPath = new Path();mBgBitmap = ((BitmapDrawable)getDrawable()).getBitmap();mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(),mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);mCanvas = new Canvas(mFgBitmap);mCanvas.drawColor(Color.GRAY);}

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

接下来,看以下如何获取用户手指滑动所产生的路径,代码如下所示。使用Path保存用户手指划过的路径。当然,这里如果使用贝塞尔曲线来做优化则会得到更好的显示效果,这里为了简化演示功能,就不适应贝塞尔曲线了。

    @Overridepublic 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;}mCanvas.drawPath(mPath,mPaint);invalidate();return true;}

最后,只需要使用DST_IN模式将路径绘制到前面覆盖的图层上面即可。不过还需要做最关键的一步,那就是将画笔的透明度设置为0,这样才能显示出擦除的效果。因为使用PorterDuffXfermode进行图层混合时,并不是简单的只进行图层的计算,同时也会计算透明通道的值。正式由于混合了透明通道,才能形成了这样的效果。完整代码如下所示。

public class XfermodeView extends android.support.v7.widget.AppCompatImageView {private Bitmap mBgBitmap;private Bitmap mFgBitmap;private Paint mPaint;private Canvas mCanvas;private Path mPath;public XfermodeView(Context context) {super(context);}public XfermodeView(Context context, AttributeSet attrs) {super(context, attrs);}public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();init();}@Overridepublic 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;}mCanvas.drawPath(mPath,mPaint);invalidate();return true;}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawBitmap(mBgBitmap,0,0,null);canvas.drawBitmap(mFgBitmap,0,0,null);}private void init() {mPaint = new Paint();//关键的一步,这样才能显示出擦涂的效果。因为使用PorterDuffXfermode进// 行图层混合时,并不是简单的只进行图层的计算,同时也会计算透明通道的值。//正式由于混合了透明通道,才能形成了这样的效果。mPaint.setAlpha(0);mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));mPaint.setStyle(Paint.Style.STROKE);//以下代码时为了让笔触和连接处更加圆滑一点,即Paint.Join.ROUND和Paint.Cap.ROUND属性mPaint.setStrokeJoin(Paint.Join.ROUND);mPaint.setStrokeWidth(50);mPaint.setStrokeCap(Paint.Cap.ROUND);mPath = new Path();mBgBitmap = ((BitmapDrawable)getDrawable()).getBitmap();mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(),mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);mCanvas = new Canvas(mFgBitmap);mCanvas.drawColor(Color.GRAY);}}

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

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

2.Shader

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

  • BitmapShader:位图Shader。
  • LinearGradient:线性Shader。
  • RadialGradient:光速Shader。
  • SweepGradient:梯度Shader。
  • ComposeShader:混合Shader。

除了第一个Shader以外,其他的Shader都比较正常,实现了名副其实的渐变、渲染效果。而与其他的Shader所产生的渐变不同,BitmapShader产生的是一个图像,这有点像Photoshop中的图像填充渐变。它的作用是通过Paint对画布进行指定Bitmap的填充,填充时有以下几种模式可以选择。

  • CLAMP拉伸------拉伸的是图片最后一个像素,不断重复
  • REPEAT重复------横向、纵向不对重复
  • MIRROR镜像------横向不断翻转重复,纵向不断翻转重复

这几种模式的含义都非常好理解,与字面意思基本相同。这里最常使用的就是CLAMP拉伸模式,虽然他们拉伸最后一个像素,但是只要将图像设置为一定的大小,就可以避免这种拉伸。下面来看一个跟上面有类似效果的例子,将一个矩形的图片变成一张圆形的图片。当然,通过绘制不同的图形,你也可以绘制出不同形状的图形。

程序非常简单,无非就是使用BitmapShader来进行图形填充,代码如下所示。

    private Bitmap handleImageCircle(Bitmap bm) {BitmapShader bitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);Paint paint = new Paint();paint.setShader(bitmapShader);Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawCircle(300, 250, 200, paint);return bitmap;}

通过以上的代码再运行程序,效果图如下所示。

相信通过这样一幅图,大家应该可以理解使用CLAMP拉伸模式来创建圆形图像的原因了。

在讲完BitmapShader这个比较特殊的Shader后,再来看看其他比较类似的Shader。

先来看一个最简单,也是最常用的Shader------LinearGradient。直译过来就是线性渐变,没错,它就是一个简单的线性渐变,与Photoshop里面的渐变效果类似。

要使用LinearGradient也非常简单,只需要指定渐变其实的颜色就可以了,代码如下所示。

        Paint paint = new Paint();paint.setShader(new LinearGradient(0,0,400,400, Color.BLUE, Color.YELLOW, Shader.TileMode.REPEAT));Bitmap bitmap = Bitmap.createBitmap(400,400, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawRect(0,0,400,400,paint);return bitmap;

通过以上代码,画出一个LinearGradient,效果如下图所示,他是一个从(0,0)到(400,400)的一个由蓝色到黄色的渐变效果。

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

其他几种渐变模式与LinearGradient基本相同,只是渐变的小时效果不同而已,这里就不做过多的介绍了。

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

要实现这个效果,首先需要把原图赋值一份并进行翻转,代码如下所示。

        Matrix matrix = new Matrix();matrix.setScale(1f,-1f);//实现图片的垂直翻转Bitmap bitmap = Bitmap.createBitmap(bm, 0,0,bm.getWidth(),bm.getHeight(),matrix,true);

需要注意的是,使用matrix.setScale(1f,-1f)方法来实现图片的垂直翻转,这是一个非常有用的技巧,避免了使用选择变换的复杂计算。其原理相信大家只要将值带入前面讲解图像变换矩阵的计算公式中,就知道为什么了。同理,还可以实现图像的水平翻转。

首先绘制两张图片即原图和倒影图,只是这个时候还未绘制渐变层,因此倒影图和原图的透明度相同。接下来,在倒影图上面绘制一个同样大小的渐变矩形,并通过Mode.DST_IN模式绘制到倒影图上,从而形成一个具有过渡效果的渐变层,完整代码如下所示。

public class InvertedImageView extends android.support.v7.widget.AppCompatImageView {private Paint mPaint;private Bitmap mSrcBitmap;private Bitmap mDstBitmap;private PorterDuffXfermode mXfermode;public InvertedImageView(Context context) {super(context);}public InvertedImageView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public InvertedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();init();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.BLACK);//背景canvas.drawBitmap(mSrcBitmap,0,0,null);//画上面图片canvas.drawBitmap(mDstBitmap,0,mSrcBitmap.getHeight(),null);//画下面图片mPaint.setXfermode(mXfermode);//将渐变和Xfermode设置到画笔中//绘制渐变效果矩形canvas.drawRect(0,mSrcBitmap.getHeight(),mDstBitmap.getWidth(),mSrcBitmap.getHeight()*2,mPaint);mPaint.setXfermode(null);}private void init() {mSrcBitmap = ((BitmapDrawable)getDrawable()).getBitmap();int height = mSrcBitmap.getHeight();Matrix matrix = new Matrix();matrix.setScale(1f,-1f);//实现图片的垂直翻转mDstBitmap = Bitmap.createBitmap(mSrcBitmap, 0,0,mSrcBitmap.getWidth(),mSrcBitmap.getHeight(),matrix,true);mPaint = new Paint();mPaint.setShader(new LinearGradient(0,height, 0, height+height/4, 0xDD000000, 0x1000000, Shader.TileMode.CLAMP));mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);}
}

下图是效果图。

3.PathEffect

要理解PathEffect,先来看一张比较直观的图,来了解下什么是PathEffect,如下图

  • PathEffect就是指,用各种笔触效果来绘制一个路径。Android系统提供了如上图中展示的几种绘制PathEffect的方式,从上到下一次是:没效果、CornerPathEffect、DiscretePathEffect、DashPathEffect、PathDashPathEffect、ComposePathEffect。
  • CornerPathEffec:非常好理解,就是将拐弯处变得圆滑,具体圆滑程度,则有参数决定。
  • DiscretePathEffect:使用这个效果后,线段上就会产生许多杂点。
  • DashPathEffect:显然,这个效果可以用来绘制虚线,用一个数组来设置各个点之间的间隔。此后会绘制虚线是就重复这样的间隔进行绘制,另一个参数phase则用来控制绘制时数组的一个偏移量,通常可以通过设置值来实现路径的动态效果。
  • PathDashPathEffect:这个效果与前面的DashPathEffect类似,只不过他的功能更加强大,可以设置显示点的图形,即方形点的虚线、圆形点的虚线。
  • ComposePathEffect:如果每次都只能实现一种路径的特效效果,那就显得太单调了。Android提供了一种更加灵活的方式------通过ComposePathEffect来组合PathEffect,这个方法的功能就是将任意两种路径特性组合起来形成一个新的效果。

有了以上的了解,来看看上图的效果时如何实现的。

首先,需要生产一个Path,这里使用随机数来生产一些随机的点,并形成一条路径,代码如下所示。

       mPath = new Path();mPath.moveTo(0,0);for (int i = 0; i < 30; i++) {mPath.lineTo(i*35, (float) (Math.random()*100));}

接下来,就可以再onDraw()方法中通过不同的PathEffect来绘制这些Path了,代码如下所示。

        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,10,PathDashPathEffect.Style.ROTATE);mEffects[5] = new ComposePathEffect(mEffects[3],mEffects[1]);for (int i = 0 ; i < mEffects.length; i++) {mPaint.setPathEffect(mEffects[i]);canvas.drawPath(mPath,mPaint);canvas.translate(0,200);}

每绘制一个Path,就将画布平移,从而让各种PathEffect一次绘制出来。

Android图像处理之画笔特效处理相关推荐

  1. android 特效绘图,Android绘图机制与处理技巧——Android图像处理之图形特效处理...

    Android变形矩阵--Matrix 对于图像的图形变换,Android系统是通过矩阵来进行处理的,每个像素点都表达了其坐标的X.Y信息.Android的图形变换矩阵是一个3x3的矩阵,如下图所示: ...

  2. Android图像处理之图形特效处理

    前面我们了解了关于图像色彩处理的相关技巧,下面继续来探讨图形图像方面的处理技巧. 1.Android变形矩阵------Matrix 对于图像的色彩处理,Android系统提供了ColorMatrix ...

  3. Android图像处理之色彩特效处理

    Android对于图片的处理,最常用到的数据结构时位图------bitmap,它包含了一张图片所有的数据,整个图片都是由点阵和颜色组成的,所谓点阵就是一个包含像素的矩阵,每一个元素对应着图片的一个像 ...

  4. Android图像处理整理

    Android图像处理整理 参考:http://blog.csdn.net/luzhenyuxfcy/article/details/49427781 我们常用的处理方式基本都是在对像素矩阵按照一定的 ...

  5. c语言给bmp图片加滤镜,图片编辑器PixelStyle: 图像处理,滤镜特效

    图片编辑器PixelStyle: 图像处理,滤镜特效 支持系统 OS X 10.8 价格 0 下载次数 590 官方网站 *不要错过"超级抠图",一键抠图工具,从此告别PS的蜗牛抠 ...

  6. android图像处理系列之五-- 给图片添加边框(中)

    前面一篇讲到给图片加边框的方式,只能给图片加一些有规则的边框,如果想加一些比较精美的效果,就有点麻烦了.下面就给出解决这个问题的思路. 思路是:一些比较精美的花边图片我们是很难用代码控制,就目前本人水 ...

  7. android 涂鸦之图片叠加,android图像处理系列之七--图片涂鸦,水印-图片叠加...

    图片涂鸦和水印其实是一个功能,实现的方式是一样的,就是一张大图片和一张小点图片叠加即可.前面在android图像处理系列之六--给图片添加边框(下)-图片叠加中也讲到了图片叠加,里面实现的原理是直接操 ...

  8. Android系统联系人全特效实现(下),字母表快速滚动

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9050671 在上一篇文章中,我和大家一起实现了类似于Android系统联系人的分组 ...

  9. Android系统联系人全特效实现(上),分组导航和挤压动画

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9033553 记得在我刚接触Android的时候对系统联系人中的特效很感兴趣,它会根 ...

最新文章

  1. 反转链表:输入一个链表的头结点,反转该链表并输出反转后的链表的头结点。...
  2. win10搜索计算机中所有excel,windows10系统下怎样使用excel进行高级筛选
  3. 开发自己的区块链基础功能篇
  4. Linux 命令详解(二)awk 命令
  5. 英语口语-文章朗读Week10 Monday
  6. c++面向对象高级编程 学习九 pointer-like classes
  7. LeetCode MySQL 1527. Patients With a Condition(like)
  8. Spring Boot学习总结(24)——Spring Boot 2.5 新特性一览
  9. MFC初探 —— 捕获键盘消息
  10. 输出华氏-摄氏温度转换表(15分)
  11. 计算机一级题库ps视频,计算机一级Photoshop题库及答案
  12. 利用Multipart上传文件报错:The field fileUpload exceeds its maximum permitted size of 1048576 bytes
  13. 建设工程施工合同系列一建工合同的范围(未完待续)
  14. Unable to find a single main class from the following candidates 问题解决
  15. 国家气象局天气预报接口
  16. 【一篇无聊的影评】吐槽《从你的全世界路过》
  17. Linux目录与管理
  18. system76_这就是System76打开硬件的方式
  19. 蜘蛛seo超级外链软件
  20. 德国罗氏Roehrs BT40-3010090、HSK A63-3009555

热门文章

  1. 基于扭曲的后门攻击——WANET – IMPERCEPTIBLE WARPING-BASED BACKDOOR ATTACK
  2. LAMP环境搭建与配置(二)
  3. Linux远程传输scp非默认端口
  4. 电商工具时代+电商思维时代
  5. 桌面计算机用法,计算机桌面影视制作:Adobe Premiere 5.1 使用技巧
  6. dump heap及分析内存泄漏
  7. [春秋云镜]CVE-2022-2073
  8. vs2019多行注释快捷键
  9. KNN算法原理及实现
  10. css实现华为充电动画