博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Canvas&Paint 知识梳理(5) Paint#setShader
阅读量:7079 次
发布时间:2019-06-28

本文共 11745 字,大约阅读时间需要 39 分钟。

一、概述

Shader称为着色器,通过给Paint设置Shader,我们可以对图像进行渲染,在实际的使用当中,我们一般使用Shader的以下五个子类来实现不同的效果:

  • BitmapShader
  • LinearGradient
  • SweepGradient
  • RadialGradient
  • ComposeShader

其中第1个用来设置Bitmap的变换,第2~4用来设置颜色的变换,第5个用来组合上面的几个Shader,下面我们一起来看以下各个子类的使用和应用场景。

二、使用示例

2.1 BitmapShader

BitmapShader是所有五个子类当中唯一一个对Bitmap进行操作的,我们看一下它的构造函数:

/**     * Call this to create a new shader that will draw with a bitmap.     *     * @param bitmap            The bitmap to use inside the shader     * @param tileX             The tiling mode for x to draw the bitmap in.     * @param tileY             The tiling mode for y to draw the bitmap in.     */    public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY) {        mBitmap = bitmap;        mTileX = tileX;        mTileY = tileY;        init(nativeCreate(bitmap, tileX.nativeInt, tileY.nativeInt));    }复制代码

第一个参数很好理解,就是需要绘制的Bitmap,我们看一下后面的两个参数,它的取值有:

public enum TileMode {        /**         * replicate the edge color if the shader draws outside of its         * original bounds         */        CLAMP   (0),        /**         * repeat the shader's image horizontally and vertically         */        REPEAT  (1),        /**         * repeat the shader's image horizontally and vertically, alternating         * mirror images so that adjacent images always seam         */        MIRROR  (2);            TileMode(int nativeInt) {            this.nativeInt = nativeInt;        }        final int nativeInt;    }复制代码

需要注意的是,下面几种模式都是建立在绘制的区域要比原来的bimtap大的情况下的。

the shader draws outside of its original bounds复制代码
  • CLAMP:取bitmap边缘的最后一个像素进行扩展。
  • REPEAT:水平地重复整张bitmap
  • MIRROR:和REPEAT类似,但是每次重复的时候,将bitmap进行翻转。

2.1.1 CLAMP

首先,我们取一张宽高为200dp * 200dp的图片,我们整个View的宽高为300dp * 300dp

我们首先采用
CLAMP的模式:

private Bitmap mOriginalBitmap;    private Paint mPaint;    private void init() {        mOriginalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shader_pic);        mPaint = new Paint();    }    private void drawBitmapShader(Canvas canvas) {        BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);        mPaint.setShader(shader);        canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);    }复制代码

最终得到的结果为下图,可以看到,由于Paint绘制的宽高要比Bitmap原本的宽高大,因此对于多出的部分,取了边缘最后一个像素的颜色进行重复:

现在有个疑问,因为整个图片的大小为
600 * 600,而我们绘制的大小为
900 * 900,按前面的说法,对于
(600,0) - (899, 600)的区域,取的是
(599, 0) - (599, 599)这一列的颜色,而对于
(0, 600) - (600, 899)取的是
(0, 599) - (599, 599)这一行的颜色,那么
(600, 600) - (899, 899)这一区域是怎么取的呢? 现在,我们试一下,把最后
drawRect的起始点改为
(100, 100)

canvas.drawRect(100, 100, canvas.getWidth(), canvas.getHeight(), mPaint);复制代码

得到的效果如下图,可以看到,边缘部分被切割掉了。

2.1.2 REPEAT/MIRROR

对于这两种模式,实现方式和上面类似,我们就不再重复描述了,只给出下面运行的结果:

BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);复制代码

BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);复制代码

得到的结果都是和描述相符的。

2.1.3 当X轴和Y轴的TileMode不同时

上面讨论的情况,都是x轴和y轴的TileMode相同的情况,现在,我们来看一下,当两者不同时,会发生什么情况:

BitmapShader shader = new BitmapShader(mOriginalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);复制代码

最终的结果如下,可以看到,我们是先按x轴的模式进行处理,然后将x轴处理完毕后的图像再按y轴的模式进行处理,这也解释了我们前面在2.1.1中留下的疑问。

2.2 LinearGradient

LinearGradient用来处理线性渐变,同理我们先来看它的构造函数说明,和前面不同,它有两个构造函数,其中一种是另一种的简化版,我们直接来看复杂的一种:

/** Create a shader that draws a linear gradient along a line.        @param x0           The x-coordinate for the start of the gradient line        @param y0           The y-coordinate for the start of the gradient line        @param x1           The x-coordinate for the end of the gradient line        @param y1           The y-coordinate for the end of the gradient line        @param  colors      The colors to be distributed along the gradient line        @param  positions   May be null. The relative positions [0..1] of                            each corresponding color in the colors array. If this is null,                            the the colors are distributed evenly along the gradient line.        @param  tile        The Shader tiling mode    */    public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], TileMode tile) {复制代码

下面,我们从几个方面来分析一下这个构造函数中的参数。

2.2.1 起点坐标和终点坐标

对于这两个点的坐标我们可以这么理解,起点的颜色就是color[]数组的第一个元素,终点的颜色就是color[]数组的最后一个元素,这两个点的连线决定了线性变化的方向,如果两点连线和x轴的正方向是重合的时候,那么就是水平地变化,当和x轴正方向有度数时,那么这个连线相对于x轴旋转了多少,最后线性变化的图像也就会相对于水平变化的图像旋转了多少,下面我们用两个例子来说明。 首先是水平方向的:

private void drawLinearGradient(Canvas canvas) {        LinearGradient gradient = new LinearGradient(0, 0, 100, 0, new int[]{ Color.WHITE, Color.BLACK }, null, Shader.TileMode.REPEAT);        mPaint.setShader(gradient);        canvas.drawRect(0, 0, 900, 900, mPaint);    }复制代码

这时候的图像为:

下面,我们将终点的
y轴坐标下移一点,让起点坐标和终点坐标的连线,与
x轴形成一定的角度:

private void drawLinearGradient(Canvas canvas) {        LinearGradient gradient = new LinearGradient(0, 0, 100, 10, new int[]{ Color.WHITE, Color.BLACK }, null, Shader.TileMode.REPEAT);        mPaint.setShader(gradient);        canvas.drawRect(0, 0, 900, 900, mPaint);    }复制代码

这时候的图像为,可以看到,由于此时连线相对于x轴,顺时针旋转了一定的度数,那么最终的图像也相对于上面那种情况顺时针旋转了相应的度数。

2.2.2 colorspositions

这两个参数很好理解,因为在颜色由起点颜色变到终点颜色的过程中,我们可能还希望中间会经过别的颜色,那么这时候,我们就可以在数组的第一个和最后一个元素当中插入别的元素,这些元素就是中间会经过的颜色,并且当positions不为null的时候,colors的大小要和positions相同。

private void drawLinearGradient(Canvas canvas) {        LinearGradient gradient = new LinearGradient(0, 0, 100, 10, new int[]{ Color.WHITE, Color.BLUE, Color.BLACK }, new float[]{0, 0.5f, 1f}, Shader.TileMode.REPEAT);        mPaint.setShader(gradient);        canvas.drawRect(0, 0, 900, 900, mPaint);    }复制代码

结果为:

2.2.3 TileMode

BitmapShader不同,此时我们只用指定一个方向的变化,这个方向就是颜色线性变化对应的方向。

2.2.4 另一个构造函数

/** Create a shader that draws a linear gradient along a line.        @param x0       The x-coordinate for the start of the gradient line        @param y0       The y-coordinate for the start of the gradient line        @param x1       The x-coordinate for the end of the gradient line        @param y1       The y-coordinate for the end of the gradient line        @param  color0  The color at the start of the gradient line.        @param  color1  The color at the end of the gradient line.        @param  tile    The Shader tiling mode    */    public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,            TileMode tile) {        mType = TYPE_COLOR_START_AND_COLOR_END;        mX0 = x0;        mY0 = y0;        mX1 = x1;        mY1 = y1;        mColor0 = color0;        mColor1 = color1;        mTileMode = tile;        init(nativeCreate2(x0, y0, x1, y1, color0, color1, tile.nativeInt));    }复制代码

唯一不同的就是去掉了colorsposition数组,变成了color0color1,那么我们就只能指定起点和终点的颜色了,其它的原理和上面那个构造函数是相同的。

2.3 SweepGradient

它用来提供类似雷达的效果,同理,我们看一下构造函数:

/**     * A subclass of Shader that draws a sweep gradient around a center point.     *     * @param cx       The x-coordinate of the center     * @param cy       The y-coordinate of the center     * @param colors   The colors to be distributed between around the center.     *                 There must be at least 2 colors in the array.     * @param positions May be NULL. The relative position of     *                 each corresponding color in the colors array, beginning     *                 with 0 and ending with 1.0. If the values are not     *                 monotonic, the drawing may produce unexpected results.     *                 If positions is NULL, then the colors are automatically     *                 spaced evenly.     */    public SweepGradient(float cx, float cy, int colors[], float positions[]) 复制代码

2.3.1 中心点坐标(cx, cy)

对于(cx, cy)中心点的坐标,我们可以把它想象成一个时钟的指针,这个指针开始时指向3点钟方向,它初始的颜色就是起点颜色,那么它会以此为起点,顺时针旋转360度,在旋转的过程中,这个指针的颜色不断变化,当旋转到360度后,指针就变成了终点颜色,在旋转过程中,指针所形成的轨迹就是最终的图像。

2.3.2 TileMode

需要注意到,它和LinearGradient不同的是,由于指针是无限长的,所以形成的图像在x轴和y轴所拼接成的区域是无限大的,因此也就不存在了TileMode这个参数的必要了。

2.3.3 colors[]positions[]

这两个数组的作用和上面LinearGradient的两个数组的作用是相同的,这里就不重复说明了。

2.3.4 举例

下面举个简单的例子:

private void drawSweepGradient(Canvas canvas) {        SweepGradient gradient = new SweepGradient(450, 450, Color.WHITE, Color.BLACK);        mPaint.setShader(gradient);        canvas.drawRect(0, 0, 900, 900, mPaint);    }复制代码

最后的结果为:

2.3.5 另一个构造函数

/**     * A subclass of Shader that draws a sweep gradient around a center point.     *     * @param cx       The x-coordinate of the center     * @param cy       The y-coordinate of the center     * @param color0   The color to use at the start of the sweep     * @param color1   The color to use at the end of the sweep     */    public SweepGradient(float cx, float cy, int color0, int color1) {        mType = TYPE_COLOR_START_AND_COLOR_END;        mCx = cx;        mCy = cy;        mColor0 = color0;        mColor1 = color1;        init(nativeCreate2(cx, cy, color0, color1));    }复制代码

和前面LinearGradient中讨论的一样,color0color1就是colors[]positions[]的简化版本。

2.4 RadialGradient

它被称为圆形渐变,构造函数如下:

/** Create a shader that draws a radial gradient given the center and radius.        @param centerX  The x-coordinate of the center of the radius        @param centerY  The y-coordinate of the center of the radius        @param radius   Must be positive. The radius of the circle for this gradient.        @param colors   The colors to be distributed between the center and edge of the circle        @param stops    May be null. Valid values are between 0.0f and                        1.0f. The relative position of each corresponding color in                        the colors array. If null, colors are distributed evenly                        between the center and edge of the circle.        @param tileMode The Shader tiling mode    */    public RadialGradient(float centerX, float centerY, float radius, @NonNull int colors[], @Nullable float stops[], @NonNull TileMode tileMode) 复制代码

2.4.1 原点坐标(centerX, centerY)和半径radius

对于圆形渐变,我们可以这么理解,开始的时候,有一个半径无限小的圆环位于(centerX, centerY),它的颜色就是起点颜色,之后它开始慢慢变大,直到变为半径是radius为止,在此期间,圆环的颜色慢慢变为终点颜色,在整个变化的过程中,圆环所形成的轨迹就是最终的图像。

2.4.2 TileMode

由于在这种情况下,图像的大小是有限的,最大就是radius指定的范围,因此对于超出范围的图像,我们需要定义它的行为,但是原理还是和前面讨论的TileMode的三种情况一样的。

2.4.3 colors[]stops[]

原理和上面讨论的colors[]positions[]一样。

2.4.4 示例

private void drawRadialGradient(Canvas canvas) {        RadialGradient gradient = new RadialGradient(200, 200, 50, Color.BLUE, Color.RED, Shader.TileMode.REPEAT);        mPaint.setShader(gradient);        canvas.drawRect(0, 0, 900, 900, mPaint);    }复制代码

最终的结果为:

2.5 ComposeShader

上面,我们已经学习了四种Shader的实现方式,但是有时候,我们希望能够将它组合起来,ComposeShader就为我们提供了这种途径,可以组合两种Shader的实现。

/** Create a new compose shader, given shaders A, B, and a combining mode.        When the mode is applied, it will be given the result from shader A as its        "dst", and the result from shader B as its "src".        @param shaderA  The colors from this shader are seen as the "dst" by the mode        @param shaderB  The colors from this shader are seen as the "src" by the mode        @param mode     The mode that combines the colors from the two shaders. If mode                        is null, then SRC_OVER is assumed.    */    public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)复制代码

这就涉及到之前我们学过的PorterDuff.Mode,第一个Shader作为DST,而第二个Shader作为SRC,两个组合的结果会根据Mode的不同而发生改变,下面我们用一个简单的例子,来看一下BitmapShaderRadialGradient的组合:

private void drawComposeShader(Canvas canvas) {        BitmapShader bitmapShader = new BitmapShader(mOriginalBitmap, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);        RadialGradient radialGradient = new RadialGradient(300, 300, 300, Color.TRANSPARENT, Color.WHITE, Shader.TileMode.CLAMP);        ComposeShader composeShader = new ComposeShader(bitmapShader, radialGradient, PorterDuff.Mode.SRC_OVER);        mPaint.setShader(composeShader);        canvas.drawCircle(300, 300, 300, mPaint);    }复制代码

最终的结果为下图,可以看到,由于我们采用了SRC_OVER,因此就会出现朦胧的效果。

转载地址:http://vbvml.baihongyu.com/

你可能感兴趣的文章
注解的使用
查看>>
Android——Fragment介绍及两种基本使用方法
查看>>
《设计模式》学习笔记0——说明
查看>>
手把手教你 Spark 性能调优
查看>>
更适合物联网的边缘计算,可能只是投机者的一块鸡肋
查看>>
厉害了!阿里健康同时与3家医院签约 首次披露“ AI + 医疗”新进展
查看>>
漫谈:广告代表了尊贵
查看>>
X264 ratecontrol 码率控制
查看>>
iOS中 UISearchController 搜索栏 UI技术分享
查看>>
新手神器!不用部署深度学习环境、上传数据集!(附代码&视频教程)
查看>>
使用Python+OpenCV进行图像模板匹配(Match Template)
查看>>
Assembly Loop
查看>>
Windows注册密码能保护你的数据安全吗?
查看>>
绿盟科技网络安全威胁周报2017.01 请关注MatrixSSL堆缓冲区溢出漏洞CVE-2016-6890
查看>>
取舍有度:英特尔Security将有新产品取代旧的SaaS电子邮件安全产品
查看>>
岁月是把杀猪刀,扒一扒IT人愈发上扬的发际线
查看>>
用gdb分析core文件及常见gdb命令操作示例
查看>>
来自虚拟运营商的挑战 语音或将免费
查看>>
安吉斯媒体:流程化运作助推一站式管理
查看>>
云存储基础架构决策:公有 VS. 私有
查看>>