Include Subsampling Scale Image View as library to allow preloading tiles when a max bitmap size is provided.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Compatibility factory to instantiate decoders with empty public constructors.
|
||||
* @param <T> The base type of the decoder this factory will produce.
|
||||
*/
|
||||
public class CompatDecoderFactory <T> implements DecoderFactory<T> {
|
||||
private Class<? extends T> clazz;
|
||||
|
||||
public CompatDecoderFactory(@NonNull Class<? extends T> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T make() throws IllegalAccessException, InstantiationException {
|
||||
return clazz.newInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder;
|
||||
|
||||
/**
|
||||
* Interface for decoder (and region decoder) factories.
|
||||
* @param <T> the class of decoder that will be produced.
|
||||
*/
|
||||
public interface DecoderFactory<T> {
|
||||
/**
|
||||
* Produce a new instance of a decoder with type {@link T}.
|
||||
* @return a new instance of your decoder.
|
||||
*/
|
||||
T make() throws IllegalAccessException, InstantiationException;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Interface for image decoding classes, allowing the default {@link android.graphics.BitmapRegionDecoder}
|
||||
* based on the Skia library to be replaced with a custom class.
|
||||
*/
|
||||
public interface ImageDecoder {
|
||||
|
||||
/**
|
||||
* Decode an image. When possible, initial setup work once in this method. This method
|
||||
* must return the dimensions of the image. The URI can be in one of the following formats:
|
||||
* File: file:///scard/picture.jpg
|
||||
* Asset: file:///android_asset/picture.png
|
||||
* Resource: android.resource://com.example.app/drawable/picture
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param uri URI of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
Bitmap decode(Context context, Uri uri) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Interface for image decoding classes, allowing the default {@link android.graphics.BitmapRegionDecoder}
|
||||
* based on the Skia library to be replaced with a custom class.
|
||||
*/
|
||||
public interface ImageRegionDecoder {
|
||||
|
||||
/**
|
||||
* Initialise the decoder. When possible, initial setup work once in this method. This method
|
||||
* must return the dimensions of the image. The URI can be in one of the following formats:
|
||||
* File: file:///scard/picture.jpg
|
||||
* Asset: file:///android_asset/picture.png
|
||||
* Resource: android.resource://com.example.app/drawable/picture
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param uri URI of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
Point init(Context context, Uri uri) throws Exception;
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI thread so it can safely
|
||||
* load the image on the current thread. It is called from an {@link android.os.AsyncTask} running in a single
|
||||
* threaded executor, and while a synchronization lock is held on this object, so will never be called concurrently
|
||||
* even if the decoder implementation supports it.
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
Bitmap decodeRegion(Rect sRect, int sampleSize);
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
boolean isReady();
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any resources still in use.
|
||||
*/
|
||||
void recycle();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link com.davemorrissey.labs.subscaleview.decoder.ImageDecoder}
|
||||
* using Android's {@link android.graphics.BitmapFactory}, based on the Skia library. This
|
||||
* works well in most circumstances and has reasonable performance, however it has some problems
|
||||
* with grayscale, indexed and CMYK images.
|
||||
*/
|
||||
public class SkiaImageDecoder implements ImageDecoder {
|
||||
|
||||
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 + "://";
|
||||
|
||||
@Override
|
||||
public Bitmap decode(Context context, Uri uri) throws Exception {
|
||||
String uriString = uri.toString();
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
Bitmap bitmap;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
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) {
|
||||
}
|
||||
}
|
||||
|
||||
bitmap = BitmapFactory.decodeResource(context.getResources(), id, options);
|
||||
} else if (uriString.startsWith(ASSET_PREFIX)) {
|
||||
String assetName = uriString.substring(ASSET_PREFIX.length());
|
||||
bitmap = BitmapFactory.decodeStream(context.getAssets().open(assetName), null, options);
|
||||
} else if (uriString.startsWith(FILE_PREFIX)) {
|
||||
bitmap = BitmapFactory.decodeFile(uriString.substring(FILE_PREFIX.length()), options);
|
||||
} else {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
inputStream = contentResolver.openInputStream(uri);
|
||||
bitmap = BitmapFactory.decodeStream(inputStream, null, options);
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try { inputStream.close(); } catch (Exception e) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bitmap == null) {
|
||||
throw new RuntimeException("Skia image region decoder returned null bitmap - image format may not be supported");
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
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.graphics.Bitmap.Config;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class SkiaImageRegionDecoder implements ImageRegionDecoder {
|
||||
|
||||
private BitmapRegionDecoder decoder;
|
||||
private final Object decoderLock = new Object();
|
||||
|
||||
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 + "://";
|
||||
|
||||
@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) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Point(decoder.getWidth(), decoder.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap decodeRegion(Rect sRect, int sampleSize) {
|
||||
synchronized (decoderLock) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = sampleSize;
|
||||
options.inPreferredConfig = Config.RGB_565;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return decoder != null && !decoder.isRecycled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
decoder.recycle();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user