【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是一个枚举类,下面是不同模式的结合图:
观察上面的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的使用的更多相关文章
- 【Android】解析Paint类中MaskFilter的使用
目录结构: contents structure [+] EmbossMaskFilter BlurMaskFilter MaskFilter可以用来指定画笔的边缘效果.如果引用开启硬件加速的话,那么 ...
- 解析C#类中的构造函数
<解析C#类中的构造函数> 一. C#中的构造函数概述: C#中类包含数据成员和函数成员.函数成员提供了操作类中数据的某些功能,包括方法.属性.构造器和终结器.运算符和索引器. 构造函数 ...
- Android Studio查看类中所有方法和属性
ctrl+f3效果: alt+7效果: 注意区别:虽然所有方法都有,但是顺序自己一看效果便知.一个是根据类中的顺序,另一个是根据a-z的开头字母顺序. 百度查了一下快捷键是ctrl+f12.但是自己试 ...
- Android 编程 AMapLocationClientOption 类中的 setNeedAddress 方法用处 (高德地图 com.amap.api.location.AMapLocationClientOption 中的类)
最近在用高德地图来写Android App, 其中有一些 方法是不太理解的,这里写一下 对 高德地图 com.amap.api.location.AMapLocationClientOption ...
- Gson解析POJO类中的泛型参数
在开发Android与API交互的时候,使用Json格式传输,遇到了这样一个情况,返回数据格式POJO类如下: public class ApiResult<T> { private in ...
- android.graphics.Paint方法setXfermode (Xfermode x...
mPaint = new Paint(); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN)); 常见的Xfermod ...
- Android 编程 AMapLocationClientOption 类中的 setMockEnable (高德地图 com.amap.api.location.AMapLocationClientOption 中的类)
setMockEnable 高德地图中 AMapLocationClientOption 中有一个方法是设置APP是否接受模拟定位的设置,就是方法 setMockEnable //设置是否允许模拟位置 ...
- Spring5源码解析6-ConfigurationClassParser 解析配置类
ConfigurationClassParser 在ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中创建了Configur ...
- 你知道Spring是怎么解析配置类的吗?
彻底读懂Spring(二)你知道Spring是怎么解析配置类的吗? 推荐阅读: Spring官网阅读系列 彻底读懂Spring(一)读源码,我们可以从第一行读起 Spring执行流程图如下: 如果图片 ...
随机推荐
- Java实现检验一串数字的出栈合法性
题目描述: 解题思路: 判断出栈合法性的关键在于,对于每一个数,在它后面出栈且比它小的数,必是以降序排列的. 比如说3 4 2 1 5这一组数,对于第一个数 3 来说,后面比它小的数有 1.2,而在4 ...
- 比特币源码分析--C++11和boost库的应用
比特币源码分析--C++11和boost库的应用 我们先停下探索比特币源码的步伐,来分析一下C++11和boost库在比特币源码中的应用.比特币是一个纯C++编写的项目,用到了C++11和bo ...
- POJ 2823 Sliding Window (模板题)【单调队列】
<题目链接> <转载于>>> > 题目大意: 给你一段序列和一个长为k的窗口,这个窗口从最左边逐渐向右滑,直到滑到最右边,问你,该窗口在滑动的过程中,最大值和 ...
- js基础梳理-关于this常见指向问题的分析
首先,依然回顾<js基础梳理-究竟什么是执行上下文栈(执行栈),执行上下文(可执行代码)?>中的 3.执行上下文的生命周期 3.1 创建阶段 生成变量对象(Variable object, ...
- python开发之虚拟环境管理:virtualenv、virtualenvwrapper、pycharm
1 引言 进行Python开发时,多个项目可能使用到不同的依赖,例如A项目需要1.8版本的Django,而B项目需要2.0版本的Django,这时候如果没有使用虚拟环境,就需要来回卸载和安装Djang ...
- BZOJ-7-2655: calc-DP-拉格朗日插值
https://www.lydsy.com/JudgeOnline/problem.php?id=2655 以上是对 dp 一小部分打的表.dp[ i ] [ j ] 含义为 前 i 个 数 中 选 ...
- pyspider 启动错误
[root@localhost python]# pyspider all [W 180629 07:08:26 run:413] phantomjs not found, continue runn ...
- Javascript你必须要知道的面试题
1.使用 typeof bar === "object" 判断 bar 是不是一个对象有神马潜在的弊端?如何避免这种弊端? 使用 typeof 的弊端是显而易见的(这种弊端同使用 ...
- LOJ.2718.[NOI2018]归程(Kruskal重构树 倍增)
LOJ2718 BZOJ5415 洛谷P4768 Rank3+Rank1无压力 BZOJ最初还不是一道权限题... Update 2019.1.5 UOJ上被hack了....好像是纯一条链的数据过不 ...
- LeetCode(283. 移动零)
问题描述 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 说明: 必须在原数 ...