1 概述
Android中所有View的繪制工作歸根結(jié)底都是通過Paint完成的,所以下面就來看看這個東東是怎么用的赦肋,在現(xiàn)實(shí)世界中块攒,有很多種類的畫筆,比如水彩筆佃乘、鉛筆囱井、毛筆、圓珠筆等等,而Android中的畫筆就是Paint,現(xiàn)實(shí)世界的畫筆擁有的屬性都可以通過Paint的set方法設(shè)置并且Paint還擁有一些現(xiàn)實(shí)世界畫筆沒有的屬性煞躬。接下來我們就來看一下Paint中的set方法:
接下來我會依次分析下面列舉的幾個set方法:
1> public ColorFilter setColorFilter(ColorFilter filter)
2> public MaskFilter setMaskFilter(MaskFilter maskfilter)
3> public PathEffect setPathEffect(PathEffect effect)
4> public Shader setShader(Shader shader)
5> public Xfermode setXfermode(Xfermode xfermode)
其它的set方法有興趣的同學(xué)可以自己去研究一下幕屹。
2 setColorFilter
用來給Paint設(shè)置顏色過濾器,該方法接受一個ColorFilter類型的參數(shù)交洗,ColorFilter源碼如下:
/**
* A color filter can be used with a {@link Paint} to modify the color of
* each pixel drawn with that paint. This is an abstract class that should
* never be used directly.
*/
public class ColorFilter {
/**
* Holds the pointer to the native SkColorFilter instance.
*
* @hide
*/
public long native_instance;
@Override
protected void finalize() throws Throwable {
try {
super.finalize();
} finally {
destroyFilter(native_instance);
native_instance = 0;
}
}
static native void destroyFilter(long native_instance);
}
從注釋中可以得到如下兩個信息:
1> ColorFilter是用來修改Paint繪制時(shí)每一個像素Color值的送粱。
2> ColorFilter不應(yīng)該被直接使用栓袖,即應(yīng)該使用ColorFilter的子類讲逛。
那就來看看Google為我們準(zhǔn)備好的ColorFilter子類:
從上圖中可知髓绽,Google一共為我們準(zhǔn)備了3個ColorFilter子類,由上面注釋得到的第一條信息可知這3個ColorFilter子類就是用來提供修改Paint繪制時(shí)的每一個像素的規(guī)則妆绞。
2.1 ColorMatrixColorFilter(色彩矩陣顏色過濾器)
上面說過ColorFilter就是用來提供修改Paint繪制時(shí)每一個像素Color值的規(guī)則,那么對于ColorMatrixColorFilter來說枫攀,ColorMatrixColorFilter提供了通過ColorMatrix來修改Paint繪制時(shí)的每一個像素Color值的規(guī)則括饶,該規(guī)則可用于改變像素Color值的飽和度,從YUV轉(zhuǎn)換為RGB等来涨。
接下來我們就來看看ColorMatrix改變像素Color值的規(guī)則:
/**
* 4x5 matrix for transforming the color and alpha components of a Bitmap.
* The matrix can be passed as single array, and is treated as follows:
*
* <pre>
* [ a, b, c, d, e,
* f, g, h, i, j,
* k, l, m, n, o,
* p, q, r, s, t ]</pre>
*
* <p>
* When applied to a color <code>[R, G, B, A]</code>, the resulting color
* is computed as:
* </p>
*
* <pre>
* 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;</pre>
*
* <p>
* That resulting color <code>[R’, G’, B’, A’]</code>
* then has each channel clamped to the <code>0</code> to <code>255</code>
* range.
* </p>
*
* <p>
* The sample ColorMatrix below inverts incoming colors by scaling each
* channel by <code>-1</code>, and then shifting the result up by
* <code>255</code> to remain in the standard color space.
* </p>
*
* <pre>
* [ -1, 0, 0, 0, 255,
* 0, -1, 0, 0, 255,
* 0, 0, -1, 0, 255,
* 0, 0, 0, 1, 0 ]</pre>
*/
上面是Google給出的解釋图焰,相信大家可以看明白,我就不再贅敘了蹦掐。
通過ColorMatrix可以對像素Color值進(jìn)行如下運(yùn)算:
1> 像素Color值的平移運(yùn)算
2> 像素Color值的縮放運(yùn)算
3> 像素Color值的旋轉(zhuǎn)運(yùn)算
4> 像素Color值的投射運(yùn)算
2.1.1 像素Color值的平移運(yùn)算
像素Color值的平移運(yùn)算其實(shí)就是在ColorMatrix的最后一列設(shè)置某個值技羔;這樣可以增加或者減少指定通道(像素Color值包含RGBA四個通道)的飽和度。
舉個例子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginTop="50dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:src="@drawable/second_pic"/>
<ImageView
android:id="@+id/imageview_test_color_fliter"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:src="@drawable/second_pic"/>
</LinearLayout>
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 50,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
為了簡單起見卧抗,上面就直接調(diào)用了ImageView的setColorFilter方法藤滥,該方法最終會將colorMatrixColorFilter設(shè)置給繪制圖片的Paint,那么圖片中的每個像素在繪制之前都會被colorMatrix處理社裆。上面的代碼在R通道上添加50拙绊,即增大R通道的飽和度,那么圖片就會偏紅泳秀。運(yùn)行截圖如下(第一張圖片為原圖标沪,下同):
像素Color值反轉(zhuǎn)
利用ColorMatrix求出像素Color值每個通道值的補(bǔ)值作為將要被繪制像素Color值對應(yīng)的通道值。
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(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(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
運(yùn)行截圖如下(第一張圖片為原圖嗜傅,下同):
2.1.2 像素Color值的縮放運(yùn)算
像素Color值縮放運(yùn)算就是對RGBA四個通道值進(jìn)行縮放金句,并且當(dāng)對R、G吕嘀、B三個通道址同時(shí)進(jìn)行放大縮小時(shí)违寞,就是對亮度進(jìn)行調(diào)節(jié)。 舉例如下:
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
2, 0, 0, 0, 0,
0, 2, 0, 0, 0,
0, 0, 2, 0, 0,
0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
在繪制圖片之前币他,上面的代碼中的colorMatrix將圖片中所有像素Color值RGB三個通道值都增大了一倍坞靶,從而使圖片的亮度增大兩倍。運(yùn)行截圖如下:
上面是調(diào)高亮度蝴悉,下面就來降低亮度:
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
0.7f, 0, 0, 0, 0,
0, 0.7f, 0, 0, 0,
0, 0, 0.7f, 0, 0,
0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
在繪制圖片之前彰阴,上面的代碼中的colorMatrix將圖片中所有像素Color值RGB三個通道值都降低了30%,從而使圖片的亮度降低了30%拍冠。運(yùn)行截圖如下:
大家有沒有遇到在圖片背景上顯示文本的需求尿这,這時(shí)為了讓文本不被背景圖片干擾簇抵,就需要降低圖片背景的亮度,很多人的做法就是在背景圖片上覆蓋一層遮罩射众,這種做法既多了一層遮罩布局又占用內(nèi)存碟摆,所以通過上面colorMatrix降低圖片亮度才是解決這個問題更好的方案。
像素Color值的縮放運(yùn)算的應(yīng)用 通道輸出
利用ColorMatrix移除像素Color值RGB中的指定的通道值叨橱。
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
在繪制圖片之前典蜕,上面的代碼中的colorMatrix移除了圖片中所有像素Color值RG兩個通道值,從而變成只包含藍(lán)色的圖片罗洗。運(yùn)行截圖如下:
有些同學(xué)可能會說愉舔,這樣搞還是有點(diǎn)麻煩,有沒有好點(diǎn)的辦法伙菜,因此ColorMatrix提供了setScale方法:
/**
* Set this colormatrix to identity:
* <pre>
* [ 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
* </pre>
*/
public void reset() {
final float[] a = mArray;
Arrays.fill(a, 0);
a[0] = a[6] = a[12] = a[18] = 1;
}
/**
* Android中像素是由4個通道(RGBA)組成轩缤,該方法就是用來偏移4個通道的值(0表示該通道的值降到0,
* 即抹去像素中該通道的值贩绕,大于0小于1表示降低像素中該通道的值火的,1表示不變,大于1表示增大像素中該通道的值)淑倾。
*/
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;
}
reset方法會在ColorMatrix的構(gòu)造方法中被調(diào)用馏鹤,因此ColorMatrix的初始值是:
上圖中的矩陣下面統(tǒng)稱為初始矩陣,接下來看看setScale方法的源碼踊淳,setScale方法操作初始矩陣后得到的矩陣如下:
由上面的矩陣可知假瞬,該方法就是用來設(shè)置4個通道(RGBA)的取值比例,因此該方法可以用于像素Color值的縮放迂尝,下面就利用setScale方法實(shí)現(xiàn)上面三個例子相同的效果:
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setScale(2, 2, 2, 1);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setScale(0.7f, 0.7f, 0.7f, 1);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setScale(0, 0, 1, 1);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
運(yùn)行結(jié)果和上面例子相同脱茉,這里就不在贅敘了。
2.1.3 像素Color值的旋轉(zhuǎn)運(yùn)算
ColorMatrix提供了setRotate方法來實(shí)現(xiàn)像素Color值的旋轉(zhuǎn)運(yùn)算垄开,源碼如下:
/**
* 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();
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();
}
}
上面setRotate方法的源碼看起來有點(diǎn)蒙琴许,這是個什么東西,如果將RGB看做坐標(biāo)系就很好理解了溉躲,如下所示:
該方法的第一個參數(shù)axis取值為0時(shí)表示繞著Red軸進(jìn)行選擇榜田,取值為1時(shí)表示繞著Green軸旋轉(zhuǎn),取值為2時(shí)表示繞著Blue軸旋轉(zhuǎn)锻梳,那我們就以繞著Blue軸旋轉(zhuǎn)a舉例:
那么在初始矩陣基礎(chǔ)上做上面的操作后得到的矩陣如下:
同理繞著Red和Green軸旋轉(zhuǎn)得到的矩陣如下:
當(dāng)圍繞Red軸進(jìn)行旋轉(zhuǎn)時(shí)箭券,由于當(dāng)前Red通道的值是不變的,而僅利用三角函數(shù)來動態(tài)的變更綠色和藍(lán)色通道的值疑枯,這種改變就叫做色相調(diào)節(jié)辩块,當(dāng)圍繞Red軸旋轉(zhuǎn)時(shí),是對像素進(jìn)行紅色色相的調(diào)節(jié);同理废亭,當(dāng)圍繞Blue軸旋轉(zhuǎn)時(shí)国章,就是對像素進(jìn)行藍(lán)色色相調(diào)節(jié)。
舉個例子:
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setRotate(0, 180f);
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
在繪制圖片之前豆村,colorMatrix會將圖片中的所有像素都繞著Red軸旋轉(zhuǎn)180度液兽,這個時(shí)候Green和Blue軸是上的值就會變成負(fù)數(shù),由于RGB通道的值取值范圍是0到255掌动,所以Green和Blue軸上的值會被設(shè)置為0四啰,因此此時(shí)圖片只有紅色,運(yùn)行截圖如下:
2.1.4 像素Color值的投射運(yùn)算
利用其它通道分量的倍數(shù)來更改該通道分量的值粗恢,這種運(yùn)算就叫像素Color值的投射運(yùn)算拟逮。
像素Color值的投射運(yùn)算的應(yīng)用
1> 像素Color值灰度化
若要將像素灰度化,就需要將RGB三通道的色彩信息設(shè)置成一樣适滓;即:R=G=B,并且恋追,為了保證圖像亮度不變凭迹,同一個通道中的R+G+B應(yīng)該接近1。
在matlab中按照 0.2989 R苦囱,0.5870 G 和 0.1140 B 的比例構(gòu)成像素灰度值嗅绸。
在OpenCV中按照 0.299 R, 0.587 G 和 0.114 B 的比例構(gòu)成像素灰度值撕彤。
在Android中按照0.213 R鱼鸠,0.715 G 和 0.072 B 的比例構(gòu)成像素灰度值。
這些比例主要是根據(jù)人眼中三種不同的感光細(xì)胞的感光強(qiáng)度比例分配的羹铅,因此并沒有一個確切值蚀狰,不同工具調(diào)試出來的效果也不盡相同。
下面就用Android中的比例將圖片灰度化:
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = 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});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
運(yùn)行截圖如下:
2> 像素Color值反色
通過ColorMatrix將像素Color值的兩個通道值對調(diào)职员,這種操作就叫做像素Color值的反色麻蹋。
下面我們就將GB兩個通道值進(jìn)行對調(diào):
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 0, 1, 0, 0,
0, 1, 0, 0, 0,
0, 0, 0, 1, 0});
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
testColorFilterIV.setColorFilter(colorMatrixColorFilter);
運(yùn)行截圖如下:
ColorMatrix還提供了修改像素Color值飽和度的方法setSaturation,上面我們講過焊切,通過像素Color值的平移運(yùn)算可以增加或者減少指定通道(像素Color值包含RGBA四個通道)的飽和度扮授,但當(dāng)我們需要整體增強(qiáng)飽和度時(shí)就可以使用setSaturation方法,該方法源碼如下:
/**
* 用來改變像素飽和度(0表示灰度化专肪,大于0小于1表示降低飽和度刹勃,1表示不變,大于1表示增大飽和度)嚎尤。
*/
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;
}
根據(jù)setSaturation源碼可知當(dāng)參數(shù)為0時(shí)荔仁,初始矩陣就會變成:
上面的矩陣就是上面說的將像素Color值灰度化的ColorMatrix,運(yùn)行結(jié)果與上面灰度化的例子的運(yùn)行結(jié)果相同,就不在贅敘了咕晋。
下面看一下飽和度為0.5和2時(shí)的運(yùn)行截圖:
2.2 LightingColorFilter(光照顏色過濾器)
上面說過ColorFilter就是用來提供修改Paint繪制時(shí)每一個像素Color值的規(guī)則雹拄,那么對于LightingColorFilter來說,LightingColorFilter提供了如下規(guī)則:
/**
* A color filter that can be used to simulate simple lighting effects.
* A <code>LightingColorFilter</code> is defined by two parameters, one
* used to multiply the source color (called <code>colorMultiply</code>)
* and one used to add to the source color (called <code>colorAdd</code>).
* The alpha channel is left untouched by this color filter.
*
* Given a source color RGB, the resulting R'G'B' color is computed thusly:
* <pre>
* R' = R * colorMultiply.R + colorAdd.R
* G' = G * colorMultiply.G + colorAdd.G
* B' = B * colorMultiply.B + colorAdd.B
* </pre>
* The result is pinned to the <code>[0..255]</code> range for each channel.
*/
//mul和add的格式是0xAARRGGBB
//當(dāng)mul = 0xFFFFFFFF, add = 0x00000000時(shí)掌呜,像素Color值保持不變
public LightingColorFilter(int mul, int add) {
mMul = mul;
mAdd = add;
update();
}
上面的注釋已經(jīng)很詳細(xì)了滓玖,我就不再解釋了。
LightingColorFilter的作用就是增加或者減少指定通道(像素Color值包含RGBA四個通道)的飽和度质蕉,下面將像素Color值的B通道值抹去(即將B通道的飽和度設(shè)置為0):
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
LightingColorFilter lightingColorFilter = new LightingColorFilter(0xFFFFFF00, 0x00000000);
testColorFilterIV.setColorFilter(lightingColorFilter);
運(yùn)行截圖如下:
2.3 PorterDuffColorFilter(混合顏色過濾器)
上面說過ColorFilter就是用來提供修改Paint繪制時(shí)每一個像素Color值的規(guī)則势篡,那么對于PorterDuffColorFilter來說,PorterDuffColorFilter提供了如下規(guī)則:
/**
* A color filter that can be used to tint the source pixels using a single
* color and a specific {@link PorterDuff Porter-Duff composite mode}.
*/
/**
* Create a color filter that uses the specified color and Porter-Duff mode.
*
* @param color The ARGB source color used with the specified Porter-Duff mode
* @param mode The porter-duff mode that is applied
*
* @see Color
* @see #setColor(int)
* @see #setMode(android.graphics.PorterDuff.Mode)
*/
public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
mColor = color;
mMode = mode;
update();
}
上面注釋的就是通過PorterDuff.Mode將兩個Color值進(jìn)行混合處理模暗,先來通過下圖理解一下PorterDuff.Mode的作用:
那么對于PorterDuffColorFilter禁悠,src和dst對應(yīng)的是什么呢?
src:構(gòu)造方法的第一個參數(shù)
dst:將要被繪制的像素的Color值
下面在圖片上面加個遮罩:
布局代碼同上
testColorFilterIV = (ImageView) findViewById(R.id.imageview_test_color_fliter);
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(0x67000000, PorterDuff.Mode.DARKEN);
testColorFilterIV.setColorFilter(porterDuffColorFilter);
上面的代碼在圖片上添加了一個Color值為0x67000000的遮罩兑宇,運(yùn)行截圖如下: