Android繪圖機制與處理技巧(五)Android影象處理之畫筆特效處理
前面的文章中已經學習了常用的畫筆屬性,比如普通的畫筆(Paint),帶邊框、填充的style,顏色(Color),寬度(StrokeWidth),抗鋸齒(ANTI_ALIAS_FLAG)等,然而Android還提供了各種各樣專業的畫筆工具,如記號筆、毛筆、蠟筆等,使用它們可以實現更加豐富的效果,下面就來看看畫筆的一些高階屬性。
PorterDuffXfermode
下圖是Api demo的一張圖,列舉了16種PorterDuffXfermode,有點像數學中集合的交集、並集這樣的概念,它控制的是兩個影象間的混合顯示模式。
要注意的是,PorterDuffXfermode設定的是兩個圖層交集區域的顯示方式,dst是先畫的圖形,而src是後畫的圖形。
當然,這些模式也不是經常使用,用的最多的是,使用一張圖片作為另一張圖片的遮罩層,通過控制遮罩層的圖形,來控制下面被遮罩圖形的顯示效果。其中最常用的就是通過DST_IN、SRC_IN模式來實現將一個矩形圖片變成圓角圖片或者圓形圖片的效果。
圓角矩形
要使用PorterDuffXfermode非常簡單,只需要讓畫面擁有這個屬性就可以了。比如下面要實現的圓角矩形效果:
先用一個普通畫筆畫一個Mask遮罩層,再用帶PorterDuffXfermode的畫筆將影象畫在遮罩層上,這樣就可以通過上面所說的效果來混合兩個影象了,程式碼如下:
mBitmap = BitmapFactory.decodeResource(getResources(),
R.mipmap.shape_test1);
mOut = Bitmap.createBitmap(mBitmap.getWidth(),
mBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mOut);
mPaint = new Paint();
mPaint.setAntiAlias(true);
canvas.drawRoundRect(0, 0,
mBitmap.getWidth(),
mBitmap.getHeight(), 80, 80, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
刮刮卡效果
下面來實現一下刮刮卡效果,刮刮卡一般有兩個圖層,即上面的用來被刮掉的圖層和下面隱藏的圖層。在初始狀態下,上面的圖層會將下面整個圖層覆蓋,當你用手刮上面的圖層的時候,下面的圖層會慢慢顯示出來,類似於畫圖工具中的橡皮擦效果, 同樣使用PorterDuffXfermode來實現。
程式碼如下所示如下所示:
public class XfermodeView extends View {
private Bitmap mBgBitmap, mFgBitmap;
private Paint mPaint;
private Canvas mCanvas;
private Path mPath;
public XfermodeView(Context context) {
super(context);
init();
}
public XfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public XfermodeView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/*將畫筆的透明度設定為0,這樣才能顯示出擦除效果。在使用PorterDuffXfermode進行圖層混合時,並不是簡單地只進行圖層的計算,同時也會計算透明通道的值。正是由於混合了透明通道,才形成了這樣的效果。*/
private void init() {
mPaint = new Paint();
mPaint.setAlpha(0);
mPaint.setXfermode(
new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(50);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPath = new Path();
mBgBitmap = BitmapFactory.decodeResource(getResources(),
R.mipmap.shape_test);
mFgBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(),
mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mFgBitmap);
mCanvas.drawColor(Color.GRAY);
}
/*獲取使用者手指滑動所產生的路徑並將使用DST_IN模式將路徑繪製到前面覆蓋的圖層上,程式碼如下所示。使用Path儲存使用者手指劃過的痕跡。當然,如果使用貝塞爾曲線來做優化則會得到更好的顯示效果,這裡為了簡化功能,就不使用貝塞爾曲線了。*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
break;
}
mCanvas.drawPath(mPath, mPaint);
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBgBitmap, 0, 0, null);
canvas.drawBitmap(mFgBitmap, 0, 0, null);
}
}
效果圖如下所示:
Shader
Shader又被稱之為著色器、渲染器,它用來實現一系列的漸變、渲染效果。android中的Shader包括以下幾種。
- BitmapShader——點陣圖Shader
- LinearGradient——線性Shader
- RadialGradient——光束Shader
- SweepGradient——梯度Shader
- ComposeGradient——混合Shader
BitmapShader
除了地一個以外的Shader都是正常的漸變、渲染效果, 而BitmapShader與其他的Shader所產生的漸變不同,它產生的是一個影象,這有點像Photoshop中的影象填充漸變。它的作用就是通過Paint對畫布進行指定Bitmap的填充,填充時有以下幾種模式可以選擇。
- CLAMP拉伸——拉伸的是圖片最後的那一個畫素,不斷重複
- REPEAT重複——橫向、縱向不斷重複
- MIRROR映象——橫向不斷翻轉重複,縱向不斷翻轉重複
最常用的是CLAMP拉伸模式,雖然它會拉伸最後一個畫素,但是隻要將影象設定為一定的大小,就可以避免這種拉伸。下面使用這種方式將一個矩形的圖片變成一張圓形的圖片,效果如下圖:
使用BitmapShader來進行圖形填充
mBitmap = BitmapFactory.decodeResource(getResources(),
R.mipmap.shape_test);
mBitmapShader = new BitmapShader(mBitmap,
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mBitmapShader);
canvas.drawCircle(500, 250, 200, mPaint);
下面把TileMode改為REPEAT,並將Bitmap改為較小的ic_launcher圖示,就可以看出幾種模式的區別了,程式碼如下所示:
mBitmap = BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher);
mBitmapShader = new BitmapShader(mBitmap,
Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mPaint = new Paint();
mPaint.setShader(mBitmapShader);
canvas.drawCircle(500, 250, 200, mPaint);
顯示效果如下圖:
LinearGradient
要使用LinearGradient非常簡單,只需要指定漸變的起始顏色即可,程式碼如下所示:
Paint paint = new Paint();
paint.setShader(new LinearGradient(0, 0, 400, 400, Color.BLUE, Color.YELLOW, Shader.TileMode.REPEAT));
canvas.drawRect(0, 0, 400, 400, paint);
通過以上程式碼話除了一個從(0, 0)到(400, 400)的由藍色到黃色的漸變效果。
LinearGradient方法引數中的TileMode與在BitmapShader中的含義基本相同,這裡將繪製矩形的大小設定為與漸變影象大小相同,所以沒有看見REPEAT的效果。如果將圖形擴大,REPEAT的效果就出來了,如下圖所示:
其他幾種模式與LinearGradient基本相同, 只是漸變效果顯示不同而已。
遮照倒影
通常情況,漸變效果都是作為一個遮罩層來使用,同時結合PorterDuffXfermode。這樣處理後,遮罩層就不再是一個生硬的圖形,而是一個具有漸變效果的圖層。這樣處理的效果會更加柔和、更加自然。下面用LinearGradient和PorterDuffXfermode來建立一個具有倒影效果的圖片,效果如下所示:
首先將原圖複製一份並進行翻轉
mSrcBitmap = BitmapFactory.decodeResource(getResources(),
R.mipmap.shape_test);
Matrix matrix = new Matrix();
matrix.setScale(1F, -1F);
mRefBitmap = Bitmap.createBitmap(mSrcBitmap, 0, 0,
mSrcBitmap.getWidth(), mSrcBitmap.getHeight(), matrix, true);
翻轉使用matrix.setScale(1F, -1F)方法來實現圖片的垂直翻轉, 避免了使用旋轉變換的複雜計算.
在onDraw方法中繪製兩張圖片即原圖和倒影圖, 然後在倒影圖上使用Mode.DST_IN模式繪製一個相同大小的漸變矩形
mPaint = new Paint();
mPaint.setShader(new LinearGradient(0, mSrcBitmap.getHeight(), 0,
mSrcBitmap.getHeight() + mSrcBitmap.getHeight() / 4,
0XDD000000, 0X10000000, Shader.TileMode.CLAMP));
mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(mSrcBitmap, 0, 0, null);
canvas.drawBitmap(mRefBitmap, 0, mSrcBitmap.getHeight(), null);
mPaint.setXfermode(mXfermode);
// 繪製漸變效果矩形
canvas.drawRect(0, mSrcBitmap.getHeight(),
mRefBitmap.getWidth(), mSrcBitmap.getHeight() * 2, mPaint);
mPaint.setXfermode(null);
}
PathEffect
PathEffect就是指用各種筆觸效果來繪製路徑。Android系統提供瞭如下中展示的幾種繪製PathEffect的方式,從上到下依次是:
- 沒效果
- CornerPathEffect——將拐角處變得圓滑,具體圓滑的程度,由引數決定
- DiscretePathEffect——線段上就會產生許多雜點
- DashPathEffect——可以用來繪製虛線,用一個數組來設定各個點之間的間隔。此後繪製虛線時就重複這樣的間隔進行繪製,另一個引數phase則用來控制繪製時陣列的一個偏移量,通常可以通過設定值來實現路徑的動態效果。
- PathDashPathEffect——與DashPathEffect類似,可以設定顯示點的圖形,即方形點的虛線、圓形點的虛線。
- ComposePathEffect——通過ComposePathEffect來組合PathEffect,將任意兩種路徑特性組合起來形成一個新的效果。
首先生成一個Path, 這裡使用隨機數生成一些隨機的點並形成一條路徑:
mPath = new Path();
mPath.moveTo(0, 0);
for (int i = 0; i <= 30; i++) {
mPath.lineTo(i * 35, (float) (Math.random() * 100));
}
然後在onDraw方法中通過不同的PathEffect繪製這些Path:
mEffects[0] = null;
mEffects[1] = new CornerPathEffect(30);
mEffects[2] = new DiscretePathEffect(3.0F, 5.0F);
mEffects[3] = new DashPathEffect(new float[]{20, 10, 5, 10}, 0);
Path path = new Path();
path.addRect(0, 0, 8, 8, Path.Direction.CCW);
mEffects[4] = new PathDashPathEffect(path, 12, 0, PathDashPathEffect.Style.ROTATE);
mEffects[5] = new ComposePathEffect(mEffects[3], mEffects[1]);
for (int i = 0; i < mEffects.length; i++) {
mPaint.setPathEffect(mEffects[i]);
canvas.drawPath(mPath, mPaint);
canvas.translate(0, 200);
}
每次繪製一個Path, 就將畫布平移, 從而讓各種PathEffect依次繪製出來.