【Android】解析Paint類中Xfermode的使用
Paint類提供了setXfermode(Xfermode xfermode)方法,Xfermode指明瞭原影象和目標影象的結合方式。談到Xfermode就不得不談它的派生類PorterDuffXfermode,PorterDuffXfermode類只有一個建構函式如下:
public PorterDuffXfermode (PorterDuff.Mode mode)
建立PorterDuffXfermode例項,需要指定porter-duff模式。porter-duff實際上是Thomas Porter和Tom Duff的簡寫,他們在1984年發表的“Compositing Digital Images”文章中指出了該項發現。PorterDuff.Mode是一個列舉類,下面是不同模式的結合圖:
Paint paint = new Paint(); canvas.drawBitmap(destinationImage, 0, 0, paint); PorterDuff.Mode mode = // choose a mode paint.setXfermode(new PorterDuffXfermode(mode)); canvas.drawBitmap(sourceImage, 0, 0, paint);
接下來筆者將會展示這些模式的實現,希望能夠對你所有幫助。眾所周知,Bitmap中儲存著Canvas中繪製的資料。為了更好的展示Xfermode功能,我們首先應該建立一張透明的畫布層Canvas,然後在該畫布層上完成一些列的porter-duff模式,最後將該畫布層中的資料Bitmap繪製到螢幕層的畫布中。
//set hardware layer setLayerType(View.LAYER_TYPE_HARDWARE, null); Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth,screenHeight,Config.ARGB_8888); Canvas separate_canvas=new Cavnas(bitmap); separate_canvas.drawBitmap(destinationImage,0,0,paint); PorterDuff.Mode mode=//choose a mode paint.setXfermode(newPorterDuffXfermode(mode)); separate_canvas.drawBitmap(sourceImage,0,0,paint); //retore to software layer setLayerType(View.LAYER_TYPE_SOFTWARE,null); //drawing separate_bitmap to base canvas canvas.drawBitmap(separate_bitmap, 0, 0, paint);
首先用separate_bitmap建立一個separate_canvas,因為separate_bitmap上沒有任何資料,所以separate_canvas這時候是完全透明的,這時候我們可以在separate_canvas上順利地完成Xfermode的操作。之所以不在螢幕canvas上的進行這些操作,這是因為螢幕canvas不是無色的和透明的,預設是白色和不透明的,也就是說如果它的Bitmap在設定為Config.ARGB_8888的情況下,那麼ARGB的值就分別是255、255、255、255,這顯然將會對Xfermode的合成造成影響。如果這樣的影響是在你的預期範圍內的話,可以考慮直接繪製到螢幕canvas上。然後將separate_canvas已經繪製完的separate_bitmap資料繪製螢幕canvas的bitmap中。
canvas.drawBitmap(separate_bitmap, 0, 0, paint);
有一點不得不提就是hardware
//set hardware layer setLayerType(View.LAYER_TYPE_HARDWARE, null); //do Xfermode combine. .... //retore to software layer setLayerType(View.LAYER_TYPE_SOFTWARE,null);
檢視預設的圖層型別不是hardware,而是software。設定圖層型別型別為hardware,就會開啟硬體加速,在渲染圖形時就會強制使用android硬體渲染通道。在AndroidManifest.xml進行如下配置也會開啟硬體加速:
android:hardwareAccelerated="true"
hardware layers對於複雜圖形樹的繪製更快、更高效。不僅僅是Xfermode,任何需要進行開發複雜檢視或是快速動畫,筆者都強烈建議開啟此硬體加速。Canvas還提供了方法saveLayer,利用這個方法可以達到分層開發,它的原理圖:但是筆者不建議在開發中使用saveLayer這個方法,尤其在複製介面或是動畫中,因為saveLayer是一個非常“昂貴”的方法,它通常會佔用更多的資源。activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <ImageView android:id="@+id/image" android:background="@android:drawable/edit_text" android:src="@drawable/ic_launcher" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_alignParentTop="true"/> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/image" > <RadioGroup android:id="@+id/radioGroup1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <RadioButton android:id="@+id/radio0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="Src" /> <RadioButton android:id="@+id/radio1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Dst" /> <RadioButton android:id="@+id/radio2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SrcOver" /> <RadioButton android:id="@+id/radio3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="DstOver" /> <RadioButton android:id="@+id/radio4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SrcIn" /> <RadioButton android:id="@+id/radio5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="DstIn" /> <RadioButton android:id="@+id/radio6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SrcOut" /> <RadioButton android:id="@+id/radio7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="DstOut" /> <RadioButton android:id="@+id/radio8" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SrcATop" /> <RadioButton android:id="@+id/radio9" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="DstATop" /> <RadioButton android:id="@+id/radio10" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Xor" /> <RadioButton android:id="@+id/radio11" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Darken" /> <RadioButton android:id="@+id/radio12" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Lighten" /> <RadioButton android:id="@+id/radio13" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Multiply" /> <RadioButton android:id="@+id/radio14" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Screen" /> <RadioButton android:id="@+id/radio15" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Clear" /> </RadioGroup> </ScrollView> </RelativeLayout>
MainActivity.java
public class MainActivity extends Activity{ private int screenWidth=0; private int screenHeight=0; private PorterDuff.Mode porterduffmodel=null; private ImageView imageView=null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //initial porter-duff mode is Src porterduffmodel=PorterDuff.Mode.SRC; Point point=new Point(); getWindowManager().getDefaultDisplay().getSize(point); //width of screen screenWidth=point.x; //height of screen screenHeight=point.y; imageView= ((ImageView)findViewById(R.id.image)); setXfermodeImage(imageView); //choose src findViewById(R.id.radio0).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.SRC; setXfermodeImage(imageView); } }); //choose Dst findViewById(R.id.radio1).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.DST; setXfermodeImage(imageView); } }); //choose SrcOver findViewById(R.id.radio2).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.SRC_OVER; setXfermodeImage(imageView); } }); //choose DstOver findViewById(R.id.radio3).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.DST_OVER; setXfermodeImage(imageView); } }); //choose SrcIn findViewById(R.id.radio4).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.SRC_IN; setXfermodeImage(imageView); } }); //choose DstIn findViewById(R.id.radio5).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.DST_IN; setXfermodeImage(imageView); } }); //choose SrcOut findViewById(R.id.radio6).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.SRC_OUT; setXfermodeImage(imageView); } }); //choose DstOut findViewById(R.id.radio7).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.DST_OUT; setXfermodeImage(imageView); } }); //choose SrcATop findViewById(R.id.radio8).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.SRC_ATOP; setXfermodeImage(imageView); } }); //choose DstATop findViewById(R.id.radio9).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.DST_ATOP; setXfermodeImage(imageView); } }); //choose Xor findViewById(R.id.radio10).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.XOR; setXfermodeImage(imageView); } }); //choose Darken findViewById(R.id.radio11).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.DARKEN; setXfermodeImage(imageView); } }); //choose lighten findViewById(R.id.radio12).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.LIGHTEN; setXfermodeImage(imageView); } }); //choose multiply findViewById(R.id.radio13).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.MULTIPLY; setXfermodeImage(imageView); } }); //choose screen findViewById(R.id.radio14).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.SCREEN; setXfermodeImage(imageView); } }); //clear findViewById(R.id.radio15).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { porterduffmodel=PorterDuff.Mode.CLEAR; setXfermodeImage(imageView); } }); } private void setXfermodeImage(ImageView imageView){ //open hardware accelerate rendering, it's software rendering default. imageView.setLayerType(View.LAYER_TYPE_HARDWARE, null); Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888); Canvas separate_canvas=new Canvas(separate_bitmap); Bitmap destination_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888); //create destination canvas Canvas destination_canvas=new Canvas(destination_bitmap); Paint destination_paint=new Paint(); destination_paint.setColor(Color.YELLOW); destination_paint.setStyle(Style.FILL); //drawing destination circle to destination canvas destination_canvas.drawCircle(screenWidth/2,screenHeight/4,screenHeight/8,destination_paint); Bitmap source_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888); //create source canvas Canvas source_canvas=new Canvas(source_bitmap); Paint source_paint=new Paint(); source_paint.setColor(Color.BLUE); source_paint.setStyle(Style.FILL); //drawing source rectangle to source canvas source_canvas.drawRect(screenWidth/8, screenHeight/4, screenWidth/2, 7*screenHeight/16, source_paint); Paint separate_paint=new Paint(); separate_canvas.drawBitmap(destination_bitmap, 0, 0, separate_paint); separate_paint.setXfermode(new PorterDuffXfermode(porterduffmodel)); separate_canvas.drawBitmap(source_bitmap, 0, 0,separate_paint); //set separate_bitmap to target view imageView.setImageBitmap(separate_bitmap); //retrieve layer type to software layer imageView.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null); } }
效果圖:
利用這些功能,我們可以輕易的完成一些較為複雜的功能。
接下來筆者實現一個繪製圓形頭像的功能,DstIn只會繪製目標圖形中與源影象相交的部分。因此我們只需要設定源影象為圓圈,目標影象設定為圖片,然後再使用DstIn便可以完成顯示圓頭像的功能了。
public class MainActivity extends Activity { int screenWidth=0; int screenHeight=0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Point point=new Point(); getWindowManager().getDefaultDisplay().getSize(point); //initialize screen width screenWidth=point.x; //initialize screen height screenHeight=point.y; setContentView(new MyView(this)); } class MyView extends View{ Bitmap destination_bitmap=null; public MyView(Context context){ super(context); destination_bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.scene); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //create an empty separate_bitmap and a separate_canvas Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888); Canvas separate_canvas=new Canvas(separate_bitmap); //create an empty source canvas Bitmap source_bitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888); Canvas source_canvas=new Canvas(source_bitmap); //drawing a circle on source_bitmap Paint source_paint=new Paint(); source_paint.setStyle(Style.FILL); source_canvas.drawCircle(screenWidth/2,screenHeight/4,200,source_paint); Paint paint=new Paint(); //drawing destination_bitmap to separate_canvas separate_canvas.drawBitmap(destination_bitmap, 0, 0, paint); //set porter-duff mode is DST_IN paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); //drawing source_bitmap to separate_canvas separate_canvas.drawBitmap(source_bitmap, 0, 0, paint); //drawing separate_bitmap to screen canvas canvas.drawBitmap(separate_bitmap, 0, 0, new Paint()); } } }
效果圖:
最後筆者簡單展示一下,如何利用DST_OUT模式,實現刮刮樂的功能。
public class MainActivity extends Activity { int screenWidth=0; int screenHeight=0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Point point=new Point(); getWindowManager().getDefaultDisplay().getSize(point); //screen width screenWidth=point.x; //screen height screenHeight=point.y; setContentView(new MyView(this)); } class MyView extends View{ private Bitmap resultBitmap=null; private Bitmap showBitmap=null; private Bitmap tempBitmap=null; private Canvas tempCanvas=null; private float start_x = 0; private float start_y = 0; private float end_x=0; private float end_y=0; private Paint paint=null; private Path path=null; public MyView(Context context){ super(context); //create result bitmap with R.drawable.result which representing the answer. resultBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.result); //create show bitmap with R.drawable.show which used to cover answer. showBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.show); tempBitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888); tempCanvas=new Canvas(tempBitmap); tempCanvas.drawBitmap(showBitmap, 0, 0, new Paint()); path=new Path(); paint=new Paint(); paint.setStrokeWidth(20); paint.setStyle(Style.STROKE); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); paint.setStrokeCap(Paint.Cap.ROUND); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(resultBitmap, 0, 0,new Paint()); canvas.drawBitmap(tempBitmap, 0, 0, new Paint()); } @Override public boolean onTouchEvent(MotionEvent event) { if(event.getAction()==MotionEvent.ACTION_DOWN){ start_x=event.getX(); start_y=event.getY(); path.moveTo(start_x, start_y);//setting start point }else if( event.getAction()==MotionEvent.ACTION_MOVE || event.getAction()==MotionEvent.ACTION_UP){ end_x=event.getX(); end_y=event.getY(); path.lineTo(end_x,end_y); tempCanvas.drawPath(path,paint); start_x=end_x; start_y=end_y; postInvalidate(); } return true; } } }
效果圖:
這篇Blog並未詳細的講解每種模式的詳細合成模式過程,關於這些讀者可以到Google官方文件查閱。最後,希望這篇可以對你所有幫助。