Android進階——高級UI必知必會之2D繪製與使用Paint對圖形進行渲染和濾鏡混合處理(二)

引言

前一篇文章Android進階——自定義View必知必會之Android座標系與Paint的基礎應用(一)詳細總結了下關於Android座標系和Paint的基本應用的相關知識,不過Paint可不僅僅只有哪些簡單的用法,這篇好好總結下Pain的高級應用,相關文章鏈接如下:

一、Shader系渲染

所謂渲染就是對於我們繪製區域按照約定的渲染規則進行色彩的填充

1、Shader

Canvas的drawXxxx系方法是繪製具體的形狀的,Shader是定義圖形具體的着色外觀即所謂的渲染,而渲染是通過給Paint設置Shader來實現渲染的Shader着色器可以在繪製時返回對應的顏色信息,Paint的Shader定義的就是圖形的着色外觀和拉伸模式TileMode(在圖片和顯示區域大小不符的情況進行擴充渲染):

  • Shader.TileMode.CLAMP——拉伸最後一個像素鋪滿
  • Shader.TileMode.MIRROR——橫向縱向不足時不斷翻轉,鏡像平鋪
  • Shader.TileMode.REPEAT——類似電腦壁紙,橫向縱向不足時重複放置

Android 默認提供了五種Shader的子類:BitmapShader圖形渲染、LinearGradient線性渲染、SweepGradient漸變渲染(梯度渲染)、RadialGradient環形渲染和ComposeShader組合渲染

2、BitmapShader圖形渲染

用BitMap對繪製的圖形進行渲染着色,即用圖片對圖形進行貼圖

/**
 * @author : Crazy.Mo
 */
public class BitmapShaderView extends View {
    private Paint mPaint;
    private Bitmap mBitMap = null;
    private int mWidth;
    private int mHeight;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context) {
        this(context,null);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BitmapShaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mBitMap = ((BitmapDrawable) getResources().getDrawable(R.drawable.mn)).getBitmap();
        mPaint = new Paint();
        mWidth = mBitMap.getWidth();
        mHeight = mBitMap.getHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        /**
         * 位圖渲染,BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)
         * Bitmap:構造shader使用的bitmap
         * tileX:X軸方向的TileMode
         * tileY:Y軸方向的TileMode
         */
        BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.REPEAT,
                Shader.TileMode.REPEAT);
                        //設置像素矩陣,來調整大小,爲了解決寬高不一致的問題。
        float scale = Math.max(mWidth, mHeight) / Math.min(mWidth, mHeight);
        Matrix matrix = new Matrix();
        matrix.setScale(scale, scale);
        bitMapShader.setLocalMatrix(matrix);
        mPaint.setShader(bitMapShader);
        mPaint.setAntiAlias(true);
        mPaint.setShader(bitMapShader);
        mPaint.setAntiAlias(true);
        //繪製圖形的矩形區域
        canvas.drawRect(new Rect(0, 0, 1000, 1000), mPaint);
    }
}

在這裏插入圖片描述
通過ShapeDrawable也可以實現圓圖

canvas.drawCircle(200, 300, 200, mPaint);
//通過shapeDrawable也可以實現  圓圖(如:頭像)
ShapeDrawable shapeDrawble = new ShapeDrawable(new OvalShape());
shapeDrawble.getPaint().setShader(bitMapShader);
shapeDrawble.setBounds(0,0,mWidth,mWidth);
shapeDrawble.draw(canvas);

在這裏插入圖片描述

3、LinearGradient線性渲染

用於圖形元素的填充或描邊。

/**
 * @author : Crazy.Mo
 */
public class LinearGradientView extends View {
    private Paint mPaint;
    private int mWidth;
    private int mHeight;
    private int[] mColors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context) {
        this(context,null);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public LinearGradientView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mPaint = new Paint();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**線性渲染
         * x0, y0, 起始點
         *  x1, y1, 結束點
         * int[]  mColors, 中間依次要出現的幾個顏色
         * float[] positions 位置數組,position的取值範圍[0,1],作用是指定幾個顏色分別放置在那個位置上,
         * 如果傳null,漸變就線性變化。
         *    tile 用於指定控件區域大於指定的漸變區域時,空白區域的顏色填充方法
         */
		LinearGradient linearGradient = new LinearGradient( 0, 0,800, 800,
                mColors, null, Shader.TileMode.CLAMP);
        // linearGradient = new LinearGradient(0, 0, 400, 400, mColors, null, Shader.TileMode.REPEAT);
		mPaint.setShader(linearGradient);
		canvas.drawRect(0, 0, 800, 800, mPaint);
    }
}

在這裏插入圖片描述

4、SweepGradient漸變渲染(梯度渲染)

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 掃描渲染
         * cx,cy 漸變中心座標
         * color0,color1:漸變開始結束顏色
         * colors,positions:類似LinearGradient,當positions爲null時,根據顏色線性漸變
         */
        SweepGradient swepGradient = new SweepGradient(400, 400,
                mColors, null);
		mPaint.setShader(swepGradient);
		canvas.drawCircle(400, 400, 300, mPaint);
    }

在這裏插入圖片描述

5、RadialGradient環形渲染

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 環形渲染
         * centerX ,centerY:shader的中心座標,開始漸變的座標
         * radius:漸變的半徑
         * centerColor,edgeColor:中心點漸變顏色,邊界的漸變顏色
         * colors:漸變顏色數組
         * stops:漸變位置數組,類似掃描漸變的positions數組,取值[0,1],中心點爲0,半徑到達位置爲1.0f
         * tileMode:shader未覆蓋以外的填充模式
         */
        RadialGradient radialGradient = new RadialGradient(300, 300, 100,
         mColors, null, Shader.TileMode.REPEAT);
		mPaint.setShader(radialGradient);
		canvas.drawCircle(300, 300, 300, mPaint);
    }

在這裏插入圖片描述

6、ComposeShader組合渲染

Bitmap mBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.heart)).getBitmap();
BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//創建LinearGradient,用以產生從左上角到右下角的顏色漸變效果
LinearGradient linearGradient = new LinearGradient(0, 0, mWidth, mHeight,
     mColors,null, Shader.TileMode.CLAMP);
//bitmapShader對應目標像素,linearGradient對應源像素,像素顏色混合採用MULTIPLY模式
ComposeShader composeShader = new ComposeShader(linearGradient, bitmapShader, PorterDuff.Mode.MULTIPLY);
//         ComposeShader composeShader2 = new ComposeShader(composeShader, linearGradient, PorterDuff.Mode.MULTIPLY);
//將組合的composeShader作爲畫筆paint繪圖所使用的shader
mPaint.setShader(composeShader);
//用composeShader繪製矩形區域
canvas.drawRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight(), mPaint);

在這裏插入圖片描述

二、對圖片進行濾鏡處理

所謂濾鏡其實只不過是對於原本圖像色彩進行調整,因爲一張圖片本身就是由一個個像素點構成的。

1、顏色模式

是將某種顏色表現爲數字形式的模型或者說是一種記錄圖像顏色的方式,可分爲:RGB模式CMYK模式、HSB模式、Lab顏色模式、位圖模式、灰度模式、索引顏色模式、雙色調模式和多通道模式,其中RGB分別爲紅、綠、藍CMYK分別爲青色、洋紅、黃色、黑色

2、像素點的顏色通道

保存圖像顏色信息的通道稱爲顏色通道每個圖像都有一個或多個顏色通道,而圖像中默認的顏色通道數取決於其顏色模式,即一個圖像的顏色模式將決定其顏色通道的數量,ARGB模式中一個像素的顏色是由四個分量組成:

  • A爲透明度通道,取值在0—1f
  • R爲紅色通道——取值在0—255f
  • G爲綠色通道——取值在0—255f
  • B爲藍色通道——取值在0—255f

如果某個像素點的通道值越大則改像素點所佔顏色比例越多。比如A透明通道A值越小就越透明,A爲0就完全透明,A爲1f就是完全不透明;而R紅色通道值所佔比例

而CMYK圖像默認有4個通道(分別爲青色、洋紅、黃色、黑色);位圖模式、灰度、雙色調和索引顏色圖像只有一個通道;RGB和Lab圖像有3個通道。

3、顏色矩陣

Android中可以通過顏色矩陣 ColorMatrix 方便的操作顏色(當然也可以不通過顏色矩陣先拿到圖片中每個像素點信息並修改其對應的ARGB值實現,本質上顏色矩陣也是這樣實現的),可以用來方面的修改圖片中RGBA各分量的值,一般是以一個5x4 的矩陣(又稱爲5x4行列式)表示:
在這裏插入圖片描述
直接改變顏色矩陣實現不同濾鏡效果:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //關閉單個View的硬件加速功
        paint.reset();
        // 平移運算,只改變偏移量
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1, 0,0,0,0,
//                0,1,0,0,100,
//                0,0,1,0,0,
//                0,0,0,1,0,
//        });

        // 反相效果 -- 底片效果
//       ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                -1, 0,0,0,255,
//                0,-1,0,0,255,
//                0,0,-1,0,255,
//                0,0,0,1,0,
//        });
        // 縮放運算---乘法 -- 顏色增強
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1.2f, 0,0,0,0,
//                0,1.2f,0,0,0,
//                0,0,1.2f,0,0,
//                0,0,0,1.2f,0,
//        });

         /** 黑白照片
          *是將我們的三通道變爲單通道的灰度模式
          *去色原理:只要把R G B 三通道的色彩信息設置成一樣,那麼圖像就會變成灰色,
          *同時爲了保證圖像亮度不變,同一個通道里的R+G+B =1
          */
//        ColorMatrix colorMartrix = 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,
//        });

        

        // 髮色效果---(比如紅色和綠色交換)
        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
                1,0,0,0,0,
                0, 0,1,0,0,
                0,1,0,0,0,
                0,0,0,0.5F,0,
        });
        // 復古效果
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1/2f,1/2f,1/2f,0,0,
//                1/3f, 1/3f,1/3f,0,0,
//                1/4f,1/4f,1/4f,0,0,
//                0,0,0,1,0,
//        });
        /**
         *  顏色通道過濾需要兩個矩陣
         * 本身顏色矩陣A*過濾矩陣C=最終效果
         */
//        ColorMatrix colorMartrix = new ColorMatrix(new float[]{
//                1.3F,0,0,0,0,
//                0,1.3F,0,0,0,
//                0,0,1.3F,0,0,
//                0,0,0,1,0,
//        });
        RectF rectF = new RectF(200,100,bitmap.getWidth()+200,bitmap.getHeight());
        paint.setColorFilter(new ColorMatrixColorFilter(colorMartrix));
        canvas.drawBitmap(bitmap,null, rectF,paint);
    }

在這裏插入圖片描述

4、setMaskFilter設置濾鏡

  • BlurMaskFilter模糊遮罩濾鏡
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //關閉單個View的硬件加速功
       setLayerType(View.LAYER_TYPE_SOFTWARE,null);
        RectF rectF = new RectF(200,100,bitmap.getWidth()+200,bitmap.getHeight());
        paint.reset();
        paint.setColor(Color.BLUE);
        //畫布的平移
        //canvas.translate(200,0);
        /**
         * @param radius 陰影的半徑
         * @param style  NORMOL -- 整個圖像都被模糊掉
         *               SOLID -- 圖像邊界外產生一層與Paint顏色一致陰影效果,不影響圖像的本身
         *               OUTER -- 圖像邊界外產生一層陰影,並且將圖像變成透明效果
         *               INNER -- 在圖像內部邊沿產生模糊效果
         * @return
         */
        paint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.INNER));
        canvas.drawRect(rectF,paint);
}
  • EmbossMaskFilter浮雕遮罩濾鏡,繪製的圖像感覺像是從屏幕中“凸”起來更有立體感一樣(在PS設計軟件中類似的效果稱之爲斜面浮雕)
        /**
         * @param direction  指定光源的位置,長度爲xxx的數組標量[x,y,z]
         * @param ambient    環境光的因子 (0~1),越接近0,環境光越暗
         * @param specular   鏡面反射係數 越接近0,鏡面反射越強
         * @param blurRadius 模糊半徑 值越大,模糊效果越明顯
         */
        paint.setMaskFilter(new EmbossMaskFilter(new float[]{1,1,1},0.2f,60,80));
        canvas.drawRect(rectF,paint);

在這裏插入圖片描述

如果應用啓用了硬件加速,看不到任何陰影效果(BlurMaskFilter,EmbossMaskFilter都需關閉硬件加速)所以現在用得比較少了。

三、使用Xfermode完成圖片混合處理

使用Paint的時候可以通過Xfermode來完成圖像組合的效果將繪製的圖形的像素和Canvas上對應位置的像素按照一定的規則進行混合,形成新的像素,再更新到Canvas中形成最終的圖形,即通過兩張圖的不同的組合模式,生成一張圖(與PS的圖層混合模式類似),

1、Xfermode模式

Xfermode主要參與角色分爲:源圖像目標圖像,Android提供了18種不同的組合形式,具體參見PorterDuff.Mode

模式 說明
ADD 飽和相加,對圖像飽和度進行相加,不常用
CLEAR 清除圖像
DARKEN 變暗,較深的顏色覆蓋較淺的顏色,若兩者深淺程度相同則混合
DST 只顯示目標圖像
DST_ATOP 在源圖像和目標圖像相交的地方繪製【目標圖像】,在不相交的地方繪製【源圖像】,相交處的效果受到源圖像和目標圖像alpha的影響
DST_IN 只在源圖像和目標圖像相交的地方繪製【目標圖像】,繪製效果受到源圖像對應地方透明度影響
DST_OUT 只在源圖像和目標圖像不相交的地方繪製【目標圖像】,在相交的地方根據源圖像的alpha進行過濾,源圖像完全不透明則完全過濾,完全透明則不過濾
DST_OVER 將目標圖像放在源圖像上方
LIGHTEN 變亮,與DARKEN相反,DARKEN和LIGHTEN生成的圖像結果與Android對顏色值深淺的定義有關
MULTIPLY 正片疊底,源圖像素顏色值乘以目標圖像素顏色值除以255得到混合後圖像像素顏色值
OVERLAY 疊加
SCREEN 濾色,色調均和,保留兩個圖層中較白的部分,較暗的部分被遮蓋
SRC 只顯示源圖像
SRC_ATOP 在源圖像和目標圖像相交的地方繪製【源圖像】,在不相交的地方繪製【目標圖像】,相交處的效果受到源圖像和目標圖像alpha的影響
SRC_IN 只在源圖像和目標圖像相交的地方繪製【源圖像】
SRC_OUT 只在源圖像和目標圖像不相交的地方繪製【源圖像】,相交的地方根據目標圖像的對應地方的alpha進行過濾,目標圖像完全不透明則完全過濾,完全透明則不過濾
SRC_OVER 將源圖像放在目標圖像上方
XOR 在源圖像和目標圖像相交的地方之外繪製它們,在相交的地方受到對應alpha和色值影響,如果完全不透明則相交處完全不繪製

從上面模式名稱可以把混合模式分爲三個類型,在Xfermode中有五個關鍵元素:

  • alpha——透明度
  • C——顏色
  • src——原圖
  • dst——目標圖
  • out——輸出

一個像素的顏色都是由四個分量ARGB組成,A表示的是Alpha值,RGB表示的是顏色,其中S表示的原像素,原像素的值用[Sa,Sc]表示,其中 Sa表示源像素的Alpha值,Sc表示源像素的顏色值。

1.1、SRC類——優先顯示的是源圖片

  • SRC [Sa, Sc] ——處理圖片相交區域時,總是顯示的是原圖片

  • SRC_IN [Sa * Da, Sc * Da] ——處理圖片相交區域時,受到目標圖片的Alpha值影響當我們的目標圖片爲空白像素的時候,目標圖片也會變成空白,即用目標圖片的透明度來改變源圖片的透明度和飽和度,當目標圖片的透明度爲0時,源圖片就不會顯示。

  • SRC_OUT [Sa * (1 - Da), Sc * (1 - Da)] —— 同SRC_IN類似 (1 - Da)
    用我們目標圖片的透明度的補值來改變源圖片的透明度和飽和度,當目標圖片的透明度爲不透明時,源圖片就不會顯示

  • SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc]——當透明度爲100%和0%時,SRC_IN 和 SRC_ATOP是通用的當透明度不爲上述的兩個值時,SRC_ATOP 比 SRC_IN 源圖像的飽和度會增

1.2、DST類——優先顯示的是目標圖片

DST_IN [Sa * Da, Sa * Dc] ----- 對比一下SRC_IN,正好和我們SRC_IN想法,在相交的時候以源圖片的透明度來改變目標圖片的透明度和飽和度
當源圖片的透明度爲0的時候,目標圖片完全不顯示

1.3、其他的疊加效果

MULTIPLY[Sa * Da, Sc * Dc] 等

2、Xfermode的簡單使用

Xfermode的應用很簡單隻需要在Paint當中調用一句代碼( paint.setXfermode(Mode),傳入不同的Xfermode子類(PorterDuff.ModePorterDuffXfermodePixelXorXfermode、AvoidXfermode)子類,簡單關注下PorterDuffXfermode 源碼:

 public class PorterDuffXfermode extends Xfermode {
          /**
           * Create an xfermode that uses the specified porter-duff mode.
           * 用一個常量類標註了16種模式, 而在這裏真正的模式是在內部美劇mode當中
           */
          public PorterDuffXfermode(PorterDuff.Mode mode) {
              porterDuffMode = mode.nativeInt;
          }
      }

應用Xfermode:

 Paint paint = new Paint();
 canvas.drawBitmap(destinationImage, 0, 0, paint);
 PorterDuff.Mode mode = PorterDuff.Mode.SRC_OUT// choose a mode
 paint.setXfermode(new PorterDuffXfermode(mode));
 canvas.drawBitmap(sourceImage, 0, 0, paint);

黃色圓形爲目標圖像、藍色矩形爲源圖像。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章