aboutsummaryrefslogtreecommitdiff
path: root/library/src/main/java/com/davemorrissey/labs/subscaleview/decoder/SkiaImageRegionDecoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/main/java/com/davemorrissey/labs/subscaleview/decoder/SkiaImageRegionDecoder.java')
-rw-r--r--library/src/main/java/com/davemorrissey/labs/subscaleview/decoder/SkiaImageRegionDecoder.java158
1 files changed, 158 insertions, 0 deletions
diff --git a/library/src/main/java/com/davemorrissey/labs/subscaleview/decoder/SkiaImageRegionDecoder.java b/library/src/main/java/com/davemorrissey/labs/subscaleview/decoder/SkiaImageRegionDecoder.java
new file mode 100644
index 0000000..eebe2bb
--- /dev/null
+++ b/library/src/main/java/com/davemorrissey/labs/subscaleview/decoder/SkiaImageRegionDecoder.java
@@ -0,0 +1,158 @@
+package com.davemorrissey.labs.subscaleview.decoder;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.*;
+import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.Keep;
+import android.text.TextUtils;
+
+import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Default implementation of {@link com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder}
+ * using Android's {@link android.graphics.BitmapRegionDecoder}, based on the Skia library. This
+ * works well in most circumstances and has reasonable performance due to the cached decoder instance,
+ * however it has some problems with grayscale, indexed and CMYK images.
+ *
+ * A {@link ReadWriteLock} is used to delegate responsibility for multi threading behaviour to the
+ * {@link BitmapRegionDecoder} instance on SDK >= 21, whilst allowing this class to block until no
+ * tiles are being loaded before recycling the decoder. In practice, {@link BitmapRegionDecoder} is
+ * synchronized internally so this has no real impact on performance.
+ */
+public class SkiaImageRegionDecoder implements ImageRegionDecoder {
+
+ private BitmapRegionDecoder decoder;
+ private final ReadWriteLock decoderLock = new ReentrantReadWriteLock(true);
+
+ private static final String FILE_PREFIX = "file://";
+ private static final String ASSET_PREFIX = FILE_PREFIX + "/android_asset/";
+ private static final String RESOURCE_PREFIX = ContentResolver.SCHEME_ANDROID_RESOURCE + "://";
+
+ private final Bitmap.Config bitmapConfig;
+
+ @Keep
+ @SuppressWarnings("unused")
+ public SkiaImageRegionDecoder() {
+ this(null);
+ }
+
+ @SuppressWarnings({"WeakerAccess", "SameParameterValue"})
+ public SkiaImageRegionDecoder(Bitmap.Config bitmapConfig) {
+ Bitmap.Config globalBitmapConfig = SubsamplingScaleImageView.getPreferredBitmapConfig();
+ if (bitmapConfig != null) {
+ this.bitmapConfig = bitmapConfig;
+ } else if (globalBitmapConfig != null) {
+ this.bitmapConfig = globalBitmapConfig;
+ } else {
+ this.bitmapConfig = Bitmap.Config.RGB_565;
+ }
+ }
+
+ @Override
+ public Point init(Context context, Uri uri) throws Exception {
+ String uriString = uri.toString();
+ if (uriString.startsWith(RESOURCE_PREFIX)) {
+ Resources res;
+ String packageName = uri.getAuthority();
+ if (context.getPackageName().equals(packageName)) {
+ res = context.getResources();
+ } else {
+ PackageManager pm = context.getPackageManager();
+ res = pm.getResourcesForApplication(packageName);
+ }
+
+ int id = 0;
+ List<String> segments = uri.getPathSegments();
+ int size = segments.size();
+ if (size == 2 && segments.get(0).equals("drawable")) {
+ String resName = segments.get(1);
+ id = res.getIdentifier(resName, "drawable", packageName);
+ } else if (size == 1 && TextUtils.isDigitsOnly(segments.get(0))) {
+ try {
+ id = Integer.parseInt(segments.get(0));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ decoder = BitmapRegionDecoder.newInstance(context.getResources().openRawResource(id), false);
+ } else if (uriString.startsWith(ASSET_PREFIX)) {
+ String assetName = uriString.substring(ASSET_PREFIX.length());
+ decoder = BitmapRegionDecoder.newInstance(context.getAssets().open(assetName, AssetManager.ACCESS_RANDOM), false);
+ } else if (uriString.startsWith(FILE_PREFIX)) {
+ decoder = BitmapRegionDecoder.newInstance(uriString.substring(FILE_PREFIX.length()), false);
+ } else {
+ InputStream inputStream = null;
+ try {
+ ContentResolver contentResolver = context.getContentResolver();
+ inputStream = contentResolver.openInputStream(uri);
+ decoder = BitmapRegionDecoder.newInstance(inputStream, false);
+ } finally {
+ if (inputStream != null) {
+ try { inputStream.close(); } catch (Exception e) { /* Ignore */ }
+ }
+ }
+ }
+ return new Point(decoder.getWidth(), decoder.getHeight());
+ }
+
+ @Override
+ public Bitmap decodeRegion(Rect sRect, int sampleSize) {
+ getDecodeLock().lock();
+ try {
+ if (decoder != null && !decoder.isRecycled()) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = sampleSize;
+ options.inPreferredConfig = bitmapConfig;
+ Bitmap bitmap = decoder.decodeRegion(sRect, options);
+ if (bitmap == null) {
+ throw new RuntimeException("Skia image decoder returned null bitmap - image format may not be supported");
+ }
+ return bitmap;
+ } else {
+ throw new IllegalStateException("Cannot decode region after decoder has been recycled");
+ }
+ } finally {
+ getDecodeLock().unlock();
+ }
+ }
+
+ @Override
+ public synchronized boolean isReady() {
+ return decoder != null && !decoder.isRecycled();
+ }
+
+ @Override
+ public synchronized void recycle() {
+ decoderLock.writeLock().lock();
+ try {
+ decoder.recycle();
+ decoder = null;
+ } finally {
+ decoderLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Before SDK 21, BitmapRegionDecoder was not synchronized internally. Any attempt to decode
+ * regions from multiple threads with one decoder instance causes a segfault. For old versions
+ * use the write lock to enforce single threaded decoding.
+ */
+ private Lock getDecodeLock() {
+ if (Build.VERSION.SDK_INT < 21) {
+ return decoderLock.writeLock();
+ } else {
+ return decoderLock.readLock();
+ }
+ }
+}