Include Subsampling Scale Image View as library to allow preloading tiles when a max bitmap size is provided.
This commit is contained in:
parent
80a59548a5
commit
322f54380d
19 changed files with 3499 additions and 3 deletions
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
50
app/src/main/java/eu/kanade/mangafeed/util/GLUtil.java
Normal file
50
app/src/main/java/eu/kanade/mangafeed/util/GLUtil.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
1
libs/SubsamplingScaleImageView/.gitignore
vendored
Normal file
1
libs/SubsamplingScaleImageView/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
build/
|
7
libs/SubsamplingScaleImageView/AndroidManifest.xml
Normal file
7
libs/SubsamplingScaleImageView/AndroidManifest.xml
Normal 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>
|
38
libs/SubsamplingScaleImageView/build.gradle
Normal file
38
libs/SubsamplingScaleImageView/build.gradle
Normal 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')
|
||||
}
|
||||
}
|
15
libs/SubsamplingScaleImageView/project.properties
Normal file
15
libs/SubsamplingScaleImageView/project.properties
Normal 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
|
28
libs/SubsamplingScaleImageView/res/values/attrs.xml
Normal file
28
libs/SubsamplingScaleImageView/res/values/attrs.xml
Normal 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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
include ':app'
|
||||
include ':app', ':SubsamplingScaleImageView'
|
||||
project(':SubsamplingScaleImageView').projectDir = new File('libs/SubsamplingScaleImageView')
|
Reference in a new issue