Android进阶——高级UI必知必会之2D绘制与使用Paint对图形进行渲染和滤镜混合处理(二)

引言

前一篇文章Android进阶——自定义View必知必会之Android坐标系与Paint的基础应用(一)详细总结了下关于Android坐标系和Paint的基本应用的相关知识,不过Paint可不仅仅只有哪些简单的用法,这篇好好总结下Pain的高级应用,相关文章链接如下:

一、Shader系渲染

所谓渲染就是对于我们绘制区域按照约定的渲染规则进行色彩的填充

1、Shader

Canvas的drawXxxx系方法是绘制具体的形状的,Shader是定义图形具体的着色外观即所谓的渲染,而渲染是通过给Paint设置Shader来实现渲染的Shader着色器可以在绘制时返回对应的颜色信息,Paint的Shader定义的就是图形的着色外观和拉伸模式TileMode(在图片和显示区域大小不符的情况进行扩充渲染):

  • Shader.TileMode.CLAMP——拉伸最后一个像素铺满
  • Shader.TileMode.MIRROR——横向纵向不足时不断翻转,镜像平铺
  • Shader.TileMode.REPEAT——类似电脑壁纸,横向纵向不足时重复放置

Android 默认提供了五种Shader的子类:BitmapShader图形渲染、LinearGradient线性渲染、SweepGradient渐变渲染(梯度渲染)、RadialGradient环形渲染和ComposeShader组合渲染

2、BitmapShader图形渲染

用BitMap对绘制的图形进行渲染着色,即用图片对图形进行贴图

/**
 * @author : Crazy.Mo
 */
public class BitmapShaderView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;
    private int mWidth;
    private int mHeight;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context) {
        this(context,null);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.mn)).getBitmap();
        mPaint = new Paint();
        mWidth = mBitMap.getWidth();
        mHeight = mBitMap.getHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        /**
         * 位图渲染,BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)
         * Bitmap:构造shader使用的bitmap
         * tileX:X轴方向的TileMode
         * tileY:Y轴方向的TileMode
         */
        BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.REPEAT,
                Shader.TileMode.REPEAT);
                        //设置像素矩阵,来调整大小,为了解决宽高不一致的问题。
        float scale = Math.max(mWidth, mHeight) / Math.min(mWidth, mHeight);
        Matrix matrix = new Matrix();
        matrix.setScale(scale, scale);
        bitMapShader.setLocalMatrix(matrix);
        mPaint.setShader(bitMapShader);
        mPaint.setAntiAlias(true);
        mPaint.setShader(bitMapShader);
        mPaint.setAntiAlias(true);
        //绘制图形的矩形区域
        canvas.drawRect(new Rect(0, 0, 1000, 1000), mPaint);
    }
}

在这里插入图片描述
通过ShapeDrawable也可以实现圆图

canvas.drawCircle(200, 300, 200, mPaint);
//通过shapeDrawable也可以实现  圆图(如:头像)
ShapeDrawable shapeDrawble = new ShapeDrawable(new OvalShape());
shapeDrawble.getPaint().setShader(bitMapShader);
shapeDrawble.setBounds(0,0,mWidth,mWidth);
shapeDrawble.draw(canvas);

在这里插入图片描述

3、LinearGradient线性渲染

用于图形元素的填充或描边。

/**
 * @author : Crazy.Mo
 */
public class LinearGradientView extends View {
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private int[] mColors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context) {
        this(context,null);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mPaint = new Paint();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**线性渲染
         * x0, y0, 起始点
         *  x1, y1, 结束点
         * int[]  mColors, 中间依次要出现的几个颜色
         * float[] positions 位置数组,position的取值范围[0,1],作用是指定几个颜色分别放置在那个位置上,
         * 如果传null,渐变就线性变化。
         *    tile 用于指定控件区域大于指定的渐变区域时,空白区域的颜色填充方法
         */
		LinearGradient linearGradient = new LinearGradient( 0, 0,800, 800,
                mColors, null, Shader.TileMode.CLAMP);
        // linearGradient = new LinearGradient(0, 0, 400, 400, mColors, null, Shader.TileMode.REPEAT);
		mPaint.setShader(linearGradient);
		canvas.drawRect(0, 0, 800, 800, mPaint);
    }
}

在这里插入图片描述

4、SweepGradient渐变渲染(梯度渲染)

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 扫描渲染
         * cx,cy 渐变中心坐标
         * color0,color1:渐变开始结束颜色
         * colors,positions:类似LinearGradient,当positions为null时,根据颜色线性渐变
         */
        SweepGradient swepGradient = new SweepGradient(400, 400,
                mColors, null);
		mPaint.setShader(swepGradient);
		canvas.drawCircle(400, 400, 300, mPaint);
    }

在这里插入图片描述

5、RadialGradient环形渲染

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 环形渲染
         * centerX ,centerY:shader的中心坐标,开始渐变的坐标
         * radius:渐变的半径
         * centerColor,edgeColor:中心点渐变颜色,边界的渐变颜色
         * colors:渐变颜色数组
         * stops:渐变位置数组,类似扫描渐变的positions数组,取值[0,1],中心点为0,半径到达位置为1.0f
         * tileMode:shader未覆盖以外的填充模式
         */
        RadialGradient radialGradient = new RadialGradient(300, 300, 100,
         mColors, null, Shader.TileMode.REPEAT);
		mPaint.setShader(radialGradient);
		canvas.drawCircle(300, 300, 300, mPaint);
    }

在这里插入图片描述

6、ComposeShader组合渲染

Bitmap mBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.heart)).getBitmap();
BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
LinearGradient linearGradient = new LinearGradient(0, 0, mWidth, mHeight,
     mColors,null, Shader.TileMode.CLAMP);
//bitmapShader对应目标像素,linearGradient对应源像素,像素颜色混合采用MULTIPLY模式
ComposeShader composeShader = new ComposeShader(linearGradient, bitmapShader, PorterDuff.Mode.MULTIPLY);
//         ComposeShader composeShader2 = new ComposeShader(composeShader, linearGradient, PorterDuff.Mode.MULTIPLY);
//将组合的composeShader作为画笔paint绘图所使用的shader
mPaint.setShader(composeShader);
//用composeShader绘制矩形区域
canvas.drawRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), mPaint);

在这里插入图片描述

二、对图片进行滤镜处理

所谓滤镜其实只不过是对于原本图像色彩进行调整,因为一张图片本身就是由一个个像素点构成的。

1、颜色模式

是将某种颜色表现为数字形式的模型或者说是一种记录图像颜色的方式,可分为:RGB模式CMYK模式、HSB模式、Lab颜色模式、位图模式、灰度模式、索引颜色模式、双色调模式和多通道模式,其中RGB分别为红、绿、蓝CMYK分别为青色、洋红、黄色、黑色

2、像素点的颜色通道

保存图像颜色信息的通道称为颜色通道每个图像都有一个或多个颜色通道,而图像中默认的颜色通道数取决于其颜色模式,即一个图像的颜色模式将决定其颜色通道的数量,ARGB模式中一个像素的颜色是由四个分量组成:

  • A为透明度通道,取值在0—1f
  • R为红色通道——取值在0—255f
  • G为绿色通道——取值在0—255f
  • B为蓝色通道——取值在0—255f

如果某个像素点的通道值越大则改像素点所占颜色比例越多。比如A透明通道A值越小就越透明,A为0就完全透明,A为1f就是完全不透明;而R红色通道值所占比例

而CMYK图像默认有4个通道(分别为青色、洋红、黄色、黑色);位图模式、灰度、双色调和索引颜色图像只有一个通道;RGB和Lab图像有3个通道。

3、颜色矩阵

Android中可以通过颜色矩阵 ColorMatrix 方便的操作颜色(当然也可以不通过颜色矩阵先拿到图片中每个像素点信息并修改其对应的ARGB值实现,本质上颜色矩阵也是这样实现的),可以用来方面的修改图片中RGBA各分量的值,一般是以一个5x4 的矩阵(又称为5x4行列式)表示:
在这里插入图片描述
直接改变颜色矩阵实现不同滤镜效果:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //关闭单个View的硬件加速功
        paint.reset();
        // 平移运算,只改变偏移量
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1, 0,0,0,0,
//                0,1,0,0,100,
//                0,0,1,0,0,
//                0,0,0,1,0,
//        });

        // 反相效果 -- 底片效果
//       ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                -1, 0,0,0,255,
//                0,-1,0,0,255,
//                0,0,-1,0,255,
//                0,0,0,1,0,
//        });
        // 缩放运算---乘法 -- 颜色增强
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1.2f, 0,0,0,0,
//                0,1.2f,0,0,0,
//                0,0,1.2f,0,0,
//                0,0,0,1.2f,0,
//        });

         /** 黑白照片
          *是将我们的三通道变为单通道的灰度模式
          *去色原理:只要把R G B 三通道的色彩信息设置成一样,那么图像就会变成灰色,
          *同时为了保证图像亮度不变,同一个通道里的R+G+B =1
          */
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                0.213f, 0.715f,0.072f,0,0,
//                0.213f, 0.715f,0.072f,0,0,
//                0.213f, 0.715f,0.072f,0,0,
//                0,0,0,1,0,
//        });

        

        // 发色效果---(比如红色和绿色交换)
        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                1,0,0,0,0,
                0, 0,1,0,0,
                0,1,0,0,0,
                0,0,0,0.5F,0,
        });
        // 复古效果
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1/2f,1/2f,1/2f,0,0,
//                1/3f, 1/3f,1/3f,0,0,
//                1/4f,1/4f,1/4f,0,0,
//                0,0,0,1,0,
//        });
        /**
         *  颜色通道过滤需要两个矩阵
         * 本身颜色矩阵A*过滤矩阵C=最终效果
         */
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1.3F,0,0,0,0,
//                0,1.3F,0,0,0,
//                0,0,1.3F,0,0,
//                0,0,0,1,0,
//        });
        RectF rectF = new RectF(200,100,bitmap.getWidth()+200,bitmap.getHeight());
        paint.setColorFilter(new ColorMatrixColorFilter(colorMartrix));
        canvas.drawBitmap(bitmap,null, rectF,paint);
    }

在这里插入图片描述

4、setMaskFilter设置滤镜

  • BlurMaskFilter模糊遮罩滤镜
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //关闭单个View的硬件加速功
       setLayerType(View.LAYER_TYPE_SOFTWARE,null);
        RectF rectF = new RectF(200,100,bitmap.getWidth()+200,bitmap.getHeight());
        paint.reset();
        paint.setColor(Color.BLUE);
        //画布的平移
        //canvas.translate(200,0);
        /**
         * @param radius 阴影的半径
         * @param style  NORMOL -- 整个图像都被模糊掉
         *               SOLID -- 图像边界外产生一层与Paint颜色一致阴影效果,不影响图像的本身
         *               OUTER -- 图像边界外产生一层阴影,并且将图像变成透明效果
         *               INNER -- 在图像内部边沿产生模糊效果
         * @return
         */
        paint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.INNER));
        canvas.drawRect(rectF,paint);
}
  • EmbossMaskFilter浮雕遮罩滤镜,绘制的图像感觉像是从屏幕中“凸”起来更有立体感一样(在PS设计软件中类似的效果称之为斜面浮雕)
        /**
         * @param direction  指定光源的位置,长度为xxx的数组标量[x,y,z]
         * @param ambient    环境光的因子 (0~1),越接近0,环境光越暗
         * @param specular   镜面反射系数 越接近0,镜面反射越强
         * @param blurRadius 模糊半径 值越大,模糊效果越明显
         */
        paint.setMaskFilter(new EmbossMaskFilter(new float[]{1,1,1},0.2f,60,80));
        canvas.drawRect(rectF,paint);

在这里插入图片描述

如果应用启用了硬件加速,看不到任何阴影效果(BlurMaskFilter,EmbossMaskFilter都需关闭硬件加速)所以现在用得比较少了。

三、使用Xfermode完成图片混合处理

使用Paint的时候可以通过Xfermode来完成图像组合的效果将绘制的图形的像素和Canvas上对应位置的像素按照一定的规则进行混合,形成新的像素,再更新到Canvas中形成最终的图形,即通过两张图的不同的组合模式,生成一张图(与PS的图层混合模式类似),

1、Xfermode模式

Xfermode主要参与角色分为:源图像目标图像,Android提供了18种不同的组合形式,具体参见PorterDuff.Mode

模式 说明
ADD 饱和相加,对图像饱和度进行相加,不常用
CLEAR 清除图像
DARKEN 变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
DST 只显示目标图像
DST_ATOP 在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
DST_IN 只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
DST_OUT 只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤
DST_OVER 将目标图像放在源图像上方
LIGHTEN 变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
MULTIPLY 正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
OVERLAY 叠加
SCREEN 滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖
SRC 只显示源图像
SRC_ATOP 在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响
SRC_IN 只在源图像和目标图像相交的地方绘制【源图像】
SRC_OUT 只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
SRC_OVER 将源图像放在目标图像上方
XOR 在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制

从上面模式名称可以把混合模式分为三个类型,在Xfermode中有五个关键元素:

  • alpha——透明度
  • C——颜色
  • src——原图
  • dst——目标图
  • out——输出

一个像素的颜色都是由四个分量ARGB组成,A表示的是Alpha值,RGB表示的是颜色,其中S表示的原像素,原像素的值用[Sa,Sc]表示,其中 Sa表示源像素的Alpha值,Sc表示源像素的颜色值。

1.1、SRC类——优先显示的是源图片

  • SRC [Sa, Sc] ——处理图片相交区域时,总是显示的是原图片

  • SRC_IN [Sa * Da, Sc * Da] ——处理图片相交区域时,受到目标图片的Alpha值影响当我们的目标图片为空白像素的时候,目标图片也会变成空白,即用目标图片的透明度来改变源图片的透明度和饱和度,当目标图片的透明度为0时,源图片就不会显示。

  • SRC_OUT [Sa * (1 - Da), Sc * (1 - Da)] —— 同SRC_IN类似 (1 - Da)
    用我们目标图片的透明度的补值来改变源图片的透明度和饱和度,当目标图片的透明度为不透明时,源图片就不会显示

  • SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc]——当透明度为100%和0%时,SRC_IN 和 SRC_ATOP是通用的当透明度不为上述的两个值时,SRC_ATOP 比 SRC_IN 源图像的饱和度会增

1.2、DST类——优先显示的是目标图片

DST_IN [Sa * Da, Sa * Dc] ----- 对比一下SRC_IN,正好和我们SRC_IN想法,在相交的时候以源图片的透明度来改变目标图片的透明度和饱和度
当源图片的透明度为0的时候,目标图片完全不显示

1.3、其他的叠加效果

MULTIPLY[Sa * Da, Sc * Dc] 等

2、Xfermode的简单使用

Xfermode的应用很简单只需要在Paint当中调用一句代码( paint.setXfermode(Mode),传入不同的Xfermode子类(PorterDuff.ModePorterDuffXfermodePixelXorXfermode、AvoidXfermode)子类,简单关注下PorterDuffXfermode 源码:

 public class PorterDuffXfermode extends Xfermode {
          /**
           * Create an xfermode that uses the specified porter-duff mode.
           * 用一个常量类标注了16种模式, 而在这里真正的模式是在内部美剧mode当中
           */
          public PorterDuffXfermode(PorterDuff.Mode mode) {
              porterDuffMode = mode.nativeInt;
          }
      }

应用Xfermode:

 Paint paint = new Paint();
 canvas.drawBitmap(destinationImage, 0, 0, paint);
 PorterDuff.Mode mode = PorterDuff.Mode.SRC_OUT// choose a mode
 paint.setXfermode(new PorterDuffXfermode(mode));
 canvas.drawBitmap(sourceImage, 0, 0, paint);

黄色圆形为目标图像、蓝色矩形为源图像。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CrazyMo_/article/details/102764981