前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通常是比较大的.比如地图,星空,航天这些领域.
具体实现:
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,这是解析整张图片的.当它判断高宽在一定范围内,不需要分块加载,就会调用这个解码
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的解码就比较简单了
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.
public Point init(final Context context, @NonNull final Uri uri) throws Exception {this.context = context;this.uri = uri;initialiseDecoder();return this.imageDimensions;}
这里初始化解码器,然后返回图片的高宽维度.
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类里面实现.
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;}
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上也不太容易.