1. 程式人生 > >Android繪圖機制與處理技巧(五)Android影象處理之畫筆特效處理

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依次繪製出來.

程式碼下載