PhotoView大图绘制——硬件加速限制
Posted David-Kuper
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PhotoView大图绘制——硬件加速限制相关的知识,希望对你有一定的参考价值。
最近在做PhotoView图片的效果定制时,在加载展示图片情境下,统一把图片按照屏幕宽度作为固定值,计算宽高的缩放比然后对Bitmap进行伸缩,这样可以避免一般情况下的大图加载产生——OOM和trying to draw too large(xxxbytes) bitmap的问题。
当即便这样,也还是会有加载的图片尺寸超过限制的时候,就经常会看到这个warning,图片显示不出来:
Bitmap too large to be uploaded into a texture (1080x9431, max=8192x8192)
Bitmap too large to be uploaded into a texture (1080x9431, max=8192x8192)
因为指定的比例的按照屏幕宽度进行宽高比缩放,宽度到了合适的范围,但是当图片太长的时候,还是会触发上面的问题,无法显示图片。
方案一:关闭硬件加速
上面的警告是由于OpenGL硬件加速造成的,可以在Activity、Application、Window、甚至View中把硬件加速关闭即刻。关闭的方式如下:
1、硬件加速的级别
Application
<application
android:hardwareAccelerated="false"
...>
</application>
Activity
<application
android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
Window
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Note: 你可以关闭View级别的硬件加速,但是你不能在View级别开启硬件加速,因为它还依赖其他的设置
2、两种获取是否支持硬件加速的方式
//returns true if the View is attached to a hardware accelerated window.
View.isHardwareAccelerated();
//returns true if the Canvas is hardware accelerated
Canvas.isHardwareAccelerated();
如果必须进行这样的验证,建议你在draw的代码块中使用:Canvas.isHardwareAccelerated(),因为如果一个View被attach到一个硬件加速的Window上,即使没有硬件加速的Canvas,它也是可以被绘制的。比如:将一个View以bitmap的形式进行缓存
通过关闭硬件加速可以避免上面的问题,但是会让使用软件变得卡顿。因此这不是一个好的解决方案,直接放弃。
方案二:通过OpenGL的 GLES类获取硬件加速的最大限制值,然后再对将
Bitmap进行裁剪、缩放既然是超出了最大限制,那么只要我们将Bitmap缩放到合适的值即可。那么思路就是:
Step1:获取OpenGL硬件加速最大长宽限制
Step2:开启大图模式,阻止PhotoView默认情况下的 ScaleType:FIT_CENTER
Step3:获取Bitmap进行缩放、裁剪处理
Step4:重新刷新绘制
获取OpenGL硬件加速最大限制方式如下:
//获取OpenGL上下文
EGLContext ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0);
EGL14.eglMakeCurrent(dpy, surf, surf, ctx);
int[] maxSize = new int[1];
//获取最大的长宽限制
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);
具体代码:
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static int getMaxTextureEgl14()
EGLDisplay dpy = EGL14.
eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] vers = new int[2];
EGL14.eglInitialize(dpy, vers, 0, vers, 1);
int[] configAttr =
EGL14.EGL_COLOR_BUFFER_TYPE,
EGL14.EGL_RGB_BUFFER, EGL14.EGL_LEVEL, 0,
EGL14.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_SURFACE_TYPE,
EGL14.EGL_PBUFFER_BIT, EGL14.EGL_NONE
;
EGLConfig[] configs = new EGLConfig[1];
int[] numConfig = new int[1];
EGL14.eglChooseConfig(dpy, configAttr, 0,
configs, 0, 1, numConfig, 0);
if (numConfig[0] == 0)
return 0;
EGLConfig config = configs[0];
int[] surfAttr =
EGL14.EGL_WIDTH, 64, EGL14.EGL_HEIGHT, 64,
EGL14.EGL_NONE
;
EGLSurface surf = EGL14
.eglCreatePbufferSurface(dpy, config, surfAttr, 0);
int[] ctxAttrib =
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
;
EGLContext ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0);
EGL14.eglMakeCurrent(dpy, surf, surf, ctx);
int[] maxSize = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE,
maxSize, 0);
EGL14.eglMakeCurrent(dpy, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroySurface(dpy, surf);
EGL14.eglDestroyContext(dpy, ctx);
EGL14.eglTerminate(dpy);
return maxSize[0];
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private static int getMaxTextureEgl10()
EGL10 egl = (EGL10) javax.microedition
.khronos.egl.EGLContext.getEGL();
javax.microedition.khronos.egl.EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] vers = new int[2];
egl.eglInitialize(dpy, vers);
int[] configAttr =
EGL10.EGL_COLOR_BUFFER_TYPE,
EGL10.EGL_RGB_BUFFER, EGL10.EGL_LEVEL, 0,
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
EGL10.EGL_NONE
;
javax.microedition.khronos.egl.EGLConfig[] configs = new
javax.microedition.khronos.egl.EGLConfig[1];
int[] numConfig = new int[1];
egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
if (numConfig[0] == 0)
return 0;
javax.microedition.khronos.egl.EGLConfig config =
configs[0];
int[] surfAttr =
EGL10.EGL_WIDTH,
64, EGL10.EGL_HEIGHT, 64, EGL10.EGL_NONE
;
javax.microedition.khronos.egl.EGLSurface surf =
egl.eglCreatePbufferSurface(dpy, config, surfAttr);
final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
// missing in EGL10
int[] ctxAttrib =
EGL_CONTEXT_CLIENT_VERSION, 1, EGL10.EGL_NONE
;
javax.microedition.khronos.egl.EGLContext ctx =
egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
egl.eglMakeCurrent(dpy, surf, surf, ctx);
int[] maxSize = new int[1];
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
egl.eglDestroySurface(dpy, surf);
egl.eglDestroyContext(dpy, ctx);
egl.eglTerminate(dpy);
return maxSize[0];
拿到了硬件加速值之后,就可以对当前的大图进行测量,然后决定是否需要剪裁、缩放。
方案三:分块显示大图Bitmap
对于超大的Bitmap可以进行分块展示。这种方案有两种不同实现:
(1)把Bitmap分块切割到List中,使用ListView或者RecyclerView中展示。
——这样做比较方便,容易实现,但是方法优点投机取巧。
实现步骤:
这种方式的实现步骤主要就在于Bitmap的分割,只要确定了每一个子View的大小,那么分割就完成了。方法很简单,就不在赘述了。
(2)使用BitmapRegionDecoder类,重写ImageView的Touch事件,保证在手指移动内容的时候将对应绘制区域的Bitmap进行加载绘制,也就是说只加载当前绘制区的Bitmap。
——这样做的方式最好,官方推荐,实现方式比第一种稍微复杂一点。
实现步骤:
step1:监听ImageView的onTouch事件。
step2:传入图片的输入流inputStream,解析Bitmap的大小,初始化可见绘制区Rect的坐标。
step3:在滑动过程中确定可见区Rect的坐标,然后通过BitmapRegionDecoder加载对应的内容至Rect中,生成可见区的Bitmap
step4:在onDraw中对可见区Bitmap进行绘制。
最终实施:方案二
选择方案二的原因是比较简单,同时考虑到特大特长尺寸的图片场景不多。在这种情况下,使用分块显示可能大材小用,而且分块显示的速
度也是一个影响因素,因此方案二比较适合解决目前的问题。
以上是关于PhotoView大图绘制——硬件加速限制的主要内容,如果未能解决你的问题,请参考以下文章