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大图绘制——硬件加速限制的主要内容,如果未能解决你的问题,请参考以下文章

Android大图绘制——硬件加速限制分析与方案

Android大图绘制——硬件加速限制分析与方案

grid.arrange在菜单后面没有完成绘制大图

如何提高 JPictureBox 的大图绘制速度?

在大图像中绘制边界框

如何为 Apple 的大图像缩小 iOS 示例设置最大缩放限制?