From fd7307490e868dcd7da5e87a7606ce0b91131554 Mon Sep 17 00:00:00 2001 From: Dimitar Tachev Date: Mon, 27 Nov 2017 18:13:04 +0200 Subject: [PATCH 1/5] Fixed the inSampleSize calculations based on total pixels (double inSampleSize size -> 4x less pixels) --- .../src/main/java/org/nativescript/widgets/Image/Fetcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java index 37c179a..727a579 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java @@ -524,14 +524,14 @@ public static int calculateInSampleSize(BitmapFactory.Options options, // end up being too large to fit comfortably in memory, so we should // 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 final long totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels > totalReqPixelsCap) { inSampleSize *= 2; - totalPixels /= 2; + totalPixels = (width / inSampleSize) * (height / inSampleSize); } } return inSampleSize; From e077d256125e5499fdcf647a0200d295abdd4738 Mon Sep 17 00:00:00 2001 From: Dimitar Tachev Date: Mon, 27 Nov 2017 18:17:01 +0200 Subject: [PATCH 2/5] 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. --- .../nativescript/widgets/BorderDrawable.java | 2 +- .../nativescript/widgets/Image/Fetcher.java | 195 +++++++++++++----- .../nativescript/widgets/Image/Worker.java | 18 +- .../org/nativescript/widgets/ImageView.java | 8 +- 4 files changed, 160 insertions(+), 63 deletions(-) diff --git a/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java b/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java index 2f7c859..4ee7b12 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java @@ -261,7 +261,7 @@ public void refresh(int borderTopColor, Fetcher fetcher = Fetcher.getInstance(context); // TODO: Implement option to pass load-mode like in ImageView class. boolean loadAsync = backgroundImageUri.startsWith("http"); - fetcher.loadImage(backgroundImageUri, this, 0, 0, true, loadAsync, null); + fetcher.loadImage(backgroundImageUri, this, 0, 0, false, true, loadAsync, null); } } diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java index 727a579..4717a8c 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java @@ -19,10 +19,13 @@ import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; +import android.graphics.Matrix; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.media.ExifInterface; import android.os.Build; import android.util.Log; +import android.util.TypedValue; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -156,7 +159,7 @@ protected void closeCacheInternal() { * @param data The data to load the bitmap, in this case, a regular http URL * @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); FileDescriptor fileDescriptor = null; FileInputStream fileInputStream = null; @@ -211,7 +214,7 @@ private Bitmap processHttp(String data, int decodeWidth, int decodeHeight) { Bitmap bitmap = null; if (fileDescriptor != null) { bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth, - decodeHeight, getCache()); + decodeHeight, keepAspectRatio, getCache()); } if (fileInputStream != null) { try { @@ -222,14 +225,14 @@ private Bitmap processHttp(String data, int decodeWidth, int decodeHeight) { 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; Bitmap bitmap = null; try { outputStream = new ByteArrayOutputStreamInternal(); if (downloadUrlToStream(data, outputStream)) { - bitmap = decodeSampledBitmapFromByteArray(outputStream.getBuffer(), decodeWidth, decodeHeight, getCache()); + bitmap = decodeSampledBitmapFromByteArray(outputStream.getBuffer(), decodeWidth, decodeHeight, keepAspectRatio, getCache()); } } catch (IllegalStateException e) { Log.e(TAG, "processHttpNoCache - " + e); @@ -246,28 +249,28 @@ private Bitmap processHttpNoCache(String data, int decodeWidth, int decodeHeight } @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) { Log.v(TAG, "process: " + uri); } if (uri.startsWith(FILE_PREFIX)) { 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)) { String resPath = uri.substring(RESOURCE_PREFIX.length()); int resId = mResources.getIdentifier(resPath, "drawable", mPackageName); if (resId > 0) { - return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, getCache()); + return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, keepAspectRatio, getCache()); } else { Log.v(TAG, "Missing Image with resourceID: " + uri); return null; } } else { if (useCache && mHttpDiskCache != null) { - return processHttp(uri, decodeWidth, decodeHeight); + return processHttp(uri, decodeWidth, decodeHeight, keepAspectRatio); } else { - return processHttpNoCache(uri, decodeWidth, decodeHeight); + return processHttpNoCache(uri, decodeWidth, decodeHeight, keepAspectRatio); } } } @@ -343,7 +346,7 @@ public byte[] getBuffer() { * that are equal to or greater than the requested width and height */ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, - int reqWidth, int reqHeight, Cache cache) { + int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { // BEGIN_INCLUDE (read_bitmap_dimensions) // First decode with inJustDecodeBounds=true to check dimensions @@ -351,14 +354,7 @@ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); - // If requested width/height were not specified - decode in full size. - if (reqWidth > 0 && reqHeight > 0) { - // Calculate inSampleSize - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - } - else { - options.inSampleSize = 1; - } + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); // END_INCLUDE (read_bitmap_dimensions) @@ -369,7 +365,39 @@ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, // Decode bitmap with inSampleSize set 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. + */ + } finally { + try { + if (is != null) is.close(); + } catch (IOException e) { + // Ignore + } + } + + if (bitmap == null && options != null && options.inBitmap != null) { + throw new IllegalArgumentException("Problem decoding into existing bitmap"); + } + + ExifInterface ei = null; + try { + ei = new ExifInterface(is); + } catch (final IOException e) { + Log.e(TAG, "Error in reading bitmap - " + e); + } + + return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio); } /** @@ -382,22 +410,15 @@ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, * @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 */ - public static Bitmap decodeSampledBitmapFromFile(String filename, - int reqWidth, int reqHeight, Cache cache) { + public static Bitmap decodeSampledBitmapFromFile(String fileName, + int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(filename, options); + BitmapFactory.decodeFile(fileName, options); - // If requested width/height were not specified - decode in full size. - if (reqWidth > 0 && reqHeight > 0) { - // Calculate inSampleSize - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - } - else { - options.inSampleSize = 1; - } + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); // If we're running on Honeycomb or newer, try to use inBitmap if (Utils.hasHoneycomb()) { @@ -406,7 +427,70 @@ public static Bitmap decodeSampledBitmapFromFile(String filename, // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; - return BitmapFactory.decodeFile(filename, options); + + final Bitmap bitmap = BitmapFactory.decodeFile(fileName, options); + ExifInterface ei = null; + try { + ei = new ExifInterface(fileName); + } catch (final IOException e) { + Log.e(TAG, "Error in reading bitmap - " + e); + } + + return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio); + } + + private static Bitmap scaleAndRotateBitmap(Bitmap bitmap, ExifInterface ei, int reqWidth, int reqHeight, boolean keepAspectRatio) { + 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 = calculateAngleFromFile(ei); + if (rotationAngle != 0) { + matrix.postRotate(rotationAngle); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } + } + + + return bitmap; + } + + private static int calculateAngleFromFile(ExifInterface ei) { + int rotationAngle = 0; + final int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + + switch (orientation) { + case android.media.ExifInterface.ORIENTATION_ROTATE_90: + rotationAngle = 90; + break; + case android.media.ExifInterface.ORIENTATION_ROTATE_180: + rotationAngle = 180; + break; + case android.media.ExifInterface.ORIENTATION_ROTATE_270: + rotationAngle = 270; + break; + } + + return rotationAngle; } /** @@ -420,21 +504,14 @@ public static Bitmap decodeSampledBitmapFromFile(String filename, * that are equal to or greater than the requested width and height */ public static Bitmap decodeSampledBitmapFromDescriptor( - FileDescriptor fileDescriptor, int reqWidth, int reqHeight, Cache cache) { + FileDescriptor fileDescriptor, int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); - // If requested width/height were not specified - decode in full size. - if (reqWidth > 0 && reqHeight > 0) { - // Calculate inSampleSize - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - } - else { - options.inSampleSize = 1; - } + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; @@ -455,25 +532,26 @@ public static Bitmap decodeSampledBitmapFromDescriptor( 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... } - return results; + + ExifInterface ei = null; + try { + ei = new ExifInterface(fileDescriptor); + } catch (final IOException e) { + Log.e(TAG, "Error in reading bitmap - " + e); + } + + return scaleAndRotateBitmap(results, ei, reqWidth, reqHeight, keepAspectRatio); } public static Bitmap decodeSampledBitmapFromByteArray( - byte[] buffer, int reqWidth, int reqHeight, Cache cache) { + byte[] buffer, int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); - // If requested width/height were not specified - decode in full size. - if (reqWidth > 0 && reqHeight > 0) { - // Calculate inSampleSize - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - } - else { - options.inSampleSize = 1; - } + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; @@ -483,7 +561,17 @@ public static Bitmap decodeSampledBitmapFromByteArray( addInBitmapOptions(options, cache); } - return BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); + final Bitmap bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); + + ExifInterface ei = null; + try { + ByteArrayInputStream bs = new ByteArrayInputStream(buffer); + ei = new ExifInterface(bs); + } catch (final IOException e) { + Log.e(TAG, "Error in reading bitmap - " + e); + } + + return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio); } /** @@ -499,13 +587,14 @@ public static Bitmap decodeSampledBitmapFromByteArray( * @return The value to be used for inSampleSize */ public static int calculateInSampleSize(BitmapFactory.Options options, - int reqWidth, int reqHeight) { + int reqWidth, int reqHeight, boolean keepAspectRatio) { // BEGIN_INCLUDE (calculate_sample_size) // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; + reqWidth = reqWidth > 0 ? reqWidth : width; + reqHeight = reqHeight > 0 ? reqHeight : height; int inSampleSize = 1; - if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java index 62bd060..7ef53e6 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Worker.java @@ -95,7 +95,7 @@ public void removeBitmap(String uri) { * @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. */ - 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) { return; } @@ -111,7 +111,7 @@ public void loadImage(String uri, BitmapOwner owner, int decodeWidth, int decode if (value == null && !async) { // Decode sync. - value = processBitmap(uri, decodeWidth, decodeHeight, useCache); + value = processBitmap(uri, decodeWidth, decodeHeight, keepAspectRatio, useCache); if (value != null) { if (mCache != null && useCache) { if (debuggable > 0) { @@ -135,7 +135,7 @@ public void loadImage(String uri, BitmapOwner owner, int decodeWidth, int decode listener.onImageLoaded(true); } } 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 = new AsyncDrawable(mResources, mLoadingBitmap, task); owner.setDrawable(asyncDrawable); @@ -194,7 +194,7 @@ public void setExitTasksEarly(boolean exitTasksEarly) { * {@link Worker#loadImage(String, BitmapOwner, int, int, boolean, boolean, OnImageLoadedListener)} * @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. @@ -263,18 +263,20 @@ private static BitmapWorkerTask getBitmapWorkerTask(BitmapOwner owner) { private class BitmapWorkerTask extends AsyncTask { private int mDecodeWidth; private int mDecodeHeight; + private boolean mKeepAspectRatio; private String mUri; private boolean mCacheImage; private final WeakReference imageViewReference; private final OnImageLoadedListener mOnImageLoadedListener; - public BitmapWorkerTask(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean cacheImage) { - this(uri, owner, decodeWidth, decodeHeight, cacheImage, null); + public BitmapWorkerTask(String uri, BitmapOwner owner, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean cacheImage) { + 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; mDecodeHeight = decodeHeight; + mKeepAspectRatio = keepAspectRatio; mCacheImage = cacheImage; mUri = uri; imageViewReference = new WeakReference(owner); @@ -308,7 +310,7 @@ protected Bitmap doInBackground(Void... params) { // process method (as implemented by a subclass) if (bitmap == null && !isCancelled() && getAttachedOwner() != null && !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 diff --git a/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java b/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java index 8344d9e..d45fdb5 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java @@ -30,6 +30,7 @@ public class ImageView extends android.widget.ImageView implements BitmapOwner { private String mUri; private int mDecodeWidth; private int mDecodeHeight; + private boolean mKeepAspectRatio; private boolean mUseCache; private boolean mAsync; private Worker.OnImageLoadedListener mListener; @@ -162,9 +163,14 @@ private void computeScaleFactor(int measureWidth, int measureHeight, boolean wid } 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; mDecodeWidth = decodeWidth; mDecodeHeight = decodeHeight; + mKeepAspectRatio = keepAspectRatio; mUseCache = useCache; mAsync = async; @@ -188,7 +194,7 @@ private void loadImage() { Fetcher fetcher = Fetcher.getInstance(this.getContext()); if (mUri != null && fetcher != null) { // Get the Bitmap from cache. - fetcher.loadImage(mUri, this, mDecodeWidth, mDecodeHeight, mUseCache, mAsync, mListener); + fetcher.loadImage(mUri, this, mDecodeWidth, mDecodeHeight, mKeepAspectRatio, mUseCache, mAsync, mListener); } } From d9c39ea8fc2b8989e9c3ca7bd234dd039273840e Mon Sep 17 00:00:00 2001 From: Dimitar Tachev Date: Wed, 29 Nov 2017 19:00:02 +0200 Subject: [PATCH 3/5] Fixed the ExifInterface creation based on the API level. --- .../nativescript/widgets/Image/Fetcher.java | 87 +++++++++++-------- .../org/nativescript/widgets/Image/Utils.java | 4 + 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java index 4717a8c..09194d5 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java @@ -34,7 +34,6 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -378,26 +377,61 @@ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, If the exception happened on open, bm will be null. If it happened on close, bm is still valid. */ - } finally { - try { - if (is != null) is.close(); - } catch (IOException e) { - // Ignore - } } 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 { - ei = new ExifInterface(is); - } catch (final IOException e) { + 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 scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio); + 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; } /** @@ -429,12 +463,7 @@ public static Bitmap decodeSampledBitmapFromFile(String fileName, options.inJustDecodeBounds = false; final Bitmap bitmap = BitmapFactory.decodeFile(fileName, options); - ExifInterface ei = null; - try { - ei = new ExifInterface(fileName); - } catch (final IOException e) { - Log.e(TAG, "Error in reading bitmap - " + e); - } + ExifInterface ei = getExifInterface(fileName); return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio); } @@ -463,7 +492,7 @@ private static Bitmap scaleAndRotateBitmap(Bitmap bitmap, ExifInterface ei, int // rotate if (ei != null) { final Matrix matrix = new Matrix(); - final int rotationAngle = calculateAngleFromFile(ei); + final int rotationAngle = calculateRotationAngle(ei); if (rotationAngle != 0) { matrix.postRotate(rotationAngle); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); @@ -474,18 +503,18 @@ private static Bitmap scaleAndRotateBitmap(Bitmap bitmap, ExifInterface ei, int return bitmap; } - private static int calculateAngleFromFile(ExifInterface ei) { + private static int calculateRotationAngle(ExifInterface ei) { int rotationAngle = 0; final int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { - case android.media.ExifInterface.ORIENTATION_ROTATE_90: + case ExifInterface.ORIENTATION_ROTATE_90: rotationAngle = 90; break; - case android.media.ExifInterface.ORIENTATION_ROTATE_180: + case ExifInterface.ORIENTATION_ROTATE_180: rotationAngle = 180; break; - case android.media.ExifInterface.ORIENTATION_ROTATE_270: + case ExifInterface.ORIENTATION_ROTATE_270: rotationAngle = 270; break; } @@ -533,12 +562,7 @@ public static Bitmap decodeSampledBitmapFromDescriptor( // If image is broken, rather than an issue with the inBitmap, we will get a NULL out in this case... } - ExifInterface ei = null; - try { - ei = new ExifInterface(fileDescriptor); - } catch (final IOException e) { - Log.e(TAG, "Error in reading bitmap - " + e); - } + ExifInterface ei = getExifInterface(fileDescriptor); return scaleAndRotateBitmap(results, ei, reqWidth, reqHeight, keepAspectRatio); } @@ -563,13 +587,8 @@ public static Bitmap decodeSampledBitmapFromByteArray( final Bitmap bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); - ExifInterface ei = null; - try { - ByteArrayInputStream bs = new ByteArrayInputStream(buffer); - ei = new ExifInterface(bs); - } catch (final IOException e) { - Log.e(TAG, "Error in reading bitmap - " + e); - } + InputStream is = new ByteArrayInputStream(buffer); + ExifInterface ei = getExifInterface(is); return scaleAndRotateBitmap(bitmap, ei, reqWidth, reqHeight, keepAspectRatio); } diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Utils.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Utils.java index 30302d9..c105422 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Utils.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Utils.java @@ -52,4 +52,8 @@ public static boolean hasJellyBean() { public static boolean hasKitKat() { return Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT; } + + public static boolean hasN() { + return Build.VERSION.SDK_INT >= VERSION_CODES.N; + } } \ No newline at end of file From 9289ee85eddbc2df73be35f4d969cf9ba4a5bbc9 Mon Sep 17 00:00:00 2001 From: Dimitar Tachev Date: Fri, 1 Dec 2017 18:28:57 +0200 Subject: [PATCH 4/5] Handle file not found in the native images handling. --- .../src/main/java/org/nativescript/widgets/Image/Fetcher.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java index 09194d5..5dd4427 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java @@ -469,6 +469,10 @@ public static Bitmap decodeSampledBitmapFromFile(String fileName, } 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; From f369dfdd2febb74ce2004a17585314c3471350a8 Mon Sep 17 00:00:00 2001 From: ADjenkov Date: Tue, 27 Feb 2018 09:31:30 +0200 Subject: [PATCH 5/5] chore: apply PR comments --- .../nativescript/widgets/Image/Fetcher.java | 91 +++++++++---------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java index 5dd4427..73007d2 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java @@ -181,8 +181,7 @@ private Bitmap processHttp(String data, int decodeWidth, int decodeHeight, boole } DiskLruCache.Editor editor = mHttpDiskCache.edit(key); if (editor != null) { - if (downloadUrlToStream(data, - editor.newOutputStream(DISK_CACHE_INDEX))) { + if (downloadUrlToStream(data, editor.newOutputStream(DISK_CACHE_INDEX))) { editor.commit(); } else { editor.abort(); @@ -191,8 +190,7 @@ private Bitmap processHttp(String data, int decodeWidth, int decodeHeight, boole snapshot = mHttpDiskCache.get(key); } if (snapshot != null) { - fileInputStream = - (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); + fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); fileDescriptor = fileInputStream.getFD(); } } catch (IOException e) { @@ -212,8 +210,8 @@ private Bitmap processHttp(String data, int decodeWidth, int decodeHeight, boole Bitmap bitmap = null; if (fileDescriptor != null) { - bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth, - decodeHeight, keepAspectRatio, getCache()); + bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, decodeWidth, decodeHeight, keepAspectRatio, + getCache()); } if (fileInputStream != null) { try { @@ -231,7 +229,8 @@ private Bitmap processHttpNoCache(String data, int decodeWidth, int decodeHeight try { outputStream = new ByteArrayOutputStreamInternal(); if (downloadUrlToStream(data, outputStream)) { - bitmap = decodeSampledBitmapFromByteArray(outputStream.getBuffer(), decodeWidth, decodeHeight, keepAspectRatio, getCache()); + bitmap = decodeSampledBitmapFromByteArray(outputStream.getBuffer(), decodeWidth, decodeHeight, + keepAspectRatio, getCache()); } } catch (IllegalStateException e) { Log.e(TAG, "processHttpNoCache - " + e); @@ -248,7 +247,8 @@ private Bitmap processHttpNoCache(String data, int decodeWidth, int decodeHeight } @Override - protected Bitmap processBitmap(String uri, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean useCache) { + protected Bitmap processBitmap(String uri, int decodeWidth, int decodeHeight, boolean keepAspectRatio, + boolean useCache) { if (debuggable > 0) { Log.v(TAG, "process: " + uri); } @@ -260,7 +260,8 @@ protected Bitmap processBitmap(String uri, int decodeWidth, int decodeHeight, bo String resPath = uri.substring(RESOURCE_PREFIX.length()); int resId = mResources.getIdentifier(resPath, "drawable", mPackageName); if (resId > 0) { - return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, keepAspectRatio, getCache()); + return decodeSampledBitmapFromResource(mResources, resId, decodeWidth, decodeHeight, keepAspectRatio, + getCache()); } else { Log.v(TAG, "Missing Image with resourceID: " + uri); return null; @@ -344,8 +345,8 @@ public byte[] getBuffer() { * @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 */ - public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, - int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { + public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight, + boolean keepAspectRatio, Cache cache) { // BEGIN_INCLUDE (read_bitmap_dimensions) // First decode with inJustDecodeBounds=true to check dimensions @@ -353,7 +354,7 @@ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); + options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); // END_INCLUDE (read_bitmap_dimensions) @@ -444,15 +445,15 @@ private static ExifInterface getExifInterface(String fileName) { * @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 */ - public static Bitmap decodeSampledBitmapFromFile(String fileName, - int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { + public static Bitmap decodeSampledBitmapFromFile(String fileName, int reqWidth, int reqHeight, + boolean keepAspectRatio, Cache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(fileName, options); - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); + options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); // If we're running on Honeycomb or newer, try to use inBitmap if (Utils.hasHoneycomb()) { @@ -461,18 +462,19 @@ public static Bitmap decodeSampledBitmapFromFile(String fileName, // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; - + 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) { + 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; @@ -492,7 +494,6 @@ private static Bitmap scaleAndRotateBitmap(Bitmap bitmap, ExifInterface ei, int bitmap = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, true); } - // rotate if (ei != null) { final Matrix matrix = new Matrix(); @@ -503,26 +504,25 @@ private static Bitmap scaleAndRotateBitmap(Bitmap bitmap, ExifInterface ei, int } } - 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; + 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; } @@ -536,15 +536,15 @@ private static int calculateRotationAngle(ExifInterface ei) { * @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 */ - public static Bitmap decodeSampledBitmapFromDescriptor( - FileDescriptor fileDescriptor, int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { + public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor, int reqWidth, int reqHeight, + boolean keepAspectRatio, Cache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); + options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; @@ -558,8 +558,7 @@ public static Bitmap decodeSampledBitmapFromDescriptor( try { // This can throw an error on a corrupted image when using an inBitmap results = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); - } - catch (Exception e) { + } catch (Exception e) { // clear the inBitmap and try again options.inBitmap = null; results = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); @@ -571,15 +570,15 @@ public static Bitmap decodeSampledBitmapFromDescriptor( return scaleAndRotateBitmap(results, ei, reqWidth, reqHeight, keepAspectRatio); } - public static Bitmap decodeSampledBitmapFromByteArray( - byte[] buffer, int reqWidth, int reqHeight, boolean keepAspectRatio, Cache cache) { + public static Bitmap decodeSampledBitmapFromByteArray(byte[] buffer, int reqWidth, int reqHeight, + boolean keepAspectRatio, Cache cache) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight, keepAspectRatio); + options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; @@ -603,18 +602,17 @@ public static Bitmap decodeSampledBitmapFromByteArray( * 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. * - * @param options An options object with out* params already populated (run through a decode* - * method with inJustDecodeBounds==true + * @param imageWidth The original width of the resulting bitmap + * @param imageHeight The original height of the resulting bitmap * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @return The value to be used for inSampleSize */ - public static int calculateInSampleSize(BitmapFactory.Options options, - int reqWidth, int reqHeight, boolean keepAspectRatio) { + public static int calculateInSampleSize(int imageWidth, int imageHeight, int reqWidth, int reqHeight) { // BEGIN_INCLUDE (calculate_sample_size) // Raw height and width of image - final int height = options.outHeight; - final int width = options.outWidth; + final int height = imageHeight; + final int width = imageWidth; reqWidth = reqWidth > 0 ? reqWidth : width; reqHeight = reqHeight > 0 ? reqHeight : height; int inSampleSize = 1; @@ -625,8 +623,7 @@ public static int calculateInSampleSize(BitmapFactory.Options options, // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. - while ((halfHeight / inSampleSize) > reqHeight - && (halfWidth / inSampleSize) > reqWidth) { + while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; }