我们在学画画的时候,首先需要买画笔,然后再购买画板,就可以愉快的创作(瞎画😄)啦!在安卓中也是这么设计的,毕竟设计也是要符合实际生活规律的。OK,那么本篇就是从画笔的基础开始讲起。
画笔基础 api
- 构造方法
// 创建出一只画笔🖌️
Paint paint = new Paint()
- 画笔的颜色,选择喜欢的颜色作画, 姿势很多,选择喜欢的即可
mPaint.setColor(Color.parseColor("#ff00ff"));
mPaint.setColor(Color.argb(200, 200, 200,200));
// 或 setARGB 的方式
mPaint.setARGB(200, 200, 200, 200);
- 画笔的尺寸大小,粗细程度, 比如我就喜欢粗的笔
mPaint.setStrokeWidth(30);
- 画笔的样式,比实际动手画画方便一些,比如我们画一个圆要填充整个圆,是不是得一点点的涂。但是手机上画可以一下搞定。这里有三个模式: STROKE(画线)、FILL(填充)、FILL_AND_STROKE (轮廓和填充)
// 有三种模式: FILL、STROKE、FILL_AND_STROKE
mPaint.setStyle(Paint.Style.STROKE);
- 抗锯齿 在计算机图形学中产生图形锯齿是因为计算机屏幕中的点是离散的有大小的像素点来表示的,当我们用计算机画连续的曲线的时候由于屏幕像素没有办法表示小数坐标,然后用四舍五入后整数来表示,放大来看,就会出现锯齿。一般解决办法就是找一些次要的像素填充或者透明度填充(通过计算后的到边缘的颜色值)
mPaint.setAntiAlias(true);
来吧!看看效果左边未设置抗锯齿,右边设置了抗锯齿。

你是不是想说,这不一样吗?这么看确实看不出区别。毕竟是人的肉眼,肯定没办法观察到很细节的东西。那么我们通过放大后再观察一下:

这下你看到了吧!未开启抗锯齿算法优化的化,可以看到边缘有锯齿形状。而右侧的就更加平顺一些。那我们有没有必要开启抗锯齿呢? 其实大部分情况下是可以不开启的,因为现在的手机分辨越来约高,只要不是对精细度追求极致的化,是可以不用开启的。
- 图像抖动 由于现在的手机分辨率越来越高,并且色彩深度默认为 32 位,效果也足够清晰了。抖动”是印刷行业和出版业中常用的一种工艺,老式的针式打印机只能打印出来黑点和白点,可是黑白图片是有灰度级的,那么如何打印出来图片呢?“抖动”由此而生,抖动试图通过在白色背景上生成黑色的二值图像来给出色调变化的直观印象,可以假想一下,黑点越密,那么远距离观察就越黑,如何控制黑点的分布就是“抖动”算法的核心。

如果颗粒越小,是不是就会看起来是灰色啦,欺骗人的肉眼。

- 图形端的形状 当然这是自己的术语描述,应该叫什么不清楚。共有三种:ROUND、BUTT、SQUARE, 第一种好理解圆的形状,第三种也好理解矩形形状,第二种是什么鬼。我们看下效果图。
mPaint.setStrokeCap(Paint.Cap.ROUND);

- 线段之间拐角形状
线段与线段之间的形状,可以让线段的连接看来更加圆润还是棱角分明。它也有三种: MITER、ROUND、BEVEL。从单词的的翻译来看,似乎只知道 ROUND,直接来看效果图。
mPaint.setStrokeJoin(Paint.Join.ROUND);

仔细看的化能看出在四个顶点都有些不同,MITER 更加尖一些, ROUND 则圆润一些,BEVEL 好像是切了一个角而来。ROUND 很好理解,说一些 MITER 和 BEVEl。它们两者都有相似之处,就是线段的末尾进行延长相交,对于 MITER 来说如果线段之间的夹角越小,那么顶点就会越尖。再来看一张图,这张图就比刚才矩形更加直观了。

- set(src)
这个很简单,就是从某一个创建好的画笔复制一份使用
- setFlags
等同于分别设置 setAntiAlias(true) 和 setDither(true)
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
高阶部分 api
设置颜色
- setShader(Shader shader) 设置 Shader
翻译过来称为着色器,其实也是用来设置画笔的颜色的。它有 5 个子类,依次是: LinearGradient、SweepGradient、RadialGradient、BitmapShader、ComposeShader
. LinearGradient 从名字可以看出是线性渐变的意思,直接上效果图
LinearGradient linearGradient = new LinearGradient(
300,
300,
700,
700,
Color.RED,
Color.YELLOW,
Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
可以看到接近最后一个点的地方设置成了蓝色,这几个参数的意思是: 起点的 x, y 左边,终点的 x,y 坐标,起点的颜色,终点的颜色, Shader.TileMode.CLAMP 在两个端点之外延用端点的颜色。

再来看看其他的用法,稳住别慌,坚持住。
LinearGradient linearGradient = new LinearGradient(
300,
300,
900,
900,
new int[] {
Color.RED,
Color.BLUE
},
new float[] {
0.5f,
0.5f
},
Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
这回发现多了两个参数,其实并没有,只是用int[] 添加颜色,float[] 数组用来指定端点到某一个区域的填充的颜色,需要和前面的 int[] 数组长度一直,否则会 gg。

继续来看 TileMode 的三种区别
MIRROR:

REPEAT

. SweepGradient 渐变,扇形渐变,也就是说某一个角度扫过的区域的渐变效果,可以看到跟线性渐变的使用方式差不多。
SweepGradient sweepGradient = new SweepGradient(300, 300,
new int[]{Color.RED, Color.GREEN, Color.BLUE},
new float[]{
0.1f, 0.3f, 1.0f
});
mPaint.setShader(sweepGradient);

. RadialGradient 径向渐变,根据圆的半径进行渐变色
RadialGradient radialGradient = new RadialGradient(700, 700, 300,
new int[]{Color.RED, Color.GREEN, Color.BLUE},
new float[]{0.2f, 0.5f, 1.0f},
Shader.TileMode.CLAMP);
mPaint.setShader(radialGradient);
效果图

它也有三种使用模式,其实跟线性渐变一样。
RadialGradient radialGradient = new RadialGradient(700, 700, 100,
new int[]{Color.RED, Color.GREEN, Color.BLUE},
new float[]{0.2f, 0.5f, 1.0f},
Shader.TileMode.CLAMP); // 更换模式.
REPEAT:

RadialGradient radialGradient = new RadialGradient(700, 700, 150,
Color.RED, Color.BLUE, Shader.TileMode.MIRROR);
mPaint.setShader(radialGradient);
MIRROR:

. BitmapShader. Bitmap 是图片,哟,图片也可以处理的哦!
BitmapShader bitmapShader = new BitmapShader(mBitmap,
Shader.TileMode.REPEAT,
Shader.TileMode.MIRROR);
mPaint.setShader(bitmapShader);
// 绘制一个矩形,填充 view 的宽高.
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
CLAMP: 以图片的 x 轴的最后一个像素填充 x 轴和 y 轴

REPEAT:以相同的图片进行填充.

MIRROR: 镜像的方式填充,好比照镜子一样,原像和镜像刚好相反

当前你也可以混合,比如在 x 轴方向重复填充, y 轴方向镜像填充

还可以这样:将图片填充到圆里

. ComposeShader 使用组合的方式,将前面的着色器配合来使用.
// 关闭硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.beauty);
mHeartBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.heart);
BitmapShader bitmapShader1 = new BitmapShader(mBitmap,
Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
BitmapShader bitmapShader2 = new BitmapShader(mHeartBitmap,
Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
ComposeShader composeShader = new ComposeShader(bitmapShader1, bitmapShader2, PorterDuff.Mode.SRC_OUT);
mPaint.setShader(composeShader);
canvas.drawCircle(500, 500, 300, mPaint);
- setColorFilter(ColorFilter colorFilter)
从名字就可以看出,主要是对颜色进行过滤。就是在绘制之前先过滤点指定的颜色再进行显示。也有三个子类供我们使用: LightingColorFilter、PorterDuffColorFilter、ColorMatrixColorFilter
. LightingColorFilter 光照过滤,就好比我们带着太阳镜过滤了太阳光中对眼睛有伤害的紫外线,然后眼睛看到了就是比较柔的。学到老活到老, 来看使用
LightingColorFilter colorFilter = new LightingColorFilter(
Color.parseColor("#ffffff"),
Color.parseColor("#000000"));
mPaint.setColorFilter(colorFilter);
通过看源码,可以看到它的计算规则是:
R' = R * colorMultiply.R + colorAdd.R
G' = G * colorMultiply.G + colorAdd.G
B' = B * colorMultiply.B + colorAdd.B
不难发现,如果采取上面的方式设置 mul: #ffffff, add: #000000, 那么计算如下:
R' = R * 0xff/0xff + 0x00
G' = G * 0xff/0xff + 0x00
B' = B * 0xff/0xff + 0x00
结果和原图一样.

现在如果要去掉原图中红色,只要将 mul 改为 0x00ffff, add 不变
LightingColorFilter colorFilter = new LightingColorFilter(
Color.parseColor("#00ffff"),
Color.parseColor("#000000"));
mPaint.setColorFilter(colorFilter);

增强红色:mul 不变, add: 0x660000
LightingColorFilter colorFilter = new LightingColorFilter(
Color.parseColor("#ffffff"),
Color.parseColor("#660000"));
mPaint.setColorFilter(colorFilter);

. PorterDuffColorFilter
指定一种颜色为源,采用图形混合模式进行处理。
// 指定白色为源,混合模式为目标显示 DST_OVER
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(
Color.parseColor("#ffffff"),
PorterDuff.Mode.DST_OVER);
mPaint.setColorFilter(porterDuffColorFilter);

更改原图颜色,并更改混合模式: SRC_OVER
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(
Color.parseColor("#ED29AB"),
PorterDuff.Mode.SRC_OVER);
mPaint.setColorFilter(porterDuffColorFilter);

. ColorMatrixColorFilter
这个就比较凶残了,因为是在是非常强大,内部是一个 4x5 的矩阵,通过变化矩阵中各通道的值,可以实现各种效果。
[ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t ]
通过代码搞搞事情,看看能玩出什么花招出来。
float[] colorMatrix = new float[] {
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0
};
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
mPaint.setColorFilter(colorMatrixColorFilter);

是不是没啥变化,因为上面矩阵定义的是白色的矩阵。所以没啥变化。再来看下面的矩阵会有什么变化。
float[] colorMatrix = new float[] {
0.33F, 0.59F, 0.11F, 0, 0,
0.33F, 0.59F, 0.11F, 0, 0,
0.33F, 0.59F, 0.11F, 0, 0,
0, 0, 0, 1, 0,
};
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
mPaint.setColorFilter(colorMatrixColorFilter);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(TECHNICOLOR);
mPaint.setColorFilter(colorMatrixColorFilter);

是不是发现变灰色了。继续修改
private static final float[] INVERT = new float[] {
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0,
};
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(TECHNICOLOR);
mPaint.setColorFilter(colorMatrixColorFilter);

是不是有点想胶卷的的图像颜色啦😄,继续修改。
private static final float[] RGB_TO_BGR = new float[] {
0, 0, 1, 0, 0,
0, 1, 0, 0, 0,
1, 0, 0, 0, 0,
0, 0, 0, 1, 0,
};
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(TECHNICOLOR);
mPaint.setColorFilter(colorMatrixColorFilter);
可以看到这里将 RGB 转为 BGR 的方式显示效果。

在看,如何设置称棕褐色。
private static final float[] SEPIA = new float[] {
0.393F, 0.769F, 0.189F, 0, 0,
0.349F, 0.686F, 0.168F, 0, 0,
0.272F, 0.534F, 0.131F, 0, 0,
0, 0, 0, 1, 0,
};
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(TECHNICOLOR);
mPaint.setColorFilter(colorMatrixColorFilter);

黑白照片
private static final float[] BLACK_AND_WHITE = new float[] {
1.5F, 1.5F, 1.5F, 0, -255,
1.5F, 1.5F, 1.5F, 0, -255,
1.5F, 1.5F, 1.5F, 0, -255,
0, 0, 0, 1, 0,
};
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(TECHNICOLOR);
mPaint.setColorFilter(colorMatrixColorFilter);

彩色照片
private static final float[] TECHNICOLOR = new float[] {
1.9125277891456083F, -0.8545344976951645F, -0.09155508482755585F, 0, 11.793603434377337F,
-0.3087833385928097F, 1.7658908555458428F, -0.10601743074722245F, 0, -70.35205161461398F,
-0.231103377548616F, -0.7501899197440212F, 1.847597816108189F, 0, 30.950940869491138F,
0, 0, 0, 1, 0
};
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(TECHNICOLOR);
mPaint.setColorFilter(colorMatrixColorFilter);

通过不同的矩阵,可以得到不同转换后的效果。
接下来讨论一下滤镜的原理,安卓系统使用一个颜色矩阵 ColorMatrix(下图 A 表示) 来处理图像的颜色,对于每一个像素点都有一个颜色分量矩阵来保存的 RGBA 值(下图 C)。

来看官方文档的描述 ColorMatrix, 在安卓系统中通过维护一个 4 * 5 的以为数组来表示。那对一个颜色的运算使用矩阵乘法如下:
// 运算的结果为 R'G'B'A'
R’ = a*R + b*G + c*B + d*A + e;
G’ = f*R + g*G + h*B + i*A + j;
B’ = k*R + l*G + m*B + n*A + o;
A’ = p*R + q*G + r*B + s*A + t;
这个公式告诉我们,第一行 abcde 决定红色,第二行 fghij 决定绿色,第三行 klmno 决定蓝色,最后一行 pqrst 决定透明度。
再根据文档中可看出 reset 方法的颜色矩阵为:
[ 1 0 0 0 0 - red vector
0 1 0 0 0 - green vector
0 0 1 0 0 - blue vector
0 0 0 1 0 ] - alpha vector
这个颜色也称为初始颜色,运用这个颜色矩阵作用在图片的话,将会保持原来的颜色不变。下面来一个简单的变化,矩阵取反,其实前面已经看到过效果了。
[ -1 0 0 0 255 - red vector
0 1- 0 0 255 - green vector
0 0 -1 0 255 - blue vector
0 0 0 -1 0 ] - alpha vector
这里的最后一列,表示的是颜色的偏移量,下面的意思就是红色和绿色偏移100。
[1,0,0,0,100,
0,1,0,0,100,
0,0,1,0,100,
0,0,0,1,0
]

下面是我写的一个可以通过设置参数来调节图片的颜色过滤,代码很简单安卓碎片知识
如果开发中是这样设置的话,我相信可能会累死。安卓 ColorMatrix 颜色矩阵中封装了一些 API 来快速调整上面这三个颜色参数,可以方便的使用。
. 色调
对色彩进行旋转运算,至于如何旋转的这个我也没有研究。感兴趣的可以上网找找资料看看。
// 第一个参数: 0 表示绕红色的旋转, 1 表示绕绿色的旋转, 2表示绕蓝色的旋转
// 至于第二个参数:可以通过源码大概可以知道。它应该在 -180 ~ 180 之间。
colorMatrix.setRotate(0, 180);
colorMatrix.setRotate(1, 180);
colorMatrix.setRotate(2, 180);
/**
* Set the rotation on a color axis by the specified values.
* <p>
* <code>axis=0</code> correspond to a rotation around the RED color
* <code>axis=1</code> correspond to a rotation around the GREEN color
* <code>axis=2</code> correspond to a rotation around the BLUE color
* </p>
*/
public void setRotate(int axis, float degrees) {
reset();
// 周期是在 -180 到 180 之间
double radians = degrees * Math.PI / 180d;
float cosine = (float) Math.cos(radians);
float sine = (float) Math.sin(radians);
switch (axis) {
// Rotation around the red color
case 0:
mArray[6] = mArray[12] = cosine;
mArray[7] = sine;
mArray[11] = -sine;
break;
// Rotation around the green color
case 1:
mArray[0] = mArray[12] = cosine;
mArray[2] = -sine;
mArray[10] = sine;
break;
// Rotation around the blue color
case 2:
mArray[0] = mArray[6] = cosine;
mArray[1] = sine;
mArray[5] = -sine;
break;
default:
throw new RuntimeException();
}
}
. 饱和度 放大色彩饱和度,大于 1 色彩开始饱和,等于 1 不变,小于 1 过度到没有色彩饱和,等于 0 为黑白图像。
colorMatrix1.setSaturation(3.0f);
就是将 RBGA 加上我们指定的 sat。
/**
* Set the matrix to affect the saturation of colors.
*
* @param sat A value of 0 maps the color to gray-scale. 1 is identity.
*/
public void setSaturation(float sat) {
reset();
float[] m = mArray;
final float invSat = 1 - sat;
final float R = 0.213f * invSat;
final float G = 0.715f * invSat;
final float B = 0.072f * invSat;
m[0] = R + sat; m[1] = G; m[2] = B;
m[5] = R; m[6] = G + sat; m[7] = B;
m[10] = R; m[11] = G; m[12] = B + sat;
}
. 亮度 以不同的颜色比列进行混合, 如果全部为 0 为黑色。全部为 1 则为图片原有颜色。如果看源码的话,可以看到它其实设置的是 a[0],a[6],a[12], a[18] 的值,然后其他的都置为0。
colorMatrix1.setScale(0f, 0f, 0f,1.0f);
/**
* Set this colormatrix to scale by the specified values.
*/
public void setScale(float rScale, float gScale, float bScale,
float aScale) {
final float[] a = mArray;
for (int i = 19; i > 0; --i) {
a[i] = 0;
}
a[0] = rScale;
a[6] = gScale;
a[12] = bScale;
a[18] = aScale;
}
好吧! 写到这儿就结束了。但是 Paint 的还没有说完,剩下的还有跟文字相关的、其他的图层混合模式。