Bitmap too large to be uploaded into a texture 4096×4096解决方案

想必这个错误,接触了大图显示的开发者基本都遇到过:

W/OpenGLRenderer: Bitmap too large to be uploaded into a texture (1200×6298, max=4096×4096)

原因是,当开启硬件加速的时候,GPU对于OpenGLRenderer 有一个尺寸限制,不同的手机会有不同的限制,这个限制值可以通过canvas.getMaximumBitmapHeight()和canvas.getMaximumBitmapWidth()来获得。
网上搜索到的方案,很多比较粗暴,比如关闭硬件加速:

<application  
 android:hardwareAccelerated="false" > 

这样,会导致手机运行app卡顿,没有开启硬件加速流畅。
另外一种常见的方案就是,对图片进行压缩显示,控制尺寸不超过限制值。
根据原图片宽高比,动态算出压缩后的尺寸上限,然后使用BitmapFactory.Options,设置图片的尺寸,以下代码引用自郭霖大神:

public static int calculateInSampleSize(BitmapFactory.Options options,
                                            int reqWidth, int reqHeight) {
        // 源图片的高度和宽度
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            // 计算出实际宽高和目标宽高的比率
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
            // 一定都会大于等于目标的宽和高。
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {
        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
        // 调用上面定义的方法计算inSampleSize值
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        // 使用获取到的inSampleSize值再次解析图片
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static Bitmap decodeSampledBitmapFromFilePath(String imagePath,
                                                         int reqWidth, int reqHeight) {
        // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imagePath, options);
        // 调用上面定义的方法计算inSampleSize值
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        // 使用获取到的inSampleSize值再次解析图片
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(imagePath,options);
    }

这样虽然可以解决无法显示的问题,但是,如果我们的需求就是要高清显示大图,不能牺牲图片细节的话,那这个解决方案显然是不可取的。
就没有一种不牺牲显示效果的解决方案吗?没有的话,我也不会写这篇文章了。

subsampling-scale-image-view

GitHub地址:
https://github.com/davemorrissey/subsampling-scale-image-view

这个库为大图而生,可以支持 20,000×20,000px 的超级大图,更大的话,加载会变得较慢慢。平常的小尺寸图片当然也是支持的,你可以把它作为你显示图片的通用控件。

subsampling-scale-image-view采用二次采样和区块显示,来解析和显示超大图,他可以有效避免大图的OOM,而且不丢失任何细节。同时也支持平时图片都有的触摸手势:双击放大,双指放大等。

使用教程

1,引入到Android Studio,添加依赖:

compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0'

2,在对应的布局文件中,替换普通ImageView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

3,现在就可以在你的activity或者fragment中加载图片了:

SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(id.imageView);
imageView.setImage(ImageSource.resource(R.drawable.monkey));
// ... or ...
imageView.setImage(ImageSource.asset("map.png"))
// ... or ...
imageView.setImage(ImageSource.uri("/sdcard/DCIM/DSCM00123.JPG"));

// bitmap也可以,但是作者不建议这么做,因为这可能会造成OOM。
SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(id.imageView);
imageView.setImage(ImageSource.bitmap(bitmap));

4,常用属性:
a.设置双击放大的动画时长:

imageView. setDoubleTapZoomDuration(200);// 单位是ms

b.设置是否自动旋转,或者指定旋转:

imageView.setOrientation(ORIENTATION_USE_EXIF);// 自动根据EXIF信息进行旋转
imageView.setOrientation(ORIENTATION_0);// 不旋转
imageView.setOrientation(ORIENTATION_90);// 顺时针旋转90度
imageView.setOrientation(ORIENTATION_180);// 顺时针旋转180度
imageView.setOrientation(ORIENTATION_270);// 顺时针旋转270度

c.更多属性可以去阅读作者写的wiki:https://github.com/davemorrissey/subsampling-scale-image-view/wiki/07.-Configuration

5,如果是网络图片,可以这么做:

Glide:推荐

SubsamplingScaleImageView image = (SubsamplingScaleImageView) findViewById(R.id.image);
image.setDoubleTapZoomDuration(200);// 单位是ms
image.setOrientation(ORIENTATION_USE_EXIF);// 自动根据EXIF信息进行旋转
Glide.with(activity)
        .load(path)
        .downloadOnly(new ViewTarget<SubsamplingScaleImageView, File>(image) {
            @Override
            public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
                image.setImage(ImageSource.uri(Uri.fromFile(resource)));
            }
        });

必须使用gilde的downloadOnly模式,先下载到本地,再使用SubsamplingScaleImageView的setImage方法,才能最高效的显示图片。

Picasso:加载的是Bitmap,所以不建议使用Picasso

SubsamplingScaleImageView image = (SubsamplingScaleImageView) findViewById(R.id.image);
image.setDoubleTapZoomDuration(200);// 单位是ms
image.setOrientation(ORIENTATION_USE_EXIF);// 自动根据EXIF信息进行旋转
Picasso.with(activity)
        .load(path)
        .into(new com.squareup.picasso.Target() {
            @Override
            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
                image.setImage(ImageSource.bitmap(bitmap));
            }

            @Override
            public void onBitmapFailed(Drawable drawable) {
                
            }
            @Override
            public void onPrepareLoad(Drawable drawable) {

            }
        });
    原文作者:工藤一号
    原文地址: https://www.jianshu.com/p/37f26ac4faed
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞