图片区域解码BitmapRegionDecoder

145 篇文章 12 订阅
64 篇文章 1 订阅

 问一个简单的问题:如何加载一张图片?可能很多朋友会说使用Glide,Picasso等图片加载库来加载图片,或者使用BitmapFactory来加载图片。但是,大家应该知道,无论是使用Glide还是使用BitmapFactory加载图片,加载的图片都是需要做压缩的,不然会导致oom。那么,如果我不允许压缩,要加载一张高清的大图,那么如何实现?这就需要使用图片区域解码BitmapRegionDecoder。

一、前言

        在大多数情况下,我们加载图片并不需要加载高清大图,这样我们完全可以使用Glide等进行图片加载。但是,加载高清大图的需求还是有的。例如:长截屏图片,相机拍摄的照片等。对于这些大图的加载,我们不能直接去decode一张原图,因为它可能会直接oom。因此,我们需要使用BitmapRegionDecoder进行区域解码。可能,很多朋友没接触过甚至没听过BitmapRegionDecoder,这也没什么奇怪的,因为大多数的业务并不需要展示高清大图。而我,也是因为几年前做手机相册app,挺过Bitmap区域解码。正好,最近花时间去研究一下。说真的,这篇文章拖了三天了,因为我在使用的过程中遇到了几个坑。

二、区域解码

        在这里,简单说一下区域解码。假设我们有一张非常大的照片,例如它的分辨率是4000*3000。那么,在常规的的手机屏幕(1080*1920)上,如果不做压缩处理,我们的图片很明显是显示不开的。因此,我们想到了这样的方案:让图片支持滑动,滑动到哪里,加载哪一部分。如下图片,我们要在手机上按照1:1的比例高清展示出来,那么1080*1920的区域,大概只能让他显示黑框中的区域。那其他的区域就需要我们滑动去加载。滑动到哪里,就展示哪一块区域。

三、BitmapRegionDecoder 

1、使用

(1)创建BitmapRegionDecoder

        使用区域解码,那么我们首先需要创建一个BitmapRegionDecoder对象。只需要调用newInstance方法,传入一个InputStream和一个boolean值。如下所示:

mDecoder = BitmapRegionDecoder.newInstance(is, false);

(2)解码Bitmap

        调用decodeRegion方法解码Bitmap,需要传入一块区域,以及参数,代码如下:

Bitmap bitmap = mDecoder.decodeRegion(mRect, mDecodeOptions);

2、原理

        BitmapRegionDecoder的实现都在native层。首先,我们看一下JAVA层能看到的源码。首先是newInstance方法:

    public static BitmapRegionDecoder newInstance(InputStream is,
            boolean isShareable) throws IOException {
        if (is instanceof AssetManager.AssetInputStream) {
            return nativeNewInstance(
                    ((AssetManager.AssetInputStream) is).getNativeAsset(),
                    isShareable);
        } else {
            // pass some temp storage down to the native code. 1024 is made up,
            // but should be large enough to avoid too many small calls back
            // into is.read(...).
            byte [] tempStorage = new byte[16 * 1024];
            return nativeNewInstance(is, tempStorage, isShareable);
        }
    }

         第一个参数是输入流,这个没什么好解释的,也就是把数据传了进来。第二个参数isSHareable,我们看一下代码注释:

* @param isShareable If this is true, then the BitmapRegionDecoder may keep a
     *                    shallow reference to the input. If this is false,
     *                    then the BitmapRegionDecoder will explicitly make a copy of the
     *                    input data, and keep that. Even if sharing is allowed,
     *                    the implementation may still decide to make a deep
     *                    copy of the input data. If an image is progressively encoded,
     *                    allowing sharing may degrade the decoding speed.

         直译一下:如果是true,那么区域解码类可以保持对输入的浅引用。如果是false,区域解码类将显式地复制输入数据,并保留它。即使允许分享,仍可能去制作输入数据的深拷贝。如果图像是逐步编码的,允许共享可能会降低解码速度。所以,老老实实的传入一个false吧。

        接下来是decodeRegion方法,传入一块区域和一个options,这块区域就是显示图片的区域,options就是设置Bitmap位数等的参数,最后是调用native方法解码。

public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
        BitmapFactory.Options.validate(options);
        synchronized (mNativeLock) {
            checkRecycled("decodeRegion called on recycled region decoder");
            if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()
                    || rect.top >= getHeight())
                throw new IllegalArgumentException("rectangle is outside the image");
            return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
                    rect.right - rect.left, rect.bottom - rect.top, options,
                    BitmapFactory.Options.nativeInBitmap(options),
                    BitmapFactory.Options.nativeColorSpace(options));
        }
    }

四、扩展

        那么,如何实现随手指滑动的高清图片加载呢?在这里,我去找了几个现成的支持滑动的View。不过,是很多年前的,在使用上有一些问题,我稍微改了一下。

1、HighImageView

        这是一个支持手势滑动的View,用来加载高清大图。在这里我直接用的PhotoView自定义的手势识别类。网上也有其他的手势识别类,我还是觉得PhotoView的比较全面,代码如下:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
 
import java.io.IOException;
import java.io.InputStream;
 
public class HighImageView extends View {
 
    private int mImageWidth;
    private int mImageHeight;
    private BitmapRegionDecoder mDecoder;
    private static BitmapFactory.Options mDecodeOptions = new BitmapFactory.Options();
    static{
        mDecodeOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
    }
    private Rect mRect = new Rect();
    private CustomGestureDetector customGestureDetector;
    private static final String TAG = "HighImageView";
 
    public HighImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    public void setImage(InputStream is,int width ,int height) {
        try {
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
            mImageWidth = width;
            mImageHeight = height;
 
            requestLayout();
            invalidate();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (Exception e) {
            }
        }
    }
 
    private void init() {
        customGestureDetector = new CustomGestureDetector(getContext(), new OnGestureListener() {
            @Override
            public void onDrag(float dx, float dy) {
                if (mImageWidth > getWidth()) {
                    mRect.offset((int) -dx, 0);
                    checkWidth();
                    invalidate();
                }
                if (mImageHeight > getHeight()) {
                    mRect.offset(0, (int) -dy);
                    checkHeight();
                    invalidate();
                }
            }
 
            @Override
            public void onFling(float startX, float startY, float velocityX, float velocityY) {
 
            }
 
            @Override
            public void onScale(float scaleFactor, float focusX, float focusY) {
                Log.d(TAG, "onScale");
            }
        });
    }
 
    private void checkHeight() {
        if (mRect.bottom > mImageHeight) {
            mRect.bottom = mImageHeight;
            mRect.top = mRect.bottom - getHeight();
        }
        if (mRect.top < 0) {
            mRect.top = 0;
            mRect.bottom = mRect.top + getHeight();
        }
    }
 
    private void checkWidth() {
        if (mRect.right > mImageWidth) {
            mRect.right = mImageWidth;
            mRect.left = mImageWidth - getWidth();
        }
        if (mRect.left < 0) {
            mRect.left = 0;
            mRect.right = mRect.left + getWidth();
        }
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        customGestureDetector.onTouchEvent(event);
        return true;
    }
 
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
 
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mRect.left + width;
        mRect.bottom = mRect.top + height;
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        Bitmap bitmap = mDecoder.decodeRegion(mRect, mDecodeOptions);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
}

2、Activity

        使用也很简单,以加载在assets下的test.jpg图片为例,传入一个inputStream和原始的宽高,如下所示:

import androidx.appcompat.app.AppCompatActivity;
 
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
 
import java.io.IOException;
import java.io.InputStream;
 
public class MainActivity extends AppCompatActivity {
 
    private HighImageView photoView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        photoView = findViewById(R.id.img);
        InputStream inputStream = null;
        try {
            inputStream = getAssets().open("test.jpg");
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
            photoView.setImage(inputStream, bitmap.getWidth(), bitmap.getHeight());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

        最后,简单的总结一下。BitmapRegionDecoder是用来加载高清大图的类,通过自定义支持手势滑动的View,可以实现随着手指拖动动态的加载。我在demo中使用了PhotoView的手势类,目前只实现了滑动,后面会把缩放的实现一起放上去。

 

转自:https://blog.csdn.net/qq_21154101/article/details/105170954

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值