一、GPU硬件加速
GPU英文全称Graphic Processing Unit,中文翻译为“图形处理器”。与CPU不同,GPU是专门为处理图形任务而产生的芯片。
在GPU出现之前,CPU一直负责着所有的运算工作,CPU的架构是有利于X86指令集的串行架构,CPU从设计思路上适合尽可能快的完成一个任务。但当面对类似多媒体、图形图像处理类型的任务时,就显得力不从心。因为在多媒体计算中通常要求更高的运算密度、多并发线程和频繁地存储器访问;显然当你打游戏时,屏幕上的动画是需要实时刷新的,这些都需要频繁的计算、存取动作;如果CPU不能及时响应,那么屏幕就会显得很卡……你的队友可能会发一句……我等的花都谢了,你咋还不动呢……
为了专门处理多媒体的计算、存储任务,GPU就应运而生了,GPU中自带处理器和存储器,以用来专门计算和存储多媒体任务。
对于Andorid来讲,在API 11之前是没有GPU的概念的,在API 11之后,在程序集中加入了对GPU加速的支持,在API 14之后,硬件加速是默认开启的!我们可以显式地强制图像计算时使用GPU而不使用CPU.
在GPU加速时,实际是使用OpenGL的函数来完成绘制的。
所以使用GPU加速的优点显而易见:硬件加速提高了Android系统显示和刷新的速度;
它有缺点也显而易见:
- 兼容性问题:由于是将绘制函数转换成OpenGL命令来绘制,定然会存在OpenGL并不能完全支持原始绘制函数的问题,所以这就会造成在打开GPU加速时,效果会失效的问题。
- 内存消耗问题:由于需要OpenGL的指令,所以需要把系统中的OpenGL相关的包加载到内存中来,所以单纯OpenGL API调用就会占用8MB,而实际上会占用更多内存;
- 电量消耗问题:多使用了一个部件,当然会更耗电……
禁用GPU硬件加速方法
那么问题就来了,如果你的APP跑在API 14版本以后,而你洽好要用那些不支持硬件加速的函数要怎么办?
那就只好禁用硬件加速喽,针对不同类型的东东,Android给我们提供了不同的禁用方法:
硬件加速分全局(Application)、Activity、Window、View 四个层级
1.在AndroidManifest.xml文件为application标签添加如下的属性即可为整个应用程序开启/关闭硬件加速:
<application android:hardwareAccelerated="true" …>
2.在Activity 标签下使用 hardwareAccelerated 属性开启或关闭硬件加速:
<activity android:hardwareAccelerated="false" />
在Window 层级使用如下代码开启硬件加速:(Window层级不支持关闭硬件加速)
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
4.View 级别如下关闭硬件加速:(view 层级上不支持开启硬件加速)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
或者使用android:layerType=”software”来关闭硬件加速:比如
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:paddingLeft="2dp"
android:layerType="software"
android:paddingRight="2dp" >
二、PorterDuffXfermode
Xfermode国外有大神称之为过渡模式,这种翻译比较贴切但恐怕不易理解,大家也可以直接称之为图像混合模式,因为所谓的“过渡”其实就是图像混合的一种。查看API文档发现其果然有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode。
由于AvoidXfermode, PixelXorXfermode都已经被标注为过时了,所以这次主要研究的是仍然在使用的PorterDuffXfermode。
从上面可以看出,派生自Xfermode的有AvoidXfermode,PixelXorXfermode,PorterDuffXfermode;
从硬件加速不支持的函数列表中,我们可以看到AvoidXfermode,PixelXorXfermode是完全不支持的,而PorterDuffXfermode是部分不支持的。
所以在使用Xfermode时,为了保险起见,我们需要做两件事:
1、禁用硬件加速:
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
2、使用离屏绘制
//新建图层
int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG);
//TODO 核心绘制代码
//还原图层
canvas.restoreToCount(layerID);
我们需要把绘制的核心代码放在saveLayer()和restoreToCount()之间即可。
PorterDuffXfermode类同样有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),虽说构造方法的签名列表里只有一个PorterDuff.Mode的参数,但是它可以实现很多酷毙的图形效果!!而PorterDuffXfermode就是图形混合模式的意思,其概念最早来自于SIGGRAPH的Tomas Proter和Tom Duff,混合图形的概念极大地推动了图形图像学的发展,延伸到计算机图形图像学像Adobe和AutoDesk公司著名的多款设计软件都可以说一定程度上受到影响,而我们PorterDuffXfermode的名字也来源于这俩人的人名组合PorterDuff,那PorterDuffXfermode能做些什么呢?我们先来看一张API DEMO里的图片:
这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:
- 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和色值影响,如果完全不透明则相交处完全不绘制
demo
自定义view(注意:这里的代码是有问题的,后面详细讲解)
public class XfermodeView extends View {
Paint paint;
int width;
int height;
public XfermodeView(Context context) {
super(context);
init();
}
public XfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//初始化画笔
paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
width = 200;
height = 200;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//禁用硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//使用离屏绘制
int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), paint, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
canvas.drawBitmap(createDstBitmap2(width, height), width, 0, paint);
canvas.drawBitmap(createSrcBitmap(width, height), width / 2, height / 2, paint);
canvas.restoreToCount(layerID);
}
public Bitmap createDstBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scrPaint.setColor(0xFFFFCC44);
canvas.drawCircle(width / 2, height / 2, width / 2, scrPaint);
return bitmap;
}
public Bitmap createDstBitmap2(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scrPaint.setColor(0xFFFF0033);
canvas.drawCircle(width / 2, height / 2, width / 2, scrPaint);
return bitmap;
}
public Bitmap createSrcBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dstPaint.setColor(0xFF66AAFF);
canvas.drawRect(new Rect(0, 0, width, height), dstPaint);
return bitmap;
}
}
显示效果如下:
这是没有任何图像混合的原图。
下面修改关键代码:
canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
canvas.drawBitmap(createDstBitmap2(width, height), width, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST));
canvas.drawBitmap(createSrcBitmap(width, height), width / 2, height / 2, paint);
paint.setXfermode(null);
首先要清楚,Xfermode操作的对象的是源图像,没有操作的是目标图像。
下面分别对各个模式进行试验:
- ADD:饱和相加,对图像饱和度进行相加,不常用
- CLEAR:清除图像
清除源图像所覆盖的区域。 - DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
- DST:只显示目标图像
- DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
- DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
到这里出现问题了,预想的效果和实际出来的效果不一致。经过和官方demo的仔细对比,终于找到问题所在,画图的代码如下:
canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
canvas.drawBitmap(createDstBitmap2(width, height), width, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(createSrcBitmap(width, height), width / 2, height / 2, paint);
paint.setXfermode(null);
问题就在drawBitmap的时候指定了left和top(canvas.translate()方法一样的问题),借用一张图:
Xfermode作用的是canvasBitmap与canvas构造中bitmap取出不同的交、并、补的区域作为最终的bitmap显示出来,我理解就是Xfermode之后作用于bitmap本身,对于额外的位置不能感知,所以导致上面的绘图出现问题。修改后的代码如下:
public class XfermodeView extends View {
Paint paint;
int width;
int height;
public XfermodeView(Context context) {
super(context);
init();
}
public XfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//初始化画笔
paint = new Paint();
// paint.setColor(Color.RED);
// paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setFilterBitmap(false);
width = 200;
height = 200;
}
@Override
protected void onDraw(Canvas canvas) {
//禁用硬件加速
// setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//使用离屏绘制
int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
canvas.drawBitmap(createDstBitmap2(width, height), 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(createSrcBitmap(width, height), 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(layerID);
}
public Bitmap createDstBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scrPaint.setColor(0xFFFFCC44);
canvas.drawOval(new RectF(0, 0, width , height), scrPaint);
return bitmap;
}
public Bitmap createDstBitmap2(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(2 * width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
scrPaint.setColor(0xFFFF0033);
canvas.drawOval(new RectF(width, 0, 2 * width , height), scrPaint);
return bitmap;
}
public Bitmap createSrcBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width / 2 + width, height / 2 + height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dstPaint.setColor(0xFF66AAFF);
canvas.drawRect(new Rect(width / 2, height / 2, width / 2 + width, height / 2 + height),
dstPaint);
return bitmap;
}
}
效果如下:
注意这个效果是没有毛病的,可能有人会说,右边圆多显示了半圆,其实不然,我把布局放出来:
为了显示出层级关系,我把部分框稍微画大了一点。因为源图像与目标图像2的右半部分没有相交,所以对那部分没有影响,该怎么样显示就怎么样显示。不信我们修改一行代码:
public Bitmap createSrcBitmap(int width, int height) {
// 把这个Bitmap的宽加大一点,包容所有的目标图像
Bitmap bitmap = Bitmap.createBitmap(width + width, height / 2 + height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dstPaint.setColor(0xFF66AAFF);
canvas.drawRect(new Rect(width / 2, height / 2, width / 2 + width, height / 2 + height),
dstPaint);
return bitmap;
}
显示结果如下:
- 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和色值影响,如果完全不透明则相交处完全不绘制
参考
Android Paint Xfermode 学习小结
Android高级进阶——绘图篇(五)setXfermode 设置混合模式