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是一个枚举类,下面是不同模式的结合图:

观察上面的16种模式。我们可以把一副图像分成两部分透明度(Alpha)和颜色(Color),源图像透明度(Alphasrc)和目标图像透明度(Alphadst)按照某种合成关系就产生了结果图像透明度(Alphaout),源图像颜色(Colorsrc)和目标图像颜色(Colordst)按照某种合成关系就产生了结果图像颜色(Colorout),然后组合Alphaout和Colorout就可以得到图像的一个像素点值了。这里的合成关系依据不同的PorterDuff.Mode会规定不同的合成关系。

在官方文档中,对每种模式的合成关系有更为详细的描述: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(new PorterDuffXfermode(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(上面提过屏幕的canvas默认是白色的和不透明的),这显然将会对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官方文档查阅。最后,希望这篇可以对你所有帮助。

【Android】解析Paint类中Xfermode的使用的更多相关文章

  1. 【Android】解析Paint类中MaskFilter的使用

    目录结构: contents structure [+] EmbossMaskFilter BlurMaskFilter MaskFilter可以用来指定画笔的边缘效果.如果引用开启硬件加速的话,那么 ...

  2. 解析C#类中的构造函数

    <解析C#类中的构造函数> 一.  C#中的构造函数概述: C#中类包含数据成员和函数成员.函数成员提供了操作类中数据的某些功能,包括方法.属性.构造器和终结器.运算符和索引器. 构造函数 ...

  3. Android Studio查看类中所有方法和属性

    ctrl+f3效果: alt+7效果: 注意区别:虽然所有方法都有,但是顺序自己一看效果便知.一个是根据类中的顺序,另一个是根据a-z的开头字母顺序. 百度查了一下快捷键是ctrl+f12.但是自己试 ...

  4. Android 编程 AMapLocationClientOption 类中的 setNeedAddress 方法用处 (高德地图 com.amap.api.location.AMapLocationClientOption 中的类)

    最近在用高德地图来写Android App, 其中有一些 方法是不太理解的,这里写一下 对  高德地图  com.amap.api.location.AMapLocationClientOption ...

  5. Gson解析POJO类中的泛型参数

    在开发Android与API交互的时候,使用Json格式传输,遇到了这样一个情况,返回数据格式POJO类如下: public class ApiResult<T> { private in ...

  6. android.graphics.Paint方法setXfermode (Xfermode x...

    mPaint = new Paint(); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN)); 常见的Xfermod ...

  7. Android 编程 AMapLocationClientOption 类中的 setMockEnable (高德地图 com.amap.api.location.AMapLocationClientOption 中的类)

    setMockEnable 高德地图中 AMapLocationClientOption 中有一个方法是设置APP是否接受模拟定位的设置,就是方法 setMockEnable //设置是否允许模拟位置 ...

  8. Spring5源码解析6-ConfigurationClassParser 解析配置类

    ConfigurationClassParser 在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了Configur ...

  9. 你知道Spring是怎么解析配置类的吗?

    彻底读懂Spring(二)你知道Spring是怎么解析配置类的吗? 推荐阅读: Spring官网阅读系列 彻底读懂Spring(一)读源码,我们可以从第一行读起 Spring执行流程图如下: 如果图片 ...

随机推荐

  1. Java大数统计-hdu1316

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1316 题目描述: 给你一个范围,问你在这个范围内有多少斐波拉契数. 代码实现: import java ...

  2. OSFPv3的配置

    实验目的 1.  掌握 OSPFv3 的配置方法 2.  掌握在帧中继环境下 OSPFv3 的配置方法 3.  掌握 OSPFv3 NSSA 的配置方法 4.  掌握外部路由汇总的配置 5.  掌握区 ...

  3. switch反汇编

    以下总结为debug模式

  4. 关闭VS2015的WPF UI调试工具

    VS菜单: 工具 > 选项 > 调试 > 常规 > 启用Xaml 的UI调试工具.把勾勾去掉.

  5. 2019-1-23IntelliJ IDEAget的使用教程及出现的问题

    第一条:快捷键: Ctrl+Alt+h:显示调用当前方法的所有位置. Ctrl+Alt+B:跳转到方法实现处 自动修正,我这是 Alt + L Ctrl+Enter,导入包,自动修正Ctrl+Alt+ ...

  6. BZOJ.4820.[SDOI2017]硬币游戏(思路 高斯消元 哈希/AC自动机/KMP)

    BZOJ 洛谷 建出AC自动机,每个点向两个儿子连边,可以得到一张有向图.参照 [SDOI2012]走迷宫 可以得到一个\(Tarjan\)+高斯消元的\(O((nm)^3)\)的做法.(理论有\(6 ...

  7. bzoj 泛做

    3003 这个题是这样的,对序列差分后,每个取反操作就是给两个端点的值取反,然后背包之后再状压就好了 4128 这题棒棒的QAQBSGS 23333 4176 这个杜教筛呃呃呃大爷链接 3028 我要 ...

  8. Windows下bat脚本自动发邮件

    摘要:说明:很多木马会利用自身的程序截取系统敏感文件或信息发往指定的邮箱,而blat并不是木马,它小巧却有强大的发邮件功能,可不要用它做违法事,感觉和木马功能有一拼!下面先看个具体的实例(在blat同 ...

  9. STM32(HY-SRF05)超声波测距项目

    参考资料: https://www.cnblogs.com/qsyll0916/p/6964638.html http://blog.csdn.net/zhangdaxia2/article/deta ...

  10. [PA2014]Zadanie

    [PA2014]Zadanie 题目大意: 一棵\(n(n\le3\times10^5)\)个点的树,每个点上有\(a_i\)个人.树上所有人到\(i\)号点距离之和为\(b_i\).已知\(\{b_ ...