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上也不太容易.

相关推荐
网络安全指导员8 小时前
恶意PDF文档分析记录
网络·安全·web安全·pdf
晴天飛 雪10 小时前
SpringBoot 实现 PDF 添加水印
spring boot·后端·pdf
周末zm14 小时前
golang将word、excel转换为pdf
pdf·word·excel
wjg102415 小时前
连接数据库导出数据库信息支持excel pdf html markdown
pdf
lu_rong_qq17 小时前
分享 pdf 转 word 的免费平台
pdf
一只小白菜~20 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
流形填表1 天前
pdf的统计图表数据提取;图表转excel
pdf·excel
harmful_sheep2 天前
Java操作PDF:一键生成文件,插入文字、选项、签名及公章
pdf
BillDev2 天前
SobarQube实现PDF报告导出
pdf·sonarqube
落落鱼20132 天前
TP6将HTML转换为PDF文件,非法UTF-8编码和中文乱码问题
前端·pdf·html