From bd32882a22d6144017da063bb97a563e38f9305e Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 12:04:54 +0200 Subject: [PATCH 01/10] return isPartial = false from getLibrary --- src/android/PhotoLibrary.java | 11 +++++++---- src/ios/PhotoLibrary.swift | 12 ++++++------ www/PhotoLibrary.js | 20 ++++++++++++++++---- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/android/PhotoLibrary.java b/src/android/PhotoLibrary.java index ec6f765d..29109642 100644 --- a/src/android/PhotoLibrary.java +++ b/src/android/PhotoLibrary.java @@ -62,7 +62,10 @@ public void run() { } ArrayList library = service.getLibrary(getContext()); - callbackContext.success(new JSONArray(library)); + JSONObject result = new JSONObject(); + result.put("isPartial", false); + result.put("library", new JSONArray(library)); + callbackContext.success(result); } catch (Exception e) { e.printStackTrace(); @@ -89,7 +92,7 @@ public void run() { } PhotoLibraryService.PictureData thumbnail = service.getThumbnail(getContext(), photoId, thumbnailWidth, thumbnailHeight, quality); - callbackContext.sendPluginResult(createPluginResult(PluginResult.Status.OK, thumbnail)); + callbackContext.sendPluginResult(createMultipartPluginResult(PluginResult.Status.OK, thumbnail)); } catch (Exception e) { e.printStackTrace(); @@ -113,7 +116,7 @@ public void run() { } PhotoLibraryService.PictureData photo = service.getPhoto(getContext(), photoId); - callbackContext.sendPluginResult(createPluginResult(PluginResult.Status.OK, photo)); + callbackContext.sendPluginResult(createMultipartPluginResult(PluginResult.Status.OK, photo)); } catch (Exception e) { e.printStackTrace(); @@ -307,7 +310,7 @@ private Context getContext() { } - private PluginResult createPluginResult(PluginResult.Status status, PhotoLibraryService.PictureData pictureData) { + private PluginResult createMultipartPluginResult(PluginResult.Status status, PhotoLibraryService.PictureData pictureData) { return new PluginResult(status, Arrays.asList( diff --git a/src/ios/PhotoLibrary.swift b/src/ios/PhotoLibrary.swift index 5f977298..9f58b78b 100644 --- a/src/ios/PhotoLibrary.swift +++ b/src/ios/PhotoLibrary.swift @@ -17,7 +17,7 @@ import Foundation // Will sort by creation date func getLibrary(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) @@ -31,7 +31,7 @@ import Foundation let thumbnailHeight = options["thumbnailHeight"] as! Int service.getLibrary(thumbnailWidth, thumbnailHeight: thumbnailHeight) { (library) in - let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: library) + let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAsMultipart: [library, false]) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) } @@ -40,7 +40,7 @@ import Foundation func getThumbnail(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) @@ -75,7 +75,7 @@ import Foundation func getPhoto(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) @@ -131,7 +131,7 @@ import Foundation func saveImage(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) @@ -158,7 +158,7 @@ import Foundation func saveVideo(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) diff --git a/www/PhotoLibrary.js b/www/PhotoLibrary.js index 0baaa980..1326880a 100644 --- a/www/PhotoLibrary.js +++ b/www/PhotoLibrary.js @@ -10,7 +10,7 @@ var isBrowser = cordova.platformId == 'browser'; var photoLibrary = {}; // Will start caching for specified size -photoLibrary.getLibrary = function (success, error, options) { +photoLibrary.getLibrary = function (success, error, options, partialCallback) { if (!options) { options = {}; @@ -23,8 +23,20 @@ photoLibrary.getLibrary = function (success, error, options) { }; cordova.exec( - function (library) { + function (result) { + + var library = result.library; + var isPartial = result.isPartial; + + if (isPartial) { + if (typeof partialCallback === 'function') { + addUrlsToLibrary(library, partialCallback, options); + } + return; + } + addUrlsToLibrary(library, success, options); + }, error, 'PhotoLibrary', @@ -228,7 +240,7 @@ var getRequestAuthenticationOptionsWithDefaults = function (options) { }; -var addUrlsToLibrary = function (library, success, options) { +var addUrlsToLibrary = function (library, callback, options) { var urlsLeft = library.length; @@ -236,7 +248,7 @@ var addUrlsToLibrary = function (library, success, options) { libraryItem.photoURL = photoURL; urlsLeft -= 1; if (urlsLeft === 0) { - success(library); + callback(library); } }; From 86ad3953675e3a99c40bf922074ade7eb7e97b79 Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 13:13:59 +0200 Subject: [PATCH 02/10] Fixed swift to return isPartial --- src/ios/PhotoLibrary.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ios/PhotoLibrary.swift b/src/ios/PhotoLibrary.swift index 9f58b78b..34b7fc3e 100644 --- a/src/ios/PhotoLibrary.swift +++ b/src/ios/PhotoLibrary.swift @@ -29,10 +29,17 @@ import Foundation let options = command.arguments[0] as! NSDictionary let thumbnailWidth = options["thumbnailWidth"] as! Int let thumbnailHeight = options["thumbnailHeight"] as! Int - + service.getLibrary(thumbnailWidth, thumbnailHeight: thumbnailHeight) { (library) in - let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAsMultipart: [library, false]) + + let result: NSDictionary = [ + "isPartial": false, + "library": library + ] + + let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: result as! [String: AnyObject]) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) + } } From 1e0432f47b09fd250f2f34b08635a4ada6e7a4ab Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 13:34:40 +0200 Subject: [PATCH 03/10] ios: added partialCallback to service --- src/ios/PhotoLibrary.swift | 148 +++++++++++++++++------------- src/ios/PhotoLibraryService.swift | 2 +- 2 files changed, 83 insertions(+), 67 deletions(-) diff --git a/src/ios/PhotoLibrary.swift b/src/ios/PhotoLibrary.swift index 34b7fc3e..c5ce7af0 100644 --- a/src/ios/PhotoLibrary.swift +++ b/src/ios/PhotoLibrary.swift @@ -1,69 +1,85 @@ import Foundation @objc(PhotoLibrary) class PhotoLibrary : CDVPlugin { - + override func pluginInitialize() { - + // Do not call PhotoLibraryService here, as it will cause permission prompt to appear on app start. - + URLProtocol.registerClass(PhotoLibraryProtocol.self) - + } - -// override func onMemoryWarning() { -// self.service.stopCaching() -// } - + + // override func onMemoryWarning() { + // self.service.stopCaching() + // } + // Will sort by creation date func getLibrary(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) return } - + let service = PhotoLibraryService.instance - + let options = command.arguments[0] as! NSDictionary let thumbnailWidth = options["thumbnailWidth"] as! Int let thumbnailHeight = options["thumbnailHeight"] as! Int - service.getLibrary(thumbnailWidth, thumbnailHeight: thumbnailHeight) { (library) in - + func createResult (library: [NSDictionary], isPartial: Bool) -> [String: AnyObject] { let result: NSDictionary = [ - "isPartial": false, + "isPartial": isPartial, "library": library ] - - let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: result as! [String: AnyObject]) - self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) - + return result as! [String: AnyObject] } - + + service.getLibrary( + thumbnailWidth, thumbnailHeight: thumbnailHeight, + partialCallback: { (library) in + + let result = createResult(library: library, isPartial: true) + + let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: result) + pluginResult!.setKeepCallbackAs(true) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) + + }, + completion: { (library) in + + let result = createResult(library: library, isPartial: false) + + let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: result) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) + + }) + } } - + func getThumbnail(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) return } - + let service = PhotoLibraryService.instance - + let photoId = command.arguments[0] as! String let options = command.arguments[1] as! NSDictionary let thumbnailWidth = options["thumbnailWidth"] as! Int let thumbnailHeight = options["thumbnailHeight"] as! Int let quality = options["quality"] as! Float - + service.getThumbnail(photoId, thumbnailWidth: thumbnailWidth, thumbnailHeight: thumbnailHeight, quality: quality) { (imageData) in - + let pluginResult = imageData != nil ? CDVPluginResult( status: CDVCommandStatus_OK, @@ -72,29 +88,29 @@ import Foundation CDVPluginResult( status: CDVCommandStatus_ERROR, messageAs: "Could not fetch the thumbnail") - + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) - + } - + } } - + func getPhoto(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) return } - + let service = PhotoLibraryService.instance - + let photoId = command.arguments[0] as! String - + service.getPhoto(photoId) { (imageData) in - + let pluginResult = imageData != nil ? CDVPluginResult( status: CDVCommandStatus_OK, @@ -103,53 +119,53 @@ import Foundation CDVPluginResult( status: CDVCommandStatus_ERROR, messageAs: "Could not fetch the image") - + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) - + } - + } } - + func stopCaching(_ command: CDVInvokedUrlCommand) { - + let service = PhotoLibraryService.instance - + service.stopCaching() - + let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) - + } - + func requestAuthorization(_ command: CDVInvokedUrlCommand) { - + let service = PhotoLibraryService.instance - + service.requestAuthorization({ - let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) - self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) - }, failure: { (err) in - let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: err) - self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) - }) - + let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) + }, failure: { (err) in + let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: err) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) + }) + } - + func saveImage(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) return } - + let service = PhotoLibraryService.instance - + let url = command.arguments[0] as! String let album = command.arguments[1] as! String - + service.saveImage(url, album: album) { (url: URL?, error: String?) in if (error != nil) { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: error) @@ -159,24 +175,24 @@ import Foundation self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) } } - + } } - + func saveVideo(_ command: CDVInvokedUrlCommand) { DispatchQueue.global(qos: .default).async { - + if !PhotoLibraryService.hasPermission() { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: PhotoLibraryService.PERMISSION_ERROR) self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) return } - + let service = PhotoLibraryService.instance - + let url = command.arguments[0] as! String let album = command.arguments[1] as! String - + service.saveVideo(url, album: album) { (url: URL?, error: String?) in if (error != nil) { let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: error) @@ -186,8 +202,8 @@ import Foundation self.commandDelegate!.send(pluginResult, callbackId: command.callbackId ) } } - + } } - + } diff --git a/src/ios/PhotoLibraryService.swift b/src/ios/PhotoLibraryService.swift index c1167056..4fec435f 100644 --- a/src/ios/PhotoLibraryService.swift +++ b/src/ios/PhotoLibraryService.swift @@ -76,7 +76,7 @@ final class PhotoLibraryService { } - func getLibrary(_ thumbnailWidth: Int, thumbnailHeight: Int, completion: @escaping (_ result: [NSDictionary]) -> Void) { + func getLibrary(_ thumbnailWidth: Int, thumbnailHeight: Int, partialCallback: @escaping (_ result: [NSDictionary]) -> Void, completion: @escaping (_ result: [NSDictionary]) -> Void) { let fetchResult = PHAsset.fetchAssets(with: .image, options: self.fetchOptions) From f69275f55dcc6f2d872a64bebce0865041125c62 Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 14:14:00 +0200 Subject: [PATCH 04/10] android: added support for partial result to service --- src/android/PhotoLibrary.java | 38 ++++++++++++++++++++++++---- src/android/PhotoLibraryService.java | 13 ++++++++-- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/android/PhotoLibrary.java b/src/android/PhotoLibrary.java index 29109642..21c0c8a7 100644 --- a/src/android/PhotoLibrary.java +++ b/src/android/PhotoLibrary.java @@ -61,11 +61,39 @@ public void run() { return; } - ArrayList library = service.getLibrary(getContext()); - JSONObject result = new JSONObject(); - result.put("isPartial", false); - result.put("library", new JSONArray(library)); - callbackContext.success(result); + service.getLibrary(getContext(), new PhotoLibraryService.MyRunnable() { // partialCallback + @Override + public void run(ArrayList library) { + try { + + JSONObject result = new JSONObject(); + result.put("isPartial", true); + result.put("library", new JSONArray(library)); + + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); + callbackContext.sendPluginResult(pluginResult); + + } catch (Exception e) { + e.printStackTrace(); + callbackContext.error(e.getMessage()); + } + } + }, new PhotoLibraryService.MyRunnable() { // completion + @Override + public void run(ArrayList library) { + try { + + JSONObject result = new JSONObject(); + result.put("isPartial", false); + result.put("library", new JSONArray(library)); + + callbackContext.success(result); + } catch (Exception e) { + e.printStackTrace(); + callbackContext.error(e.getMessage()); + } + } + }); } catch (Exception e) { e.printStackTrace(); diff --git a/src/android/PhotoLibraryService.java b/src/android/PhotoLibraryService.java index 7ddc3b57..cd922826 100644 --- a/src/android/PhotoLibraryService.java +++ b/src/android/PhotoLibraryService.java @@ -62,9 +62,12 @@ public static PhotoLibraryService getInstance() { return instance; } - public ArrayList getLibrary(Context context) throws JSONException { + public void getLibrary(Context context, MyRunnable partialCallback, MyRunnable completion) throws JSONException { - return queryLibrary(context, ""); + // TODO: make use of partialCallback + + String whereClause = ""; + completion.run(queryLibrary(context, whereClause)); } @@ -619,4 +622,10 @@ private void saveMedia(CordovaInterface cordova, String url, String album, Map data); + + } + } From 9b241af5d166bb8b3918f1620105297bfecf7206 Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 15:23:14 +0200 Subject: [PATCH 05/10] ios: partial result each 0.5 sec --- src/ios/PhotoLibraryService.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/ios/PhotoLibraryService.swift b/src/ios/PhotoLibraryService.swift index 4fec435f..637652fa 100644 --- a/src/ios/PhotoLibraryService.swift +++ b/src/ios/PhotoLibraryService.swift @@ -32,6 +32,9 @@ final class PhotoLibraryService { static let PERMISSION_ERROR = "Permission Denial: This application is not allowed to access Photo data." let dataURLPattern = try! NSRegularExpression(pattern: "^data:.+?;base64,", options: NSRegularExpression.Options(rawValue: 0)) + + // TODO: provide it as option to getLibrary + static let PARTIAL_RESULT_PERIOD_SEC = 0.5 // Waiting time for returning partial results in getLibrary fileprivate init() { fetchOptions = PHFetchOptions() @@ -95,6 +98,13 @@ final class PhotoLibraryService { var library = [NSDictionary?](repeating: nil, count: fetchResult.count) var requestsLeft = fetchResult.count + + var lastPartialResultTime = NSDate() + + func sendPartialResult(_ library: [NSDictionary?]) { + let libraryCopy = library.filter { $0 != nil } + partialCallback(libraryCopy as! [NSDictionary]) + } fetchResult.enumerateObjects({ (asset: PHAsset, index, stop) in @@ -120,6 +130,14 @@ final class PhotoLibraryService { if requestsLeft == 0 { completion(library as! [NSDictionary]) + } else { + // Each PARTIAL_RESULT_PERIOD_SEC seconds provide partial result + let elapsedSec = abs(lastPartialResultTime.timeIntervalSinceNow) + if elapsedSec > PhotoLibraryService.PARTIAL_RESULT_PERIOD_SEC { + lastPartialResultTime = NSDate() + + sendPartialResult(library) + } } } }) From 6479ffb089787c4430207b00970ba686fb944485 Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 15:25:14 +0200 Subject: [PATCH 06/10] android: minor refactoring --- src/android/PhotoLibrary.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/android/PhotoLibrary.java b/src/android/PhotoLibrary.java index 21c0c8a7..f8e03ee7 100644 --- a/src/android/PhotoLibrary.java +++ b/src/android/PhotoLibrary.java @@ -66,10 +66,7 @@ public void run() { public void run(ArrayList library) { try { - JSONObject result = new JSONObject(); - result.put("isPartial", true); - result.put("library", new JSONArray(library)); - + JSONObject result = createGetLibraryResult(library, true); PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result); callbackContext.sendPluginResult(pluginResult); @@ -83,11 +80,9 @@ public void run(ArrayList library) { public void run(ArrayList library) { try { - JSONObject result = new JSONObject(); - result.put("isPartial", false); - result.put("library", new JSONArray(library)); - + JSONObject result = createGetLibraryResult(library, false); callbackContext.success(result); + } catch (Exception e) { e.printStackTrace(); callbackContext.error(e.getMessage()); @@ -362,4 +357,11 @@ private void requestAuthorization(boolean read, boolean write) { cordova.requestPermissions(this, REQUEST_AUTHORIZATION_REQ_CODE, permissions.toArray(new String[0])); } + private static JSONObject createGetLibraryResult(ArrayList library, boolean isPartial) throws JSONException { + JSONObject result = new JSONObject(); + result.put("isPartial", isPartial); + result.put("library", new JSONArray(library)); + return result; + } + } From 9dd1aa3401f318e29964a64421edc85d9f26091f Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 20:00:27 +0200 Subject: [PATCH 07/10] android: fixed rotated thumbnail scale #9 --- src/android/PhotoLibraryService.java | 68 ++++++---------------------- 1 file changed, 14 insertions(+), 54 deletions(-) diff --git a/src/android/PhotoLibraryService.java b/src/android/PhotoLibraryService.java index cd922826..a225271e 100644 --- a/src/android/PhotoLibraryService.java +++ b/src/android/PhotoLibraryService.java @@ -5,10 +5,9 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; import android.graphics.Matrix; -import android.graphics.Paint; import android.media.ExifInterface; +import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; @@ -78,14 +77,6 @@ public PictureData getThumbnail(Context context, String photoId, int thumbnailWi String imageURL = getImageURL(photoId); File imageFile = new File(imageURL); - // if image is rotated by 90 or 270 degrees, swap provided width and height - boolean swapDimensions = getSwapFromPhotoID(photoId); - if (swapDimensions) { - int tempWidth = thumbnailWidth; - thumbnailWidth = thumbnailHeight; - thumbnailHeight = tempWidth; - } - // TODO: maybe it never worth using MediaStore.Images.Thumbnails.getThumbnail, as it returns sizes less than 512x384? if (thumbnailWidth == 512 && thumbnailHeight == 384) { // In such case, thumbnail will be cached by MediaStore int imageId = getImageId(photoId); @@ -115,25 +106,24 @@ public PictureData getThumbnail(Context context, String photoId, int thumbnailWi if (bitmap != null) { - // resize to exact size needed - Bitmap resizedBitmap = resizeBitmap(bitmap, thumbnailWidth, thumbnailHeight); - if (bitmap != resizedBitmap) { + // correct image orientation + int orientation = getImageOrientation(imageFile); + Bitmap rotatedBitmap = rotateImage(bitmap, orientation); + if (bitmap != rotatedBitmap) { bitmap.recycle(); } - // correct image orientation - int orientation = getImageOrientation(imageFile); - Bitmap rotatedBitmap = rotateImage(resizedBitmap, orientation); - if (resizedBitmap != rotatedBitmap) { - resizedBitmap.recycle(); + Bitmap thumbnailBitmap = ThumbnailUtils.extractThumbnail(rotatedBitmap, thumbnailWidth, thumbnailHeight); + if (rotatedBitmap != thumbnailBitmap) { + rotatedBitmap.recycle(); } // TODO: cache bytes for performance - byte[] bytes = getJpegBytesFromBitmap(rotatedBitmap, quality); + byte[] bytes = getJpegBytesFromBitmap(thumbnailBitmap, quality); String mimeType = "image/jpeg"; - rotatedBitmap.recycle(); + thumbnailBitmap.recycle(); return new PictureData(bytes, mimeType); @@ -320,8 +310,6 @@ private ArrayList queryLibrary(Context context, String whereClause) System.err.println(queryResult); } else { - boolean swapDimensions = false; - // swap width and height if needed try { int orientation = getImageOrientation(new File(queryResult.getString("nativeURL"))); @@ -329,17 +317,15 @@ private ArrayList queryLibrary(Context context, String whereClause) int tempWidth = queryResult.getInt("width"); queryResult.put("width", queryResult.getInt("height")); queryResult.put("height", tempWidth); - swapDimensions = true; } } catch (IOException e) { // Do nothing } - // photoId is in format "imageid;imageurl;" or "imageid;imageurl;swap" + // photoId is in format "imageid;imageurl" queryResult.put("id", queryResult.get("id") + ";" + - queryResult.get("nativeURL") + ";" + - (swapDimensions ? "swap" : "")); + queryResult.get("nativeURL")); results.add(queryResult); } @@ -444,33 +430,6 @@ private static String getImageURL(String photoId) { return photoId.split(";")[1]; } - // photoId is in format "imageid;imageurl;[swap]" - private static boolean getSwapFromPhotoID(String photoId) { - String[] split = photoId.split(";"); - return split.length >=3 && split[2].equals("swap"); - } - - // from http://stackoverflow.com/a/15441311/1691132 - private static Bitmap resizeBitmap(Bitmap bitmap, int width, int height) { - - if (bitmap.getWidth() == width && bitmap.getHeight() == height) { - return bitmap; - } - - Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - float originalWidth = bitmap.getWidth(), originalHeight = bitmap.getHeight(); - Canvas canvas = new Canvas(result); - float scale = width/originalWidth; - float xTranslation = 0.0f, yTranslation = (height - originalHeight * scale)/2.0f; - Matrix transformation = new Matrix(); - transformation.postTranslate(xTranslation, yTranslation); - transformation.preScale(scale, scale); - Paint paint = new Paint(); - paint.setFilterBitmap(true); - canvas.drawBitmap(bitmap, transformation, paint); - return result; - } - private static int getImageOrientation(File imageFile) throws IOException { ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); @@ -480,13 +439,14 @@ private static int getImageOrientation(File imageFile) throws IOException { } + // see http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ private static Bitmap rotateImage(Bitmap source, int orientation) { Matrix matrix = new Matrix(); switch (orientation) { case ExifInterface.ORIENTATION_NORMAL: // 1 - return source; + return source; case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: // 2 matrix.setScale(-1, 1); break; From 5613157766657403f8b27f01de2421f29980db26 Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 20:22:03 +0200 Subject: [PATCH 08/10] updated typescript typings --- PhotoLibrary.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PhotoLibrary.d.ts b/PhotoLibrary.d.ts index 6b562db7..162370a5 100644 --- a/PhotoLibrary.d.ts +++ b/PhotoLibrary.d.ts @@ -1,7 +1,7 @@ declare module PhotoLibraryCordova { export interface Plugin { - getLibrary(success: (result: LibraryItem[]) => void, error: (err: any) => void, options: GetLibraryOptions): void; + getLibrary(success: (result: LibraryItem[]) => void, error: (err: any) => void, options: GetLibraryOptions, partialCallback: (result: string) => void): void; getThumbnailURL(photoId: string, success: (result: string) => void, error: (err: any) => void, options: GetThumbnailOptions): void; getThumbnailURL(libraryItem: LibraryItem, success: (result: string) => void, error: (err: any) => void, options: GetThumbnailOptions): void; From b007eacfff095b67782b3a3ac107993692e530bd Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 20:22:21 +0200 Subject: [PATCH 09/10] added partialCallback to README --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7a89ab39..dca31e60 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,14 @@ cordova.plugins.photoLibrary.getLibrary( function (err) { console.log('Error occured'); }, - { + { // optional options thumbnailWidth: 512, thumbnailHeight: 384, quality: 0.8 + }, + function partialCallback(partialLibrary) { // optional + // If this callback provided and loading library takes time, it will be called each 0.5 seconds with + // library that filled up to this time. You can start displaying photos to user right then. } ); ``` @@ -128,7 +132,7 @@ cordova.plugins.photoLibrary.getThumbnailURL( function (err) { console.log('Error occured'); }, - { + { // optional options thumbnailWidth: 512, thumbnailHeight: 384, quality: 0.8 @@ -158,7 +162,7 @@ cordova.plugins.photoLibrary.getThumbnail( function (err) { console.log('Error occured'); }, - { + { // optional options thumbnailWidth: 512, thumbnailHeight: 384, quality: 0.8 @@ -196,6 +200,7 @@ TypeScript definitions are provided in [PhotoLibrary.d.ts](https://github.com/te - saveImage and saveVideo should return saved libraryItem. - Improve documentation. - EXIF rotation hell is not handled on browser platform. +- partialCallback currently implemented only for iOS platform. android and browser platform implementations needed. # References From ac651653959dc00e7bf040c11e12db69f551b03b Mon Sep 17 00:00:00 2001 From: viskin Date: Thu, 17 Nov 2016 21:17:18 +0200 Subject: [PATCH 10/10] fixed isPartial result in browser platform --- src/browser/PhotoLibraryProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/PhotoLibraryProxy.js b/src/browser/PhotoLibraryProxy.js index 421b70e2..b59c39d4 100644 --- a/src/browser/PhotoLibraryProxy.js +++ b/src/browser/PhotoLibraryProxy.js @@ -13,7 +13,7 @@ var photoLibraryProxy = { files2Library(files).then(lib => { library = lib; removeFilesElement(filesElement); - success(library); + success({ library: library, isPartial: false }); }); }, false);