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