ColorFilter详解
ColorFilter主要用来处理颜色,这里将讲解它的三个子类,ColorMatrixColorFilter,
LightingColorFilter以及PorterDuffColorFilter。
1.ColorMatrixColorFilter:
ColorMatrixColorFilter的构造方法很简单,一个是传入数组,一个是传入ColorMatrix类型的对象
1. public ColorMatrixColorFilter(ColorMatrix matrix) {
2. mMatrix.set(matrix);
3. update();
4. }
1. public ColorMatrixColorFilter(float[] array) {
2. if (array.length < 20) {
3. throw new ArrayIndexOutOfBoundsException();
4. }
5. mMatrix.set(array);
6. update();
7. }
这里主要来看一下ColorMatrix这个类,它内部有一个数组mArray,其实他保存的是一个4x5颜色矩阵,
1. * [ a, b, c, d, e,
2. * f, g, h, i, j,
3. * k, l, m, n, o,
4. * p, q, r, s, t ]
可以用来修改ARGB的值,其中 第一行决定红色R,第二行决定绿色G,第三行决定蓝色B,第四行决定了透明度A,第五列是颜色的偏移量
而图像的ARGB值存储在一个5*1的颜色分量矩阵中[R, G, B, A,1]。最终运算的结果是两矩阵相乘
1. R = a*R + b*G + c*B + d*A + e;
2. G = f*R + g*G + h*B + i*A + j;
3. B = k*R + l*G + m*B + n*A + o;
4. A = p*R + q*G + r*B + s*A + t;
我们看到mArray的大小为20,也就相当于一个4*5的数组,
1. private final float[] mArray = new float[20];
在初始化的时候,矩阵的数值会进行初始化
1. /**
2. * Set this colormatrix to identity:
3. * <pre>
4. * [ 1 0 0 0 0 - red vector
5. * 0 1 0 0 0 - green vector
6. * 0 0 1 0 0 - blue vector
7. * 0 0 0 1 0 ] - alpha vector
8. * </pre>
9. */
10. public void reset() {
11. final float[] a = mArray;
12. 0);
13. 0] = a[6] = a[12] = a[18] = 1;
14. }
其中第五列是偏移量,比如
1. [ 1 0 0 0 8
2. 0 1 0 0 8
3. 0 0 1 0 8
4. 0 0 0 1 0 ]
表示个颜色分量计算完成之后要在加上8,其中最后一行是表示透明度的,一般不要修改。我们来演示一下
1. public class ColorFilterView extends View {
2. private Paint mPaint;
3. private Bitmap mBitmap;
4. private int padding = 12;
5. float[] colorMatrix = {
6. 1, 0, 0, 0, 0, //red
7. 0, 0, 0, 0, 0, //green
8. 0, 0, 0, 0, 0, //blue
9. 0, 0, 0, 1, 0 //alpha
10. };
11. private ColorMatrixColorFilter mLightingColorFilter= new ColorMatrixColorFilter(colorMatrix);
12.
13. public ColorFilterView(Context context, AttributeSet attrs) {
14. super(context, attrs);
15. init();
16. }
17.
18. private void init() {
19. new Paint(Paint.ANTI_ALIAS_FLAG);
20. mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
21. }
22.
23. @Override
24. protected void onDraw(Canvas canvas) {
25. super.onDraw(canvas);
26. for (int i = 0; i < 8; i++) {
27. mPaint.setColorFilter(mLightingColorFilter);
28. canvas.drawBitmap(mBitmap,
29. 4) * (mBitmap.getWidth() + padding), (i / 4)
30. * (mBitmap.getHeight() + padding), mPaint);
31. }
32. }
33.
34. }
看一下结果
再来修改一下
1. float[] colorMatrix = {
2. 1, 0, 0, 0, 0, //red
3. 0, 1, 0, 0, 0, //green
4. 0, 0, 0, 0, 0, //blue
5. 0, 0, 0, 1, 0 //alpha
6. };
看一下运行结果
最后在修改一下,让他还原正常图片
1. float[] colorMatrix = {
2. 1, 0, 0, 0, 0, //red
3. 0, 1, 0, 0, 0, //green
4. 0, 0, 1, 0, 0, //blue
5. 0, 0, 0, 1, 0 //alpha
6. };
看一下结果
OK,上面的演示完了,我们看下面的一个矩阵。
1. [ 1 0 0 0 8
2. 0 2 0 0 8
3. 0 0 3 0 8
4. 0 0 0 1 0 ]
他表示红色分量偏移8,绿色分量*2在偏移8,蓝色分量*3在偏移8。下面看一下主要的方法,setScale(float rScale, float gScale, float bScale, float aScale)
1. public void setScale(float rScale, float gScale, float bScale,
2. float aScale) {
3. final float[] a = mArray;
4.
5. for (int i = 19; i > 0; --i) {
6. 0;
7. }
8. 0] = rScale;
9. 6] = gScale;
10. 12] = bScale;
11. 18] = aScale;
12. }
这个和矩阵刚初始化的时候差不多,不过这里的值不是1,是我们传入的值,代表的是亮度,我们看一下
1. public class ColorFilterView extends View {
2. private Paint mPaint;
3. private Bitmap mBitmap;
4. private ColorMatrix colorMatrix = new ColorMatrix();
5. private ColorMatrixColorFilter matrixColorFilter[] = new ColorMatrixColorFilter[24];
6. private int padding = 12;
7.
8. public ColorFilterView(Context context, AttributeSet attrs) {
9. super(context, attrs);
10. init();
11. }
12.
13. private void init() {
14. new Paint(Paint.ANTI_ALIAS_FLAG);
15. mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
16. for (int i = 0; i < 24; i++) {
17. if (i < 8)
18. colorMatrix.setScale(i * .1f, i * .1f, i * .1f, i * .1f);
19. else if (i < 16)
20. colorMatrix.setScale(i * .1f, i * .1f, i * .1f, i * .1f);
21. else
22. colorMatrix.setScale(i * .1f, i * .1f, i * .1f, i * .1f);
23. new ColorMatrixColorFilter(colorMatrix);
24. }
25. }
26.
27. @Override
28. protected void onDraw(Canvas canvas) {
29. super.onDraw(canvas);
30. for (int i = 0; i < 24; i++) {
31. mPaint.setColorFilter(matrixColorFilter[i]);
32. canvas.drawBitmap(mBitmap,
33. 4) * (mBitmap.getWidth() + padding), (i / 4)
34. * (mBitmap.getHeight() + padding), mPaint);
35. }
36. }
37. }
我们看一下运行结果
再来修改一下
1. for (int i = 0; i < 24; i++) {
2. if (i < 8)
3. colorMatrix.setScale(i * .1f, i * .3f, i * .9f, i * .1f);
4. else if (i < 16)
5. colorMatrix.setScale(i * .1f, i * .3f, i * .9f, i * .1f);
6. else
7. colorMatrix.setScale(i * .1f, i * .3f, i * .9f, i * .1f);
8. new ColorMatrixColorFilter(colorMatrix);
9. }
看一下运行结果
再看另一个方法setRotate(int axis, float degrees),表示的是色相
1. /**
2. * Set the rotation on a color axis by the specified values.
3. * <p>
4. * <code>axis=0</code> correspond to a rotation around the RED color
5. * <code>axis=1</code> correspond to a rotation around the GREEN color
6. * <code>axis=2</code> correspond to a rotation around the BLUE color
7. * </p>
8. */
9. public void setRotate(int axis, float degrees) {
10. reset();
11. double radians = degrees * Math.PI / 180d;
12. float cosine = (float) Math.cos(radians);
13. float sine = (float) Math.sin(radians);
14. switch (axis) {
15. // Rotation around the red color
16. case 0:
17. 6] = mArray[12] = cosine;
18. 7] = sine;
19. 11] = -sine;
20. break;
21. // Rotation around the green color
22. case 1:
23. 0] = mArray[12] = cosine;
24. 2] = -sine;
25. 10] = sine;
26. break;
27. // Rotation around the blue color
28. case 2:
29. 0] = mArray[6] = cosine;
30. 1] = sine;
31. 5] = -sine;
32. break;
33. default:
34. throw new RuntimeException();
35. }
36. }
其中axis为0时表示的是红色分量旋转的角度,为1时是绿色分量旋转的角度,为2时是蓝色分量旋转的角度,
1. for (int i = 0; i < 24; i++) {
2. if (i < 8)
3. 0, i*50);
4. else if (i < 16)
5. 1, i*50);
6. else
7. 2, i*50);
8. new ColorMatrixColorFilter(colorMatrix);
9. }
看一下运行结果
再来修改一下
1. for (int i = 0; i < 24; i++) {
2. if (i < 8)
3. 0, i*50);
4. else if (i < 16)
5. 1, (i%8)*50);
6. else
7. 2, (i%8)*50);
8. new ColorMatrixColorFilter(colorMatrix);
9. }
看一下运行结果
再看另一个方法setSaturation(float sat),代表的是饱和度,其中0是灰色,1是正常
1. for (int i = 0; i < 24; i++) {
2. if (i < 8)
3. colorMatrix.setSaturation(i*.2f);
4. else if (i < 16)
5. colorMatrix.setSaturation(i*.5f);
6. else
7. colorMatrix.setSaturation(i*2f);
8. new ColorMatrixColorFilter(colorMatrix);
9. }
运行结果为
setConcat(ColorMatrix matA, ColorMatrix matB),两矩阵相乘
1. public void setConcat(ColorMatrix matA, ColorMatrix matB) {
2. float[] tmp;
3. if (matA == this || matB == this) {
4. new float[20];
5. else {
6. tmp = mArray;
7. }
8.
9. final float[] a = matA.mArray;
10. final float[] b = matB.mArray;
11. int index = 0;
12. for (int j = 0; j < 20; j += 5) {
13. for (int i = 0; i < 4; i++) {
14. 0] * b[i + 0] + a[j + 1] * b[i + 5] +
15. 2] * b[i + 10] + a[j + 3] * b[i + 15];
16. }
17. 0] * b[4] + a[j + 1] * b[9] +
18. 2] * b[14] + a[j + 3] * b[19] +
19. 4];
20. }
21.
22. if (tmp != mArray) {
23. 0, mArray, 0, 20);
24. }
25. }
preConcat(ColorMatrix prematrix)前乘,postConcat(ColorMatrix postmatrix)后乘,调用的都是setConcat(ColorMatrix matA, ColorMatrix matB)方法,因为矩阵的乘法不具有交换律,改变两个矩阵的位置会产生不同的结果。
LightingColorFilter:
再来看ColorFilter的另一个子类LightingColorFilter光线颜色过滤,就一个构造方法
1. public LightingColorFilter(int mul, int add) {
2. mMul = mul;
3. mAdd = add;
4. update();
5. }
mul表示颜色增加的倍数,add为色彩增加,
1. public class ColorFilterView extends View {
2. private Paint mPaint;
3. private Bitmap mBitmap;
4. private LightingColorFilter mLightingColorFilter[] = new LightingColorFilter[8];
5. private int padding = 12;
6.
7. public ColorFilterView(Context context, AttributeSet attrs) {
8. super(context, attrs);
9. init();
10. }
11.
12. private void init() {
13. new Paint(Paint.ANTI_ALIAS_FLAG);
14. mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
15. //不变
16. 0] = new LightingColorFilter(0xFFFFFFFF,
17. 0x00000000);
18. //去掉红色
19. 1] = new LightingColorFilter(0xFF00FFFF,
20. 0x00000000);
21. //去掉绿色
22. 3] = new LightingColorFilter(0xFFFF00FF,
23. 0x00000000);
24. //去掉蓝色
25. 4] = new LightingColorFilter(0xFFFFFF00,
26. 0x00000000);
27. //增加红色
28. 5] = new LightingColorFilter(0xFFFFFFFF,
29. 0x00560000);
30. //增加绿色
31. 6] = new LightingColorFilter(0xFFFFFFFF,
32. 0x00006400);
33. //增加蓝色
34. 7] = new LightingColorFilter(0xFFFFFFFF,
35. 0x00000056);
36. }
37.
38. @Override
39. protected void onDraw(Canvas canvas) {
40. super.onDraw(canvas);
41. for (int i = 0; i < 8; i++) {
42. mPaint.setColorFilter(mLightingColorFilter[i]);
43. canvas.drawBitmap(mBitmap,
44. 4) * (mBitmap.getWidth() + padding), (i / 4)
45. * (mBitmap.getHeight() + padding), mPaint);
46. }
47. }
48.
49. }
运行结果为
3.PorterDuffColorFilter
ColorFilter下还有最后一个子类,PorterDuff混合模式的色彩过滤器,下面是其构造器:
1. public PorterDuffColorFilter(int color, PorterDuff.Mode mode)
Google文档:PorterDuff滤光器可以用于点源像素使用一个单一的颜色和一个特定的波特达夫复合模式。
PorterDuffColorFilter的构造器也很简单,其中第一个参数表示一个16进制的色彩值,第二个参数是一个枚举值PorterDuff.Mode,表示图片混排的模式,PorterDuff.Mode在Android下一共有16种。下面我们先写一个小例子看一下,这里我们还是使用上面的图片,为原图添加图片混排模式,颜色值设置为红色0XFFFF0000,混排模式设置为PorterDuff.Mode.DARKEN。
1. public class CustomView2 extends View {
2.
3. private Context mContext;
4. private Paint mPaint;
5. private Bitmap mBitmap;
6. private int x, y;
7.
8. public CustomView2(Context context) {
9. this(context, null);
10. }
11.
12. public CustomView2(Context context, AttributeSet attrs) {
13. super(context, attrs);
14. mContext = context;
15. initRes();
16. initPaint();
17. }
18.
19. private void initRes() {
20. //获取图片
21. mBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.image);
22. //获取图片显示起始位置
23. 2 - mBitmap.getWidth() / 2;
24. 2 - mBitmap.getHeight() / 2;
25. }
26.
27. private void initPaint() {
28. new Paint(Paint.ANTI_ALIAS_FLAG);
29. new PorterDuffColorFilter(0XFFFF0000, PorterDuff.Mode.DARKEN);
30. mPaint.setColorFilter(colorFilter);
31. }
32.
33. @Override
34. protected void onDraw(Canvas canvas) {
35. canvas.drawBitmap(mBitmap, x, y, mPaint);
36. }
37. }
上面的图片就是运行之后的效果了,原图不仅变红了,而且还变暗了。其实我们这里将PorterDuffColorFilter的构造器参数拆开来分析一下,首先我们传递进去一个红色的颜色值0XFFFF0000,这里相当于创建了一张新的图层,图层的颜色就是0XFFFF0000,而我们的原图可以看作是第二张图层,我们先把这2个图片重叠放在一起,就会发现得到一个原图上很红的图片,然后我们看一下PorterDuff.Mode是DARKEN模式,表示在之前得到的“原图+很红”的图片上进一步将色调调成暗色,最终得到了如上所示的图片。
关于PorterDuff.Mode,Android系统一共提供了18种混排模式,在模拟器的ApiDemos/Graphics/XferModes,有张效果图:
这张图可以很形象的说明图片各种混排模式下的效果。其中Src代表原图,Dst代表目标图,两张图片使用不同的混排方式后,得到的图像是如上图所示的。PorterDuff.Mode也提供了18种混排模式已经算法,其中比上图多了ADD和OVERLAY两种模式:
其中Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色,[...,..]前半部分计算的是结果图像的Alpha通道值,“,”后半部分计算的是结果图像的颜色值。图像混排后是依靠这两个值来重新计算ARGB值的,具体计算算法,抱歉,我也不知道,不过不要紧,不了解计算算法也不影响我们程序员写程序的。我们只要对照上面的apiDemo中提供的图片就能推测出混排后的结果的,下面是在网上找到的汉字语言描述,感谢这位作者的总结。
注意:先绘制dst,再绘制src。
1.PorterDuff.Mode.CLEAR
所绘制源图像不会提交到画布上。
2.PorterDuff.Mode.SRC
只显示源图像。
3.PorterDuff.Mode.DST
只显示目标图像。
4.PorterDuff.Mode.SRC_OVER
正常绘制显示,源图像居上显示。
5.PorterDuff.Mode.DST_OVER
上下层都显示。目标图像居上显示。
6.PorterDuff.Mode.SRC_IN
取两层绘制交集中的源图像。
7.PorterDuff.Mode.DST_IN
取两层绘制交集中的目标图像。
8.PorterDuff.Mode.SRC_OUT
只在源图像和目标图像不相交的地方绘制源图像。
9.PorterDuff.Mode.DST_OUT
只在源图像和目标图像不相交的地方绘制目标图像。
10.PorterDuff.Mode.SRC_ATOP
在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像。
11.PorterDuff.Mode.DST_ATOP
在源图像和目标图像相交的地方绘制目标图像而在不相交的地方绘制源图像。
12.PorterDuff.Mode.XOR
异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN
取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN
取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY
取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN
滤色。