編輯:關於Android編程
這篇文章將逐個講解每個模式的意義。這裡所講的各種模式,在大家理解了之後可以回過頭來看看setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.XXXX));中的混合過程,其實在PorterDuffColorFilter中的混合過程與這裡的setXfermode()設置混合模式的計算方式和效果是完全相同的,只是在PorterDuffColorFilter中只能使用純色彩,而且是完全覆蓋在圖片上方;而setXfermode()則不同,它只會在目標圖像和源圖像交合的位置起作用,而且源圖像不一定是純色的。
在開始講解之前,我們隨便拿一個效果圖來看一下,我們在這個效果圖中需要關注哪兩點
對應代碼:
canvas.drawBitmap(dstBmp, 0, 0, mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN)); canvas.drawBitmap(srcBmp,width/2,height/2, mPaint);前面在講canvas的髒區域更新時,已經提到,在最後一句計算效果圖像時,是以源圖像所在區域為計算目標的,把計算後的源圖像更新到對應區域內。
public class MyView extends View { private int width = 400; private int height = 400; private Bitmap dstBmp; private Bitmap srcBmp; private Paint mPaint; public MyView(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); srcBmp = makeSrc(width, height); dstBmp = makeDst(width, height); mPaint = new Paint(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerID = canvas.saveLayer(0,0,width*2,height*2,mPaint,Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(dstBmp, 0, 0, mPaint); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); canvas.drawBitmap(srcBmp,width/2,height/2, mPaint); mPaint.setXfermode(null); canvas.restoreToCount(layerID); } // create a bitmap with a circle, used for the "dst" image static Bitmap makeDst(int w, int h) { Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm); Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); p.setColor(0xFFFFCC44); c.drawOval(new RectF(0, 0, w, h), p); return bm; } // create a bitmap with a rect, used for the "src" image static Bitmap makeSrc(int w, int h) { Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm); Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); p.setColor(0xFF66AAFF); c.drawRect(0, 0, w, h, p); return bm; } }與上篇文章唯一不同的地方在於,只是把模式改為了Mode.ADD
從效果圖中可以看出,只有源圖與目標圖像相交的部分的圖像的飽和度產生了變化,沒相交的部分是沒有變的,因為對方的飽和度是0,當然不相交的位置飽和度是不會變的。
這個模式的應用范圍比較少,暫時想不到哪裡會用到;
這個效果比較容易理解,兩個圖像重合的區域才會有顏色值變化,所以只有重合區域才有變亮的效果,源圖像非重合的區域,由於對應區域的目標圖像是空白像素,所以直接顯示源圖像。
大家對公式其實不必理解,用過Photoshop的同學,應該都知道圖層模式裡的“變亮”模式,在博客《自定義控件三部曲之繪圖篇(九)——Paint之setColorFilter》中已經講過在photoshop中的操作方法。
所以大家想知道結果,直接拿目標圖像和源圖像在photoshop中模擬一下就可以得到結果;
我們在實際應用中,會有下面的這個情況,當選中一本書時,給這本書加上燈光效果
其實它是兩張圖合成的:
DST:目標圖像
SRC:源圖像
可以看到,在這張圖片的最上方中間的位置有些白色半透明的填充,其它位置都是透明的。
下面我們來看一下代碼:
public class LightBookView extends View { private Paint mBitPaint; private Bitmap BmpDST,BmpSRC; public LightBookView(Context context, AttributeSet attrs) { super(context, attrs); mBitPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.book_bg,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.book_light,null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); //先畫書架,做為目標圖像 canvas.drawBitmap(BmpDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN)); //再圖光點 canvas.drawBitmap(BmpSRC,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } }意思就是先把書架做為目標圖像畫在底層,然後給mBitPaint設置上PorterDuffXfermode,然後將處理過的源圖蓋在目標圖像上。代碼難度不大,就不再細講。
同樣是對應photoshop中的混合模式中的變暗模式;同樣大家可以在photoshop中就可以得到效果,就不再細講了。
有些同學會奇怪了,Photoshop中也有正片疊底啊,相交區域正片疊底後的顏色確實是綠色的,但源圖像的非相交區域怎麼沒了?
我們來看下他的計算公式:[Sa * Da, Sc * Dc],在計算alpha值時的公式是Sa * Da,是用源圖像的alpha值乘以目標圖像的alpha值;由於源圖像的非相交區域所對應的目標圖像像素的alpha是0,所以結果像素的alpha值仍是0,所以源圖像的非相交區域在計算後是透明的。
在兩個圖像的相交區域的混合方式是與photoshop中的正片疊底效果是一致的。
雖然沒有給出公式,但從效果圖中可以看到,源圖像交合部分有效果,非交合部分依然是存在的,這就可以肯定一點,當目標圖像透明時,在這個模式下源圖像的色值不會受到影響;
同樣,只是源圖像與目標圖像交合部分有效果,源圖像非交合部分保持原樣。
到這裡,這六種混合模式就講完了,我們下面總結一下:
1、這幾種模式都是PhotoShop中存在的模式,是通過計算改變交合區域的顏色值的
2、除了Mode.MULTIPLY(正片疊底)會在目標圖像透明時將結果對應區域置為透明,其它圖像都不受目標圖像透明像素影響,即源圖像非交合部分保持原樣。
圖二:
然後完成的效果如下:
我們先想想這個要實現的效果有哪些特性:
首先,
在圖一中,小鳥整個都是藍色的
在圖二中,只有小鳥的邊緣部分是白色的,中間部分是透明的。
在最終的合成圖中:圖一和圖二中小鳥與邊緣的是顯示的,而且還有某種效果,但小鳥中間的區域變透明了!顯示的是底部Activity的背景色。
想到我們前面學到的幾種樣式中,只有Mode.MULTIPLY(正片疊底)會在兩個圖像的一方透明時,結果像素就是透明的。所以這裡使用的模式就是Mode.MULTIPLY
對應代碼如下:
public class TwitterView extends View { private Paint mBitPaint; private Bitmap BmpDST,BmpSRC; public TwitterView(Context context, AttributeSet attrs) { super(context, attrs); mBitPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.twiter_bg,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.twiter_light,null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); canvas.drawBitmap(BmpSRC,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } }源碼在文章底部給出
大家注意SRC_IN模式與SRC模式的區別,一般而言,是在相交區域時無論SRC_IN還是SRC模式都是顯示源圖像,而唯一不同的是,當目標圖像是空白像素時,在SRC_IN所對應的區域也將會變成空白像素;
其實更嚴格的來講,SRC_IN模式是在相交時利用目標圖像的透明度來改變源圖像的透明度和飽和度。當目標圖像透明度為0時,源圖像就完全不顯示。
利用這個特性,我們能完成很多功能,比如:
圖像二:(去角遮罩)
效果為:
可以看到這個遮罩的四個角都是圓形切角,都是透明的。
現在我們需要利用SRC_IN的特性,顯示SRC圖像,但會把目標圖像中空白像素的部分去掉的特性來做了。
由於我們需要最終顯示小狗圖像,所以這裡需要將小狗 圖像做為SRC,將遮罩做為目標圖像
代碼為:
public class RoundImageView_SRCIN extends View { private Paint mBitPaint; private Bitmap BmpDST,BmpSRC; public RoundImageView_SRCIN(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.dog_shade,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.dog,null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(BmpSRC,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } }
用到的遮罩為:
這個遮罩好像還不太清,它是一個從上到下的白色填充漸變;白色的透明度從49%到0;
對應的代碼為:
public class InvertedImageView_SRCIN extends View{ private Paint mBitPaint; private Bitmap BmpDST,BmpSRC,BmpRevert; public InvertedImageView_SRCIN(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.dog_invert_shade,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.dog,null); Matrix matrix = new Matrix(); matrix.setScale(1F, -1F); // 生成倒影圖 BmpRevert = Bitmap.createBitmap(BmpSRC, 0, 0, BmpSRC.getWidth(), BmpSRC.getHeight(), matrix, true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先畫出小狗圖片 canvas.drawBitmap(BmpSRC,0,0,mBitPaint); //再畫出倒影 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.translate(0,BmpSRC.getHeight()); canvas.drawBitmap(BmpDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(BmpRevert,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } }代碼中稍微有點難度的地方,可能是在利用小狗圖片生成對應的倒影圖像的位置:
Matrix matrix = new Matrix(); matrix.setScale(1F, -1F); // 生成倒影圖 BmpRevert = Bitmap.createBitmap(BmpSRC, 0, 0, BmpSRC.getWidth(), BmpSRC.getHeight(), matrix, true);這裡主要是利用matrix將圖像做一個翻轉,有關位移matrix的知識,我們會在講解canvas的變換時細講,這裡就不再講解了,繼續關注吧。
從示例圖中也可以看出,源圖像與目標圖像的相交部分由於目標圖像的透明度為100%,所以相交部分的計算結果為空白像素。在目標圖像為空白像素時,完全以源圖像顯示。
所以這個模式的特性可以概括為:以目標圖像的透明度的補值來調節源圖像的透明度和色彩飽和度。即當目標圖像為空白像素時,就完全顯示源圖像,當目標圖像的透明度為100%時,交合區域為空像素。
Mode.SRC_OUT簡單來說,當目標圖像有圖像時結果顯示空白像素,當目標圖像沒有圖像時,結果顯示源圖像。
原理:我們說了簡單來講Mode.SRC_OUT模式,當目標圖像有圖像時計算結果為空白像素,當目標圖像沒有圖像時,顯示源圖像;
所以我們把手指軌跡做為目標圖像,在與源圖像計算時,有手指軌跡的地方就變為空白像素了,看起來的效果就是被擦除了。
代碼如下:
public class DogView_SRCOUT extends View { private Paint mBitPaint; private Bitmap BmpDST,BmpSRC; private Path mPath; private float mPreX,mPreY; public DogView_SRCOUT(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); mBitPaint.setColor(Color.RED); mBitPaint.setStyle(Paint.Style.STROKE); mBitPaint.setStrokeWidth(45); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.dog,null); BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888); mPath = new Path(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); //先把手指軌跡畫到目標Bitmap上 Canvas c = new Canvas(BmpDST); c.drawPath(mPath,mBitPaint); //然後把目標圖像畫到畫布上 canvas.drawBitmap(BmpDST,0,0,mBitPaint); //計算源圖像區域 mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); canvas.drawBitmap(BmpSRC,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mPath.moveTo(event.getX(),event.getY()); mPreX = event.getX(); mPreY = event.getY(); return true; case MotionEvent.ACTION_MOVE: float endX = (mPreX+event.getX())/2; float endY = (mPreY+event.getY())/2; mPath.quadTo(mPreX,mPreY,endX,endY); mPreX = event.getX(); mPreY =event.getY(); break; case MotionEvent.ACTION_UP: break; } postInvalidate(); return super.onTouchEvent(event); } }
然後再搞個中獎結果:(guagua_text.png)
結果如下:
對應代碼為:
public class GuaGuaCardView_SRCOUT extends View{ private Paint mBitPaint; private Bitmap BmpDST,BmpSRC,BmpText; private Path mPath; private float mPreX,mPreY; public GuaGuaCardView_SRCOUT(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); mBitPaint.setColor(Color.RED); mBitPaint.setStyle(Paint.Style.STROKE); mBitPaint.setStrokeWidth(45); BmpText = BitmapFactory.decodeResource(getResources(),R.drawable.guaguaka_text,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.guaguaka_pic,null); BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888); mPath = new Path(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(BmpText,0,0,mBitPaint); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); //先把手指軌跡畫到目標Bitmap上 Canvas c = new Canvas(BmpDST); c.drawPath(mPath,mBitPaint); //然後把目標圖像畫到畫布上 canvas.drawBitmap(BmpDST,0,0,mBitPaint); //計算源圖像區域 mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); canvas.drawBitmap(BmpSRC,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: mPath.moveTo(event.getX(),event.getY()); mPreX = event.getX(); mPreY = event.getY(); return true; case MotionEvent.ACTION_MOVE: float endX = (mPreX+event.getX())/2; float endY = (mPreY+event.getY())/2; mPath.quadTo(mPreX,mPreY,endX,endY); mPreX = event.getX(); mPreY =event.getY(); break; case MotionEvent.ACTION_UP: break; } postInvalidate(); return super.onTouchEvent(event); } }與上面橡皮擦效果不同的是,在繪圖時,在特效前先把刮刮卡的中獎文字繪在底部,這時候當橡皮擦把刮刮卡的圖片給擦除掉時,就露出底部的刮刮卡的中獎文字了。
很奇怪,它的效果圖竟然與SRC_IN模式是相同的,我們來對比一下它們的公式:
SRC_IN: [Sa * Da, Sc * Da]
SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]
先看透明度:在SRC_IN中是Sa * Da,在SRC_ATOP是Da
SRC_IN是源圖像透明度乘以目標圖像的透明度做為結果透明度,而SRC_ATOP是直接使用目標圖像的透明度做為結果透明度
再看顏色值:
SRC_IN的顏色值為 Sc * Da,SRC_ATOP的顏色值為Sc * Da + (1 - Sa) * Dc;SRC_ATOP在SRC_IN的基礎上還增加了(1 - Sa) * Dc;
所以總結來了:
1、當透明度只有100%和0%時,SRC_ATOP是SRC_IN是通用的
2、當透明度不只有100%和0%時,SRC_ATOP相比SRC_IN源圖像的飽和度會增加,即會顯得更亮!
所以,前面利用SRC_IN實現的圓角效果是完全可以使用SRC_OUT模式來實現的
代碼中僅將SRC_IN模式改為SRC_OUT模式即可:
public class RoundImageView_SRCOUT extends View { private Paint mBitPaint; private Bitmap BmpDST,BmpSRC; public RoundImageView_SRCOUT(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.dog_shade,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.dog,null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BmpDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); canvas.drawBitmap(BmpSRC,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } }
public class InvertedImageView_SRCOUT extends View{ private Paint mBitPaint; private Bitmap BmpDST,BmpSRC,BmpRevert; public InvertedImageView_SRCOUT(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.dog_invert_shade,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.dog,null); Matrix matrix = new Matrix(); matrix.setScale(1F, -1F); // 生成倒影圖 BmpRevert = Bitmap.createBitmap(BmpSRC, 0, 0, BmpSRC.getWidth(), BmpSRC.getHeight(), matrix, true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先畫出小狗圖片 canvas.drawBitmap(BmpSRC,0,0,mBitPaint); //再畫出倒影 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.translate(0,BmpSRC.getHeight()); canvas.drawBitmap(BmpDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(BmpRevert,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } }效果圖如下:
然後再來看看原來SRC_IN的效果圖,對比一下:
明顯亮度是有增加的。
所以對於SRC_ATOP的結論就出來了,一般而言SRC_ATOP是可以和SRC_IN通用的,但SRC_ATOP所產生的效果圖在目標圖不是透明度不是0或100%的時候,會比SRC_IN模式產生的圖像更亮些;
好了,這篇就先到這裡了,下篇繼續給大家講解xfermode的應用。
本文考慮把賬單界面整理下,實現如下圖中的功能。做之前感覺應該不難,但實際
前言最近在項目中遇到一個問題,要求顯示下面的效果。如圖所示,“所屬農莊”必須緊挨在“商品名字”後面,但當商品名
仿微信相冊選擇圖片,查看大圖,寫的不太好,希望評論指出不足,諒解,先介紹一下我的基本思路第一步獲取手機上的所有圖片路徑: Uri
在上篇文章的例子中,我們使用了一張圖片和一個文本作為每一行的數據,發現效果已經完全達到了,而且沒出現什麼問題。但如果我們將Item的數量調大,比如調到1000、10000