diff options
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.java | 158 |
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(); + } + } +} |