android pdf框架-11,查看图片

前10篇文章,9章关于pdf的,pdf解析后,里面也是有各种图片,于是利用pdf的view来展示图片,似乎也是个不错的想法.

android手机中的图片查看功能,有的可以展示,有的不能.比如华为,荣耀对大体积的png是可以显示的,小米是不显示,只有缩略图.

一张png50m大,比如清明上河图,原图是tiff,2g左右,这是adobe收购公司发明的,也是ps常用的.

对于tiff,华为,小米都显示不了,一方面可能是太大了,我有两张,一张300多m,一张2g,还有一张600多清明上河图原图扫描版的缩略图.

查阅了不少关于tiff的解析,目前没有提供好的android可以直接用的so库或lib库,TiffBitmapFactory有一个解析版,比较老了,它解析每次都是从头解析,虽然有area,但还是不行,对于300m的图片,解析一次要7s多,不管这块是多大,就算只有100*100像素也是如此.

tif库4.1.0才支持按需加载,我还没有实现这个功能,它是链表存储的,解析每一块,要从头开始查询fd的位置.

mupdf里面有一个load-tiff.c的解析代码,每一次加载也是7s左右,后面的就不用了,于是就想到,我把它直接展示不就行了.

原来的apk已经具备了查看图片的功能,但对于pdf的view来显示图片,有几个不好的方面:

  • 手势,pdfview目前不支持双击放大这些操作,是针对pdf的手势.
  • 缩放比例,当前没有设置很大的比例.
  • 加载速度,不支持多线程,mupdf的问题,暂时没有处理.

加载大图用的是subsampling-scale-image-view,这个在github上比较有名,它具备tile的按块加载,只需解码的时候 实现regiondecoder就可以了.

而且写的很好,文档注释很全.

代码:

要实现两个类,一个是pooled的解码类,一个是普通的解码.它首先会先用pooled类,去检测高与宽.

public class MupdfPooledImageRegionDecoder implements ImageRegionDecoder

public class MupdfImageDecoder implements ImageDecoder

官方的解码skia有三个类,这里不需要,两个就够了.

首先,对于体积超过10m的图片,获取exif可能会有内存不足的问题,所以subsampling-scale-image-view的源码,把相关获取exif的去了,去了以后,有些图片的方向不对,手机转一下就可以了.目前没有好的解决方案,这只是对于三星手机拍的照片有这个问题.

图片展示要分两类,一类是android支持的各种图片,一类是tiff.因为tiff通常是比较大的.比如地图,星空,航天这些领域.

具体实现:

Kotlin 复制代码
if (path!!.endsWith("tif") || path.endsWith("tiff")) {
            binding.imageView.setBitmapDecoderFactory(
                CompatDecoderFactory(
                    MupdfImageDecoder::class.java,
                    Bitmap.Config.ARGB_8888
                )
            )
            binding.imageView.setRegionDecoderFactory(
                CompatDecoderFactory(
                    MupdfPooledImageRegionDecoder::class.java,
                    Bitmap.Config.ARGB_8888
                )
            )
        } else {
            binding.imageView.setBitmapDecoderFactory(
                CompatDecoderFactory(
                    SkiaImageDecoder::class.java,
                    Bitmap.Config.ARGB_8888
                )
            )
            binding.imageView.setRegionDecoderFactory(
                CompatDecoderFactory(
                    SkiaPooledImageRegionDecoder::class.java,
                    Bitmap.Config.ARGB_8888
                )
            )
        }

tiff的设置解码器与其它不同,针对小内存的手机,可以尝试rgb565,由于mupdf现在没有支持,所以都用argb_8888.

先看MupdfImageDecoder,这是解析整张图片的.当它判断高宽在一定范围内,不需要分块加载,就会调用这个解码

Kotlin 复制代码
public Bitmap decode(Context context, @NonNull Uri uri) throws Exception {
        Bitmap bitmap = null;
        String uriString = uri.toString();
        if (uriString.startsWith(FILE_PREFIX)) {
            String path = uriString.substring(FILE_PREFIX.length());
            Log.e(TAG, "mupdf trying to open " + path);
            try {
                document = Document.openDocument(path);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
                return null;
            }

            bitmap = renderBitmap();
        } else {
           
        }
        if (bitmap == null) {
            throw new RuntimeException("Mupdf image region decoder returned null bitmap - image format may not be supported");
        }
        return bitmap;
    }

这里先只支持path这种源.

剩下的解码,mupdf的解码就比较简单了

Kotlin 复制代码
private Bitmap renderBitmap() {
        Page page = document.loadPage(0);
        Rect b = page.getBounds();
        float width = (b.x1 - b.x0);
        float height = (b.y1 - b.y0);
        Bitmap bitmap = BitmapPool.getInstance().acquire((int) width, (int) height);
        float zoom = 2f;
        Matrix ctm = new Matrix(zoom, zoom);
        RectI bbox = new RectI(page.getBounds().transform(ctm));
        float xscale = width / (bbox.x1 - bbox.x0);
        float yscale = height / (bbox.y1 - bbox.y0);
        ctm.scale(xscale, yscale);
        AndroidDrawDevice dev = new AndroidDrawDevice(bitmap, 0, 0, 0, 0,
                bitmap.getWidth(), bitmap.getHeight());
        page.run(dev, ctm, null);
        page.destroy();
        dev.close();
        dev.destroy();
        return bitmap;
    }

图片只有一张,所以直接加载第0页,然后计算高宽,解码.

MupdfPooledImageRegionDecoder,也只支持path.

Kotlin 复制代码
public Point init(final Context context, @NonNull final Uri uri) throws Exception {
        this.context = context;
        this.uri = uri;
        initialiseDecoder();
        return this.imageDimensions;
    }

这里初始化解码器,然后返回图片的高宽维度.

Kotlin 复制代码
private void initialiseDecoder() {
        String uriString = uri.toString();

        long fileLength = Long.MAX_VALUE;
        if (uriString.startsWith(FILE_PREFIX)) {
            String path = uriString.substring(FILE_PREFIX.length());

            File file = new File(path);
            if (file.exists()) {
                fileLength = file.length();
            }

            debug("mupdf trying to open " + path);
            try {
                decoder = Document.openDocument(path);
            } catch (Exception e) {
                debug(e.getMessage());
                return;
            }
        } else {
           
        }

        if (fileLength > SHOW_LOADING_SIZE) { //对于图片过大,需要一个等待状态,因为tiff很大,加载时间会比较长,可能要十几秒.
            showLoading();
        }

        this.fileLength = fileLength;
        page = decoder.loadPage(0); //这个速度很快,可以很快得到图片的高宽
        Rect b = page.getBounds();
        int width = (int) (b.x1 - b.x0);
        int height = (int) (b.y1 - b.y0);
        this.imageDimensions.set(width, height);
        hideLoading();
    }

上面初始化后,如果判断图片的高宽超过指定范围,它会启动分块加载,后续的分块加载全在这个pooled类里面实现.

Kotlin 复制代码
public Bitmap decodeRegion(@NonNull android.graphics.Rect sRect, int sampleSize) {
        debug("Decode region " + sRect + " on thread " + Thread.currentThread().getName());
        if (sRect.width() < imageDimensions.x || sRect.height() < imageDimensions.y) {
            if (null == decoder) {
                try {
                    initialiseDecoder();
                } catch (Exception e) {
                }
            }
        }

        try {
            if (decoder != null) {
                Bitmap bitmap = renderBitmap(sRect, sampleSize);
                if (bitmap == null) {
                    throw new RuntimeException("Mupdf image decoder returned null bitmap - image format may not be supported");
                }
                return bitmap;
            }
        } catch (Exception e) {
            debug(e.getMessage());
        }
        return null;
    }
Kotlin 复制代码
public Bitmap renderBitmap(android.graphics.Rect cropBound, int sampleSize) {
        float scale = 1f / sampleSize; 缩放这里面要倒过来,其它没什么好说的了
        int pageW;
        int pageH;
        int patchX;
        int patchY;
        //如果页面的缩放为1,那么这时的pageW就是view的宽.
        pageW = (int) (cropBound.width() * scale);
        pageH = (int) (cropBound.height() * scale);

        patchX = (int) (cropBound.left * scale);
        patchY = (int) (cropBound.top * scale);
        Bitmap bitmap = BitmapPool.getInstance().acquire(pageW, pageH);
        if (null == page) {
            page = decoder.loadPage(0);
        }
        com.artifex.mupdf.fitz.Matrix ctm = new com.artifex.mupdf.fitz.Matrix(scale);
        AndroidDrawDevice dev = new AndroidDrawDevice(bitmap, patchX, patchY, 0, 0, pageW, pageH);

        try {
            page.run(dev, ctm, null);
        } catch (Exception e) {
            debug(e.getMessage());
        }
        dev.close();
        dev.destroy();

        return bitmap;
    }

剩下的实现接口的方法就不多说了.这样一个图片查看器就实现了.

具备了一般图片app的不具备,或者不太支持的功能.

  • 支持大的png,jpg,20mb,50mb,甚至更大的都可以快速打开.
  • 支持600mb(实测)的tiff图片的解析,初始化慢一些,后来就比较顺滑了.

到这里,打开300m的tiff是可以的,但是打开600m的会失败,打开2g的也是失败.因为mupdf的load-tiff.c解码前会经过它stream-read.c,这个里面判断了,如果超过一定值,会抛出异常,认为这是一个压缩炸弹,忽略.我把这个限制去了.600mb的图片没有问题.但2gb的依然不行.应该采用按需加载的形式.

虽然大图片能打开,但还是存在一些问题,tiff的解码不能按需加载,运行这个app后,会消耗大量的内存,导致其它app被回收了.但总算是能打开了.

关于查看tiff的apk,multi tiff view.apk这个虽然可以解析,但耗时也不短,另外移动缩放这些惨不忍睹, 它是目前我用的,唯一手机上能打开2gb的tiff的图片.

fast image,这个打开是各种慢,300mb就不行了.

bigtiff,这个开源的解析库是因为tiff的4g限制,以前tiff不能存储大于4gb的图片,后来改了,而bigtiff可以存储40,400gb的图片,官网上有介绍的,只是没有看到解析应用.如果能包装成jni可调用的,应该会是个不错的.

另一个服务器解码的应用非常不错.具体的名字下周去公司电脑上把相关链接都放上.这个可以解析大图,按需加载,由于对c++好久没弄,生疏了,想要弄到android上也不太容易.

相关推荐
shandianchengzi2 小时前
【记录】Excel|Excel 打印成 PDF 页数太多怎么办
pdf·excel
bianshaopeng11 小时前
android 原生加载pdf
android·pdf
卢卡斯23311 小时前
在线PDF怎么转换成JPG图片?分享14种转换操作!
pdf
J不A秃V头A18 小时前
iTextPDF中,要实现表格中的内容在数据长度超过边框时自动换行
java·pdf
嘻嘻仙人1 天前
【杂谈一之概率论】CDF、PDF、PMF和PPF概念解释与分析
pdf·概率论·pmf·cdf
资深前端之路2 天前
vue2 将页面生成pdf下载
前端·vue.js·pdf
Eiceblue2 天前
Python 复制PDF中的页面
vscode·python·pdf
J不A秃V头A2 天前
使用iTextPDF库设置文字为英文样式
java·pdf
Odoo穆尘2 天前
【零散技术】Odoo PDF 打印问题问题合集
pdf·odoo
暮毅3 天前
一、前后端分离及drf的概念
django·pdf·前后端分离