mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
[Android] Improve images handling (#109)
* Fixed the inSampleSize calculations based on total pixels (double inSampleSize size -> 4x less pixels) * Fixed the inSampleSize calculations when only hight or widths is requested. Added rotation and scaling while processing images. Added keepAspectRatio property for further flexibility and accuracy. * Fixed the ExifInterface creation based on the API level. * Handle file not found in the native images handling. * chore: apply PR comments
This commit is contained in:
committed by
Alexander Djenkov
parent
bddb2e911f
commit
9d2095cdad
@@ -261,7 +261,7 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner {
|
|||||||
Fetcher fetcher = Fetcher.getInstance(context);
|
Fetcher fetcher = Fetcher.getInstance(context);
|
||||||
// TODO: Implement option to pass load-mode like in ImageView class.
|
// TODO: Implement option to pass load-mode like in ImageView class.
|
||||||
boolean loadAsync = backgroundImageUri.startsWith("http");
|
boolean loadAsync = backgroundImageUri.startsWith("http");
|
||||||
fetcher.loadImage(backgroundImageUri, this, 0, 0, true, loadAsync, null);
|
fetcher.loadImage(backgroundImageUri, this, 0, 0, false, true, loadAsync, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,13 @@ package org.nativescript.widgets.image;
|
|||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.media.ExifInterface;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
@@ -31,7 +34,6 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -156,7 +158,7 @@ public class Fetcher extends Worker {
|
|||||||
* @param data The data to load the bitmap, in this case, a regular http URL
|
* @param data The data to load the bitmap, in this case, a regular http URL
|
||||||
* @return The downloaded and resized bitmap
|
* @return The downloaded and resized bitmap
|
||||||
*/
|
*/
|
||||||
private Bitmap processHttp(String data, int decodeWidth, int decodeHeight) {
|
private Bitmap processHttp(String data, int decodeWidth, int decodeHeight, boolean keepAspectRatio) {
|
||||||
final String key = Cache.hashKeyForDisk(data);
|
final String key = Cache.hashKeyForDisk(data);
|
||||||
FileDescriptor fileDescriptor = null;
|
FileDescriptor fileDescriptor = null;
|
||||||
FileInputStream fileInputStream = null;
|
FileInputStream fileInputStream = null;
|
||||||
@@ -179,8 +181,7 @@ public class Fetcher extends Worker {
|
|||||||
}
|
}
|
||||||
DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
|
DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
|
||||||
if (editor != null) {
|
if (editor != null) {
|
||||||
if (downloadUrlToStream(data,
|
if (downloadUrlToStream(data, editor.newOutputStream(DISK_CACHE_INDEX))) {
|
||||||
editor.newOutputStream(DISK_CACHE_INDEX))) {
|
|
||||||
editor.commit();
|
editor.commit();
|
||||||
} else {
|
} else {
|
||||||
editor.abort();
|
editor.abort();
|
||||||
@@ -189,8 +190,7 @@ public class Fetcher extends Worker {
|
|||||||
snapshot = mHttpDiskCache.get(key);
|
snapshot = mHttpDiskCache.get(key);
|
||||||
}
|
}
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
fileInputStream =
|
fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
|
||||||
(FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
|
|
||||||
fileDescriptor = fileInputStream.getFD();
|
fileDescriptor = fileInputStream.getFD();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -210,8 +210,8 @@ public class Fetcher extends Worker {
|
|||||||
|
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap = null;
|
||||||
if (fileDescriptor != null) {
|
if (fileDescriptor != null) {
|
||||||
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth,
|
bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth, decodeHeight, keepAspectRatio,
|
||||||
decodeHeight, getCache());
|
getCache());
|
||||||
}
|
}
|
||||||
if (fileInputStream != null) {
|
if (fileInputStream != null) {
|
||||||
try {
|
try {
|
||||||
@@ -222,14 +222,15 @@ public class Fetcher extends Worker {
|
|||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap processHttpNoCache(String data, int decodeWidth, int decodeHeight) {
|
private Bitmap processHttpNoCache(String data, int decodeWidth, int decodeHeight, boolean keepAspectRatio) {
|
||||||
ByteArrayOutputStreamInternal outputStream = null;
|
ByteArrayOutputStreamInternal outputStream = null;
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
outputStream = new ByteArrayOutputStreamInternal();
|
outputStream = new ByteArrayOutputStreamInternal();
|
||||||
if (downloadUrlToStream(data, outputStream)) {
|
if (downloadUrlToStream(data, outputStream)) {
|
||||||
bitmap = decodeSampledBitmapFromByteArray(outputStream.getBuffer(), decodeWidth, decodeHeight, getCache());
|
bitmap = decodeSampledBitmapFromByteArray(outputStream.getBuffer(), decodeWidth, decodeHeight,
|
||||||
|
keepAspectRatio, getCache());
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
Log.e(TAG, "processHttpNoCache - " + e);
|
Log.e(TAG, "processHttpNoCache - " + e);
|
||||||
@@ -246,28 +247,30 @@ public class Fetcher extends Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Bitmap processBitmap(String uri, int decodeWidth, int decodeHeight, boolean useCache) {
|
protected Bitmap processBitmap(String uri, int decodeWidth, int decodeHeight, boolean keepAspectRatio,
|
||||||
|
boolean useCache) {
|
||||||
if (debuggable > 0) {
|
if (debuggable > 0) {
|
||||||
Log.v(TAG, "process: " + uri);
|
Log.v(TAG, "process: " + uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri.startsWith(FILE_PREFIX)) {
|
if (uri.startsWith(FILE_PREFIX)) {
|
||||||
String filename = uri.substring(FILE_PREFIX.length());
|
String filename = uri.substring(FILE_PREFIX.length());
|
||||||
return decodeSampledBitmapFromFile(filename, decodeWidth, decodeHeight, getCache());
|
return decodeSampledBitmapFromFile(filename, decodeWidth, decodeHeight, keepAspectRatio, getCache());
|
||||||
} else if (uri.startsWith(RESOURCE_PREFIX)) {
|
} else if (uri.startsWith(RESOURCE_PREFIX)) {
|
||||||
String resPath = uri.substring(RESOURCE_PREFIX.length());
|
String resPath = uri.substring(RESOURCE_PREFIX.length());
|
||||||
int resId = mResources.getIdentifier(resPath, "drawable", mPackageName);
|
int resId = mResources.getIdentifier(resPath, "drawable", mPackageName);
|
||||||
if (resId > 0) {
|
if (resId > 0) {
|
||||||
return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, getCache());
|
return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, keepAspectRatio,
|
||||||
|
getCache());
|
||||||
} else {
|
} else {
|
||||||
Log.v(TAG, "Missing Image with resourceID: " + uri);
|
Log.v(TAG, "Missing Image with resourceID: " + uri);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (useCache && mHttpDiskCache != null) {
|
if (useCache && mHttpDiskCache != null) {
|
||||||
return processHttp(uri, decodeWidth, decodeHeight);
|
return processHttp(uri, decodeWidth, decodeHeight, keepAspectRatio);
|
||||||
} else {
|
} else {
|
||||||
return processHttpNoCache(uri, decodeWidth, decodeHeight);
|
return processHttpNoCache(uri, decodeWidth, decodeHeight, keepAspectRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,8 +345,8 @@ public class Fetcher extends Worker {
|
|||||||
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
|
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
|
||||||
* that are equal to or greater than the requested width and height
|
* that are equal to or greater than the requested width and height
|
||||||
*/
|
*/
|
||||||
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
|
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight,
|
||||||
int reqWidth, int reqHeight, Cache cache) {
|
boolean keepAspectRatio, Cache cache) {
|
||||||
|
|
||||||
// BEGIN_INCLUDE (read_bitmap_dimensions)
|
// BEGIN_INCLUDE (read_bitmap_dimensions)
|
||||||
// First decode with inJustDecodeBounds=true to check dimensions
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
@@ -351,14 +354,7 @@ public class Fetcher extends Worker {
|
|||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeResource(res, resId, options);
|
BitmapFactory.decodeResource(res, resId, options);
|
||||||
|
|
||||||
// If requested width/height were not specified - decode in full size.
|
options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
|
||||||
if (reqWidth > 0 && reqHeight > 0) {
|
|
||||||
// Calculate inSampleSize
|
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
options.inSampleSize = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// END_INCLUDE (read_bitmap_dimensions)
|
// END_INCLUDE (read_bitmap_dimensions)
|
||||||
|
|
||||||
@@ -369,7 +365,74 @@ public class Fetcher extends Worker {
|
|||||||
|
|
||||||
// Decode bitmap with inSampleSize set
|
// Decode bitmap with inSampleSize set
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
return BitmapFactory.decodeResource(res, resId, options);
|
Bitmap bitmap = null;
|
||||||
|
InputStream is = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final TypedValue value = new TypedValue();
|
||||||
|
is = res.openRawResource(resId, value);
|
||||||
|
|
||||||
|
bitmap = BitmapFactory.decodeResourceStream(res, value, is, null, options);
|
||||||
|
} catch (Exception e) {
|
||||||
|
/* do nothing.
|
||||||
|
If the exception happened on open, bm will be null.
|
||||||
|
If it happened on close, bm is still valid.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmap == null && options != null && options.inBitmap != null) {
|
||||||
|
throw new IllegalArgumentException("Problem decoding into existing bitmap");
|
||||||
|
}
|
||||||
|
|
||||||
|
ExifInterface ei = getExifInterface(is);
|
||||||
|
|
||||||
|
return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
private static ExifInterface getExifInterface(InputStream is) {
|
||||||
|
ExifInterface ei = null;
|
||||||
|
try {
|
||||||
|
if (Utils.hasN()) {
|
||||||
|
ei = new ExifInterface(is);
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.e(TAG, "Error in reading bitmap - " + e);
|
||||||
|
} finally {
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ei;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
private static ExifInterface getExifInterface(FileDescriptor fd) {
|
||||||
|
ExifInterface ei = null;
|
||||||
|
try {
|
||||||
|
if (Utils.hasN()) {
|
||||||
|
ei = new ExifInterface(fd);
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.e(TAG, "Error in reading bitmap - " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ei;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExifInterface getExifInterface(String fileName) {
|
||||||
|
ExifInterface ei = null;
|
||||||
|
try {
|
||||||
|
ei = new ExifInterface(fileName);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.e(TAG, "Error in reading bitmap - " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ei;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -382,22 +445,15 @@ public class Fetcher extends Worker {
|
|||||||
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
|
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
|
||||||
* that are equal to or greater than the requested width and height
|
* that are equal to or greater than the requested width and height
|
||||||
*/
|
*/
|
||||||
public static Bitmap decodeSampledBitmapFromFile(String filename,
|
public static Bitmap decodeSampledBitmapFromFile(String fileName, int reqWidth, int reqHeight,
|
||||||
int reqWidth, int reqHeight, Cache cache) {
|
boolean keepAspectRatio, Cache cache) {
|
||||||
|
|
||||||
// First decode with inJustDecodeBounds=true to check dimensions
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeFile(filename, options);
|
BitmapFactory.decodeFile(fileName, options);
|
||||||
|
|
||||||
// If requested width/height were not specified - decode in full size.
|
options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
|
||||||
if (reqWidth > 0 && reqHeight > 0) {
|
|
||||||
// Calculate inSampleSize
|
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
options.inSampleSize = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're running on Honeycomb or newer, try to use inBitmap
|
// If we're running on Honeycomb or newer, try to use inBitmap
|
||||||
if (Utils.hasHoneycomb()) {
|
if (Utils.hasHoneycomb()) {
|
||||||
@@ -406,7 +462,68 @@ public class Fetcher extends Worker {
|
|||||||
|
|
||||||
// Decode bitmap with inSampleSize set
|
// Decode bitmap with inSampleSize set
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
return BitmapFactory.decodeFile(filename, options);
|
|
||||||
|
final Bitmap bitmap = BitmapFactory.decodeFile(fileName, options);
|
||||||
|
ExifInterface ei = getExifInterface(fileName);
|
||||||
|
|
||||||
|
return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap scaleAndRotateBitmap(Bitmap bitmap, ExifInterface ei, int reqWidth, int reqHeight,
|
||||||
|
boolean keepAspectRatio) {
|
||||||
|
if (bitmap == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sourceWidth = bitmap.getWidth();
|
||||||
|
int sourceHeight = bitmap.getHeight();
|
||||||
|
reqWidth = reqWidth > 0 ? reqWidth : sourceWidth;
|
||||||
|
reqHeight = reqHeight > 0 ? reqHeight : sourceHeight;
|
||||||
|
|
||||||
|
// scale
|
||||||
|
if (reqWidth != sourceWidth || reqHeight != sourceHeight) {
|
||||||
|
if (keepAspectRatio) {
|
||||||
|
double widthCoef = (double) sourceWidth / (double) reqWidth;
|
||||||
|
double heightCoef = (double) sourceHeight / (double) reqHeight;
|
||||||
|
double aspectCoef = widthCoef > heightCoef ? widthCoef : heightCoef;
|
||||||
|
|
||||||
|
reqWidth = (int) Math.floor(sourceWidth / aspectCoef);
|
||||||
|
reqHeight = (int) Math.floor(sourceHeight / aspectCoef);
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate
|
||||||
|
if (ei != null) {
|
||||||
|
final Matrix matrix = new Matrix();
|
||||||
|
final int rotationAngle = calculateRotationAngle(ei);
|
||||||
|
if (rotationAngle != 0) {
|
||||||
|
matrix.postRotate(rotationAngle);
|
||||||
|
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateRotationAngle(ExifInterface ei) {
|
||||||
|
int rotationAngle = 0;
|
||||||
|
final int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||||
|
|
||||||
|
switch (orientation) {
|
||||||
|
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||||
|
rotationAngle = 90;
|
||||||
|
break;
|
||||||
|
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||||
|
rotationAngle = 180;
|
||||||
|
break;
|
||||||
|
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||||
|
rotationAngle = 270;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotationAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -419,22 +536,15 @@ public class Fetcher extends Worker {
|
|||||||
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
|
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
|
||||||
* that are equal to or greater than the requested width and height
|
* that are equal to or greater than the requested width and height
|
||||||
*/
|
*/
|
||||||
public static Bitmap decodeSampledBitmapFromDescriptor(
|
public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor, int reqWidth, int reqHeight,
|
||||||
FileDescriptor fileDescriptor, int reqWidth, int reqHeight, Cache cache) {
|
boolean keepAspectRatio, Cache cache) {
|
||||||
|
|
||||||
// First decode with inJustDecodeBounds=true to check dimensions
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
|
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
|
||||||
|
|
||||||
// If requested width/height were not specified - decode in full size.
|
options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
|
||||||
if (reqWidth > 0 && reqHeight > 0) {
|
|
||||||
// Calculate inSampleSize
|
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
options.inSampleSize = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode bitmap with inSampleSize set
|
// Decode bitmap with inSampleSize set
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
@@ -448,32 +558,27 @@ public class Fetcher extends Worker {
|
|||||||
try {
|
try {
|
||||||
// This can throw an error on a corrupted image when using an inBitmap
|
// This can throw an error on a corrupted image when using an inBitmap
|
||||||
results = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
|
results = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
|
||||||
}
|
} catch (Exception e) {
|
||||||
catch (Exception e) {
|
|
||||||
// clear the inBitmap and try again
|
// clear the inBitmap and try again
|
||||||
options.inBitmap = null;
|
options.inBitmap = null;
|
||||||
results = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
|
results = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
|
||||||
// If image is broken, rather than an issue with the inBitmap, we will get a NULL out in this case...
|
// If image is broken, rather than an issue with the inBitmap, we will get a NULL out in this case...
|
||||||
}
|
}
|
||||||
return results;
|
|
||||||
|
ExifInterface ei = getExifInterface(fileDescriptor);
|
||||||
|
|
||||||
|
return scaleAndRotateBitmap(results, ei, reqWidth, reqHeight, keepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap decodeSampledBitmapFromByteArray(
|
public static Bitmap decodeSampledBitmapFromByteArray(byte[] buffer, int reqWidth, int reqHeight,
|
||||||
byte[] buffer, int reqWidth, int reqHeight, Cache cache) {
|
boolean keepAspectRatio, Cache cache) {
|
||||||
|
|
||||||
// First decode with inJustDecodeBounds=true to check dimensions
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
|
BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
|
||||||
|
|
||||||
// If requested width/height were not specified - decode in full size.
|
options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight);
|
||||||
if (reqWidth > 0 && reqHeight > 0) {
|
|
||||||
// Calculate inSampleSize
|
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
options.inSampleSize = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode bitmap with inSampleSize set
|
// Decode bitmap with inSampleSize set
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
@@ -483,7 +588,12 @@ public class Fetcher extends Worker {
|
|||||||
addInBitmapOptions(options, cache);
|
addInBitmapOptions(options, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
|
final Bitmap bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options);
|
||||||
|
|
||||||
|
InputStream is = new ByteArrayInputStream(buffer);
|
||||||
|
ExifInterface ei = getExifInterface(is);
|
||||||
|
|
||||||
|
return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -492,20 +602,20 @@ public class Fetcher extends Worker {
|
|||||||
* the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
|
* the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
|
||||||
* having a width and height equal to or larger than the requested width and height.
|
* having a width and height equal to or larger than the requested width and height.
|
||||||
*
|
*
|
||||||
* @param options An options object with out* params already populated (run through a decode*
|
* @param imageWidth The original width of the resulting bitmap
|
||||||
* method with inJustDecodeBounds==true
|
* @param imageHeight The original height of the resulting bitmap
|
||||||
* @param reqWidth The requested width of the resulting bitmap
|
* @param reqWidth The requested width of the resulting bitmap
|
||||||
* @param reqHeight The requested height of the resulting bitmap
|
* @param reqHeight The requested height of the resulting bitmap
|
||||||
* @return The value to be used for inSampleSize
|
* @return The value to be used for inSampleSize
|
||||||
*/
|
*/
|
||||||
public static int calculateInSampleSize(BitmapFactory.Options options,
|
public static int calculateInSampleSize(int imageWidth, int imageHeight, int reqWidth, int reqHeight) {
|
||||||
int reqWidth, int reqHeight) {
|
|
||||||
// BEGIN_INCLUDE (calculate_sample_size)
|
// BEGIN_INCLUDE (calculate_sample_size)
|
||||||
// Raw height and width of image
|
// Raw height and width of image
|
||||||
final int height = options.outHeight;
|
final int height = imageHeight;
|
||||||
final int width = options.outWidth;
|
final int width = imageWidth;
|
||||||
|
reqWidth = reqWidth > 0 ? reqWidth : width;
|
||||||
|
reqHeight = reqHeight > 0 ? reqHeight : height;
|
||||||
int inSampleSize = 1;
|
int inSampleSize = 1;
|
||||||
|
|
||||||
if (height > reqHeight || width > reqWidth) {
|
if (height > reqHeight || width > reqWidth) {
|
||||||
|
|
||||||
final int halfHeight = height / 2;
|
final int halfHeight = height / 2;
|
||||||
@@ -513,8 +623,7 @@ public class Fetcher extends Worker {
|
|||||||
|
|
||||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||||
// height and width larger than the requested height and width.
|
// height and width larger than the requested height and width.
|
||||||
while ((halfHeight / inSampleSize) > reqHeight
|
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
|
||||||
&& (halfWidth / inSampleSize) > reqWidth) {
|
|
||||||
inSampleSize *= 2;
|
inSampleSize *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,14 +633,14 @@ public class Fetcher extends Worker {
|
|||||||
// end up being too large to fit comfortably in memory, so we should
|
// end up being too large to fit comfortably in memory, so we should
|
||||||
// be more aggressive with sample down the image (=larger inSampleSize).
|
// be more aggressive with sample down the image (=larger inSampleSize).
|
||||||
|
|
||||||
long totalPixels = width * height / inSampleSize;
|
long totalPixels = (width / inSampleSize) * (height / inSampleSize);
|
||||||
|
|
||||||
// Anything more than 2x the requested pixels we'll sample down further
|
// Anything more than 2x the requested pixels we'll sample down further
|
||||||
final long totalReqPixelsCap = reqWidth * reqHeight * 2;
|
final long totalReqPixelsCap = reqWidth * reqHeight * 2;
|
||||||
|
|
||||||
while (totalPixels > totalReqPixelsCap) {
|
while (totalPixels > totalReqPixelsCap) {
|
||||||
inSampleSize *= 2;
|
inSampleSize *= 2;
|
||||||
totalPixels /= 2;
|
totalPixels = (width / inSampleSize) * (height / inSampleSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return inSampleSize;
|
return inSampleSize;
|
||||||
|
|||||||
@@ -52,4 +52,8 @@ public class Utils {
|
|||||||
public static boolean hasKitKat() {
|
public static boolean hasKitKat() {
|
||||||
return Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT;
|
return Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean hasN() {
|
||||||
|
return Build.VERSION.SDK_INT >= VERSION_CODES.N;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ public abstract class Worker {
|
|||||||
* @param owner The owner to bind the downloaded image to.
|
* @param owner The owner to bind the downloaded image to.
|
||||||
* @param listener A listener that will be called back once the image has been loaded.
|
* @param listener A listener that will be called back once the image has been loaded.
|
||||||
*/
|
*/
|
||||||
public void loadImage(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean useCache, boolean async, OnImageLoadedListener listener) {
|
public void loadImage(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean useCache, boolean async, OnImageLoadedListener listener) {
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ public abstract class Worker {
|
|||||||
|
|
||||||
if (value == null && !async) {
|
if (value == null && !async) {
|
||||||
// Decode sync.
|
// Decode sync.
|
||||||
value = processBitmap(uri, decodeWidth, decodeHeight, useCache);
|
value = processBitmap(uri, decodeWidth, decodeHeight, keepAspectRatio, useCache);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
if (mCache != null && useCache) {
|
if (mCache != null && useCache) {
|
||||||
if (debuggable > 0) {
|
if (debuggable > 0) {
|
||||||
@@ -135,7 +135,7 @@ public abstract class Worker {
|
|||||||
listener.onImageLoaded(true);
|
listener.onImageLoaded(true);
|
||||||
}
|
}
|
||||||
} else if (cancelPotentialWork(uri, owner)) {
|
} else if (cancelPotentialWork(uri, owner)) {
|
||||||
final BitmapWorkerTask task = new BitmapWorkerTask(uri, owner, decodeWidth, decodeHeight, useCache, listener);
|
final BitmapWorkerTask task = new BitmapWorkerTask(uri, owner, decodeWidth, decodeHeight, keepAspectRatio, useCache, listener);
|
||||||
final AsyncDrawable asyncDrawable =
|
final AsyncDrawable asyncDrawable =
|
||||||
new AsyncDrawable(mResources, mLoadingBitmap, task);
|
new AsyncDrawable(mResources, mLoadingBitmap, task);
|
||||||
owner.setDrawable(asyncDrawable);
|
owner.setDrawable(asyncDrawable);
|
||||||
@@ -194,7 +194,7 @@ public abstract class Worker {
|
|||||||
* {@link Worker#loadImage(String, BitmapOwner, int, int, boolean, boolean, OnImageLoadedListener)}
|
* {@link Worker#loadImage(String, BitmapOwner, int, int, boolean, boolean, OnImageLoadedListener)}
|
||||||
* @return The processed bitmap
|
* @return The processed bitmap
|
||||||
*/
|
*/
|
||||||
protected abstract Bitmap processBitmap(String uri, int decodeWidth, int decodeHeight, boolean useCache);
|
protected abstract Bitmap processBitmap(String uri, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean useCache);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The {@link Cache} object currently being used by this Worker.
|
* @return The {@link Cache} object currently being used by this Worker.
|
||||||
@@ -263,18 +263,20 @@ public abstract class Worker {
|
|||||||
private class BitmapWorkerTask extends AsyncTask<Void, Void, Bitmap> {
|
private class BitmapWorkerTask extends AsyncTask<Void, Void, Bitmap> {
|
||||||
private int mDecodeWidth;
|
private int mDecodeWidth;
|
||||||
private int mDecodeHeight;
|
private int mDecodeHeight;
|
||||||
|
private boolean mKeepAspectRatio;
|
||||||
private String mUri;
|
private String mUri;
|
||||||
private boolean mCacheImage;
|
private boolean mCacheImage;
|
||||||
private final WeakReference<BitmapOwner> imageViewReference;
|
private final WeakReference<BitmapOwner> imageViewReference;
|
||||||
private final OnImageLoadedListener mOnImageLoadedListener;
|
private final OnImageLoadedListener mOnImageLoadedListener;
|
||||||
|
|
||||||
public BitmapWorkerTask(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean cacheImage) {
|
public BitmapWorkerTask(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean cacheImage) {
|
||||||
this(uri, owner, decodeWidth, decodeHeight, cacheImage, null);
|
this(uri, owner, decodeWidth, decodeHeight, keepAspectRatio, cacheImage, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BitmapWorkerTask(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean cacheImage, OnImageLoadedListener listener) {
|
public BitmapWorkerTask(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean cacheImage, OnImageLoadedListener listener) {
|
||||||
mDecodeWidth = decodeWidth;
|
mDecodeWidth = decodeWidth;
|
||||||
mDecodeHeight = decodeHeight;
|
mDecodeHeight = decodeHeight;
|
||||||
|
mKeepAspectRatio = keepAspectRatio;
|
||||||
mCacheImage = cacheImage;
|
mCacheImage = cacheImage;
|
||||||
mUri = uri;
|
mUri = uri;
|
||||||
imageViewReference = new WeakReference<BitmapOwner>(owner);
|
imageViewReference = new WeakReference<BitmapOwner>(owner);
|
||||||
@@ -308,7 +310,7 @@ public abstract class Worker {
|
|||||||
// process method (as implemented by a subclass)
|
// process method (as implemented by a subclass)
|
||||||
if (bitmap == null && !isCancelled() && getAttachedOwner() != null
|
if (bitmap == null && !isCancelled() && getAttachedOwner() != null
|
||||||
&& !mExitTasksEarly) {
|
&& !mExitTasksEarly) {
|
||||||
bitmap = processBitmap(mUri, mDecodeWidth, mDecodeHeight, mCacheImage);
|
bitmap = processBitmap(mUri, mDecodeWidth, mDecodeHeight, mKeepAspectRatio, mCacheImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the bitmap was processed and the image cache is available, then add the processed
|
// If the bitmap was processed and the image cache is available, then add the processed
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class ImageView extends android.widget.ImageView implements BitmapOwner {
|
|||||||
private String mUri;
|
private String mUri;
|
||||||
private int mDecodeWidth;
|
private int mDecodeWidth;
|
||||||
private int mDecodeHeight;
|
private int mDecodeHeight;
|
||||||
|
private boolean mKeepAspectRatio;
|
||||||
private boolean mUseCache;
|
private boolean mUseCache;
|
||||||
private boolean mAsync;
|
private boolean mAsync;
|
||||||
private Worker.OnImageLoadedListener mListener;
|
private Worker.OnImageLoadedListener mListener;
|
||||||
@@ -162,9 +163,14 @@ public class ImageView extends android.widget.ImageView implements BitmapOwner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setUri(String uri, int decodeWidth, int decodeHeight, boolean useCache, boolean async) {
|
public void setUri(String uri, int decodeWidth, int decodeHeight, boolean useCache, boolean async) {
|
||||||
|
this.setUri(uri, decodeWidth, decodeHeight, false, useCache, async);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUri(String uri, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean useCache, boolean async) {
|
||||||
mUri = uri;
|
mUri = uri;
|
||||||
mDecodeWidth = decodeWidth;
|
mDecodeWidth = decodeWidth;
|
||||||
mDecodeHeight = decodeHeight;
|
mDecodeHeight = decodeHeight;
|
||||||
|
mKeepAspectRatio = keepAspectRatio;
|
||||||
mUseCache = useCache;
|
mUseCache = useCache;
|
||||||
mAsync = async;
|
mAsync = async;
|
||||||
|
|
||||||
@@ -188,7 +194,7 @@ public class ImageView extends android.widget.ImageView implements BitmapOwner {
|
|||||||
Fetcher fetcher = Fetcher.getInstance(this.getContext());
|
Fetcher fetcher = Fetcher.getInstance(this.getContext());
|
||||||
if (mUri != null && fetcher != null) {
|
if (mUri != null && fetcher != null) {
|
||||||
// Get the Bitmap from cache.
|
// Get the Bitmap from cache.
|
||||||
fetcher.loadImage(mUri, this, mDecodeWidth, mDecodeHeight, mUseCache, mAsync, mListener);
|
fetcher.loadImage(mUri, this, mDecodeWidth, mDecodeHeight, mKeepAspectRatio, mUseCache, mAsync, mListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user