Include Subsampling Scale Image View as library to allow preloading tiles when a max bitmap size is provided.

This commit is contained in:
inorichi 2015-12-21 18:09:09 +01:00
parent 80a59548a5
commit 322f54380d
19 changed files with 3499 additions and 3 deletions

View file

@ -82,6 +82,7 @@ dependencies {
final ICEPICK_VERSION = '3.1.0'
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(":SubsamplingScaleImageView")
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
@ -107,7 +108,6 @@ dependencies {
compile 'com.jakewharton.timber:timber:4.1.0'
compile 'uk.co.ribot:easyadapter:1.5.0@aar'
compile 'ch.acra:acra:4.7.0'
compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.4.1'
compile "frankiesardo:icepick:$ICEPICK_VERSION"
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'

View file

@ -34,6 +34,7 @@ import eu.kanade.mangafeed.ui.reader.viewer.horizontal.LeftToRightReader;
import eu.kanade.mangafeed.ui.reader.viewer.horizontal.RightToLeftReader;
import eu.kanade.mangafeed.ui.reader.viewer.vertical.VerticalReader;
import eu.kanade.mangafeed.ui.reader.viewer.webtoon.WebtoonReader;
import eu.kanade.mangafeed.util.GLUtil;
import eu.kanade.mangafeed.util.ToastUtil;
import icepick.Icepick;
import nucleus.factory.RequiresPresenter;
@ -57,6 +58,8 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
protected CompositeSubscription subscriptions;
private Subscription customBrightnessSubscription;
private int maxBitmapSize;
public static final int LEFT_TO_RIGHT = 1;
public static final int RIGHT_TO_LEFT = 2;
public static final int VERTICAL = 3;
@ -88,6 +91,8 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
setBlackTheme();
initializeSettings();
maxBitmapSize = GLUtil.getMaxTextureSize();
}
@Override
@ -282,4 +287,8 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
return viewer;
}
public int getMaxBitmapSize() {
return maxBitmapSize;
}
}

View file

@ -59,6 +59,8 @@ public class ViewPagerReaderFragment extends BaseFragment {
progressText.setTextColor(ContextCompat.getColor(getContext(), R.color.light_grey));
}
imageView.setParallelLoadingEnabled(true);
imageView.setMaxDimensions(activity.getMaxBitmapSize(), activity.getMaxBitmapSize());
imageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED);
imageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
imageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE);
@ -103,7 +105,7 @@ public class ViewPagerReaderFragment extends BaseFragment {
if (page == null || page.getImagePath() == null)
return;
imageView.setImage(ImageSource.uri(page.getImagePath()).tilingDisabled());
imageView.setImage(ImageSource.uri(page.getImagePath()));
progressContainer.setVisibility(View.GONE);
}

View file

@ -0,0 +1,50 @@
package eu.kanade.mangafeed.util;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
public class GLUtil {
public static int getMaxTextureSize() {
// Safe minimum default size
final int IMAGE_MAX_BITMAP_DIMENSION = 2048;
// Get EGL Display
EGL10 egl = (EGL10) EGLContext.getEGL();
EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
// Initialise
int[] version = new int[2];
egl.eglInitialize(display, version);
// Query total number of configurations
int[] totalConfigurations = new int[1];
egl.eglGetConfigs(display, null, 0, totalConfigurations);
// Query actual list configurations
EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]];
egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations);
int[] textureSize = new int[1];
int maximumTextureSize = 0;
// Iterate through all the configurations to located the maximum texture size
for (int i = 0; i < totalConfigurations[0]; i++) {
// Only need to check for width since opengl textures are always squared
egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize);
// Keep track of the maximum texture size
if (maximumTextureSize < textureSize[0])
maximumTextureSize = textureSize[0];
}
// Release
egl.eglTerminate(display);
// Return largest texture size found, or default
return Math.max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION);
}
}

View file

@ -0,0 +1 @@
build/

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.davemorrissey.labs.subscaleview"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="14"/>
</manifest>

View file

@ -0,0 +1,38 @@
apply plugin: 'com.android.library'
group = 'com.davemorrissey.labs'
version = '3.4.1'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile "com.android.support:support-annotations:23.1.1"
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot('tests')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
}

View file

@ -0,0 +1,15 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
android.library=true

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2014 David Morrissey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<declare-styleable name="SubsamplingScaleImageView">
<attr name="src" format="reference"/>
<attr name="assetName" format="string"/>
<attr name="panEnabled" format="boolean"/>
<attr name="zoomEnabled" format="boolean"/>
<attr name="quickScaleEnabled" format="boolean"/>
<attr name="tileBackgroundColor" format="color"/>
</declare-styleable>
</resources>

View file

@ -0,0 +1,233 @@
package com.davemorrissey.labs.subscaleview;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.net.Uri;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
/**
* Helper class used to set the source and additional attributes from a variety of sources. Supports
* use of a bitmap, asset, resource, external file or any other URI.
*
* When you are using a preview image, you must set the dimensions of the full size image on the
* ImageSource object for the full size image using the {@link #dimensions(int, int)} method.
*/
public final class ImageSource {
static final String FILE_SCHEME = "file:///";
static final String ASSET_SCHEME = "file:///android_asset/";
private final Uri uri;
private final Bitmap bitmap;
private final Integer resource;
private boolean tile;
private int sWidth;
private int sHeight;
private Rect sRegion;
private boolean cached;
private ImageSource(Bitmap bitmap, boolean cached) {
this.bitmap = bitmap;
this.uri = null;
this.resource = null;
this.tile = false;
this.sWidth = bitmap.getWidth();
this.sHeight = bitmap.getHeight();
this.cached = cached;
}
private ImageSource(Uri uri) {
// #114 If file doesn't exist, attempt to url decode the URI and try again
String uriString = uri.toString();
if (uriString.startsWith(FILE_SCHEME)) {
File uriFile = new File(uriString.substring(FILE_SCHEME.length() - 1));
if (!uriFile.exists()) {
try {
uri = Uri.parse(URLDecoder.decode(uriString, "UTF-8"));
} catch (UnsupportedEncodingException e) {
// Fallback to encoded URI. This exception is not expected.
}
}
}
this.bitmap = null;
this.uri = uri;
this.resource = null;
this.tile = true;
}
private ImageSource(int resource) {
this.bitmap = null;
this.uri = null;
this.resource = resource;
this.tile = true;
}
/**
* Create an instance from a resource. The correct resource for the device screen resolution will be used.
* @param resId resource ID.
*/
public static ImageSource resource(int resId) {
return new ImageSource(resId);
}
/**
* Create an instance from an asset name.
* @param assetName asset name.
*/
public static ImageSource asset(String assetName) {
if (assetName == null) {
throw new NullPointerException("Asset name must not be null");
}
return uri(ASSET_SCHEME + assetName);
}
/**
* Create an instance from a URI. If the URI does not start with a scheme, it's assumed to be the URI
* of a file.
* @param uri image URI.
*/
public static ImageSource uri(String uri) {
if (uri == null) {
throw new NullPointerException("Uri must not be null");
}
if (!uri.contains("://")) {
if (uri.startsWith("/")) {
uri = uri.substring(1);
}
uri = FILE_SCHEME + uri;
}
return new ImageSource(Uri.parse(uri));
}
/**
* Create an instance from a URI.
* @param uri image URI.
*/
public static ImageSource uri(Uri uri) {
if (uri == null) {
throw new NullPointerException("Uri must not be null");
}
return new ImageSource(uri);
}
/**
* Provide a loaded bitmap for display.
* @param bitmap bitmap to be displayed.
*/
public static ImageSource bitmap(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
return new ImageSource(bitmap, false);
}
/**
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
* or Volley.
* @param bitmap bitmap to be displayed.
*/
public static ImageSource cachedBitmap(Bitmap bitmap) {
if (bitmap == null) {
throw new NullPointerException("Bitmap must not be null");
}
return new ImageSource(bitmap, true);
}
/**
* Enable tiling of the image. This does not apply to preview images which are always loaded as a single bitmap.,
* and tiling cannot be disabled when displaying a region of the source image.
* @return this instance for chaining.
*/
public ImageSource tilingEnabled() {
return tiling(true);
}
/**
* Disable tiling of the image. This does not apply to preview images which are always loaded as a single bitmap,
* and tiling cannot be disabled when displaying a region of the source image.
* @return this instance for chaining.
*/
public ImageSource tilingDisabled() {
return tiling(false);
}
/**
* Enable or disable tiling of the image. This does not apply to preview images which are always loaded as a single bitmap,
* and tiling cannot be disabled when displaying a region of the source image.
* @return this instance for chaining.
*/
public ImageSource tiling(boolean tile) {
this.tile = tile;
return this;
}
/**
* Use a region of the source image. Region must be set independently for the full size image and the preview if
* you are using one.
* @return this instance for chaining.
*/
public ImageSource region(Rect sRegion) {
this.sRegion = sRegion;
setInvariants();
return this;
}
/**
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
* @return this instance for chaining.
*/
public ImageSource dimensions(int sWidth, int sHeight) {
if (bitmap == null) {
this.sWidth = sWidth;
this.sHeight = sHeight;
}
setInvariants();
return this;
}
private void setInvariants() {
if (this.sRegion != null) {
this.tile = true;
this.sWidth = this.sRegion.width();
this.sHeight = this.sRegion.height();
}
}
protected final Uri getUri() {
return uri;
}
protected final Bitmap getBitmap() {
return bitmap;
}
protected final Integer getResource() {
return resource;
}
protected final boolean getTile() {
return tile;
}
protected final int getSWidth() {
return sWidth;
}
protected final int getSHeight() {
return sHeight;
}
protected final Rect getSRegion() {
return sRegion;
}
protected final boolean isCached() {
return cached;
}
}

View file

@ -0,0 +1,55 @@
/*
Copyright 2014 David Morrissey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.davemorrissey.labs.subscaleview;
import android.graphics.PointF;
import java.io.Serializable;
/**
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
*/
public class ImageViewState implements Serializable {
private float scale;
private float centerX;
private float centerY;
private int orientation;
public ImageViewState(float scale, PointF center, int orientation) {
this.scale = scale;
this.centerX = center.x;
this.centerY = center.y;
this.orientation = orientation;
}
public float getScale() {
return scale;
}
public PointF getCenter() {
return new PointF(centerX, centerY);
}
public int getOrientation() {
return orientation;
}
}

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -1 +1,2 @@
include ':app'
include ':app', ':SubsamplingScaleImageView'
project(':SubsamplingScaleImageView').projectDir = new File('libs/SubsamplingScaleImageView')