图片区域解码BitmapRegionDecoder_android bitmapregiondecoder_heart荼毒的博客-程序员秘密

技术标签: 区域解码  大图加载  Android  

        问一个简单的问题:如何加载一张图片?可能很多朋友会说使用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的手势类,目前只实现了滑动,后面会把缩放的实现一起放上去。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_21154101/article/details/105170954

智能推荐

AWS (Amazon Web services) 免费主机测试使用流程—网络流量监控利器(VnStat)_along602的博客-程序员秘密

AWS 的免费主机搭建好了,墙也能翻了。但是否就可以高枕无忧了呢,非也非也,像我这种share PPTP 帐号给朋友用的人,如果交友不慎,让他拿去下载 porn 之类的东西那流量可是要命的,何况 AWS 上面用的是美刀结算,刀刀伤不起呀。 所以防范于未然,一个好的流量监控工具是比不可少的。需要在之前介绍的 Webmin 中也有一个 Bandwidth Monitor 功能,但就不知道为什么就是用不了。还有一个就是 AWS 自带的 ‘Monitoring’ 功能,用起来也不是很习惯,比较难统计总流量。好吧,

贪婪洞窟2服务器维护,《贪婪洞窟2》停服维护更新内容介绍 24日停机维护更新哪些内容..._红糖小糍粑的博客-程序员秘密

导读贪婪洞窟2更新了什么12月24日更新了什么内容?又加了什么新的玩法?更新之后玩家们会获得什么奖励?想必给为玩家们应该都想啊哟知道吧,贪婪洞窟2发布停服维护公告,此次停机维护贪婪洞窟2更新了什么?不了解...贪婪洞窟2更新了什么12月24日更新了什么内容?又加了什么新的玩法?更新之后玩家们会获得什么奖励?想必给为玩家们应该都想啊哟知道吧,贪婪洞窟2发布停服维护公告,此次停机维护贪婪洞窟2更新了什...

mzy git学习,禁用Fast forward的普通合并(六)_weixin_30455365的博客-程序员秘密

git merge --no-ff -m "msg" x-branch:禁用Fast forward的普通合并通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息(即:原来这个分支的做了什么在log中体现不出来)。为了保留原来的分支,即:普通合并,这样的话,merge的时候会提交一个commit,就会输入-m参数,在log中...

JVM学习记录3--垃圾收集器_arnoq08806的博客-程序员秘密

贴个图Serial收集器最简单的收集器,单线程,收集器会暂停用户线程,称为"stop the world"。ParNew收集器Serial收集器的多线程版本,其它类似。默认线程数为CPU线程数,通过-XX:ParallelGCThreads=? 可以指定线程数Parallel Scavenge收集器复制算法,多线程收集器。与ParNew的区别在于,该收集器关注系统吞吐量(...

linux常用终端命令(三)远程管理命令_bacite5315的博客-程序员秘密

三、远程管理常用命令关机/重启shutdown查看或配置网卡信息ifconfigping远程登录和复制文件sshscp1、关机/重启序号命令对应英文作用01shutdown 选项 时间shutdown关机/重启1.1、shu...

matlab彩色图像边缘检测,Matlab多种图像边缘检测方法_季静白的博客-程序员秘密

1、用Prewitt算子检测图像的边缘I = imread('bacteria.BMP');BW1 = edge(I,'prewitt',0.04); % 0.04为梯度阈值figure(1);imshow(I);figure(2);imshow(BW1);2、用不同σ值的LoG算子检测图像的边缘I = imread('bacteria.BMP');BW1 = edge(I...

随便推点

Python第三方库matplotlib(2D绘图库)入门与进阶_JavaGuide的博客-程序员秘密

Matplotlib简介:Matplotlib是一个Python 2D绘图库,它可以在各种平台上以各种硬拷贝格式和交互式环境生成出具有出版品质的图形。 Matplotlib可用于Python脚本,Python和IPython shell,Jupyter笔记本,Web应用程序服务器和四个图形用户界面工具包Matplotlib试图让简单的事情变得更简单,让无法实现的事情变得可能实现。 ...

verilog中reg只能在always块内使用_always块能只能用reg_always里面的变量只能为reg吗_Mr_liu_666的博客-程序员秘密

reg只能用在always块内:尝试在modelsim中编译:reg [3:0] c;c &lt;= 4'b1100;//报错如第一张图reg [3:0] c;c = 4'b1100;//报错如第二张图always块内只能用reg:大体来说,reg和wire类似于C、C++的变量,但若此变量要放在begin...end之内,则该变量只能是reg型;在begin......

【C语言】函数栈帧_白晨并不是很能熬夜的博客-程序员秘密

在了解函数栈帧之前,首先我们需要一些前导知识目录1.栈的定义及初识2.寄存器3.反汇编4.函数栈帧1.栈的定义及初识首先,我们要知道栈的定义以及栈帧又有什么作用栈(stack)又名堆栈,堆栈是一个特定的存储区或寄存器,它的一端是固定的,另一端是浮动的 。对这个存储区存入的数据,是一种特殊的数据结构。所有的数据存入或取出,只能在浮动的一端(称栈顶)进行,严格按照“先进后出”的原则存取,位于其中间的元素,必须在其栈上部(后进栈者)诸元素逐个移出后才能取出。在内存储器(...

数据库连接池的原理_Quillagua的博客-程序员秘密

数据库连接池的原理一.早期我们怎么进行数据库操作1.原理:一般来说,Java应用程序访问数据库的过程是:   ①装载数据库驱动程序;   ②通过jdbc建立数据库连接;   ③访问数据库,执行sql语句;   ④断开数据库连接。  2.分析        程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一

一幅图弄清DFT与DTFT,DFS的关系_小白study的博客-程序员秘密

很多同学学习了数字信号处理之后,被里面的几个名词搞的晕头转向,比如DFT,DTFT,DFS,FFT,FT,FS等,FT和FS属于信号与系统课程的内容,是对连续时间信号的处理,这里就不过多讨论,只解释一下前四者的关系。  首先说明一下,我不是数字信号处理专家,因此这里只站在学生的角度以最浅显易懂的性质来解释问题,而不涉及到任何公式运算。  学过卷积,我们都知道有时域卷积定理和频域卷积

UART、SPI和I2C详解_uart-rx接口影响usb充电吗_find12的博客-程序员秘密

原文链接做单片机开发时UART,SPI和I2C都是我们最经常使用到的硬件接口,我收集了相关的具体材料对这三种接口进行了详细的解释。UARTUART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用于主机与辅助设备通信,如汽车音响与外接AP之间的通信,与PC机通信包括与监控调试器和其它器件,如EEPROM通信。将由计...

推荐文章

热门文章

相关标签