From 270432154ddaf9c4fa17de1d1924139939f74384 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Tue, 13 Apr 2021 22:02:35 +0800 Subject: [PATCH] add "mimeType" parameter in "getFile" API to allow specifying image format when get full image on both platforms --- .../morbit/photogallery/PhotoGalleryPlugin.kt | 73 ++++++++++++++++--- ios/Classes/SwiftPhotoGalleryPlugin.swift | 28 ++++++- lib/photo_gallery.dart | 5 +- lib/src/image_providers/photo_provider.dart | 4 +- 4 files changed, 98 insertions(+), 12 deletions(-) diff --git a/android/src/main/kotlin/com/morbit/photogallery/PhotoGalleryPlugin.kt b/android/src/main/kotlin/com/morbit/photogallery/PhotoGalleryPlugin.kt index f08d1dc..974c9b2 100644 --- a/android/src/main/kotlin/com/morbit/photogallery/PhotoGalleryPlugin.kt +++ b/android/src/main/kotlin/com/morbit/photogallery/PhotoGalleryPlugin.kt @@ -15,9 +15,11 @@ import android.provider.MediaStore import android.content.Context import android.database.Cursor import android.database.Cursor.FIELD_TYPE_INTEGER +import android.graphics.ImageDecoder import android.os.AsyncTask import android.os.Build import android.util.Size +import java.io.FileOutputStream /** PhotoGalleryPlugin */ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler { @@ -138,8 +140,9 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler { "getFile" -> { val mediumId = call.argument("mediumId") val mediumType = call.argument("mediumType") + val mimeType = call.argument("mimeType") BackgroundAsyncTask({ - getFile(mediumId!!, mediumType) + getFile(mediumId!!, mediumType, mimeType) }, { v -> result.success(v) }) @@ -666,24 +669,31 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler { } } - private fun getFile(mediumId: String, mediumType: String?): String? { + private fun getFile(mediumId: String, mediumType: String?, mimeType: String?): String? { return when (mediumType) { imageType -> { - getImageFile(mediumId) + getImageFile(mediumId, mimeType = mimeType) } videoType -> { getVideoFile(mediumId) } else -> { - getImageFile(mediumId) ?: getVideoFile(mediumId) + getImageFile(mediumId, mimeType = mimeType) ?: getVideoFile(mediumId) } } } - private fun getImageFile(mediumId: String): String? { - var path: String? = null - + private fun getImageFile(mediumId: String, mimeType: String? = null): String? { this.context?.run { + mimeType?.let { + val type = this.contentResolver.getType( + ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediumId.toLong()) + ) + if (it != type) { + return cacheImage(mediumId, it) + } + } + val imageCursor = this.contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Images.Media.DATA), @@ -695,12 +705,12 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler { imageCursor?.use { cursor -> if (cursor.moveToNext()) { val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA) - path = cursor.getString(dataColumn) + return cursor.getString(dataColumn) } } } - return path + return null } private fun getVideoFile(mediumId: String): String? { @@ -725,6 +735,51 @@ class PhotoGalleryPlugin : FlutterPlugin, MethodCallHandler { return path } + private fun cacheImage(mediumId: String, mimeType: String): String? { + val bitmap: Bitmap? = this.context?.run { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + try { + ImageDecoder.decodeBitmap(ImageDecoder.createSource( + this.contentResolver, + ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediumId.toLong()) + )) + } catch (e: Exception) { + null + } + } else { + MediaStore.Images.Media.getBitmap( + this.contentResolver, + ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mediumId.toLong()) + ) + } + } + + bitmap?.let { + val compressFormat: Bitmap.CompressFormat + if (mimeType == "image/jpeg") { + val path = File(getCachePath(), "$mediumId.jpeg") + val out = FileOutputStream(path) + compressFormat = Bitmap.CompressFormat.JPEG + it.compress(compressFormat, 100, out) + return path.absolutePath + } else if (mimeType == "image/png") { + val path = File(getCachePath(), "$mediumId.png") + val out = FileOutputStream(path) + compressFormat = Bitmap.CompressFormat.PNG + it.compress(compressFormat, 100, out) + return path.absolutePath + } else if (mimeType == "image/webp") { + val path = File(getCachePath(), "$mediumId.webp") + val out = FileOutputStream(path) + compressFormat = Bitmap.CompressFormat.WEBP + it.compress(compressFormat, 100, out) + return path.absolutePath + } + } + + return null + } + private fun getImageMetadata(cursor: Cursor): Map { val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID) val widthColumn = cursor.getColumnIndex(MediaStore.Images.Media.WIDTH) diff --git a/ios/Classes/SwiftPhotoGalleryPlugin.swift b/ios/Classes/SwiftPhotoGalleryPlugin.swift index 281b4e0..d56084a 100644 --- a/ios/Classes/SwiftPhotoGalleryPlugin.swift +++ b/ios/Classes/SwiftPhotoGalleryPlugin.swift @@ -70,8 +70,10 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin { else if(call.method == "getFile") { let arguments = call.arguments as! Dictionary let mediumId = arguments["mediumId"] as! String + let mimeType = arguments["mimeType"] as? String getFile( mediumId: mediumId, + mimeType: mimeType, completion: { (filepath: String?, error: Error?) -> Void in result(filepath?.replacingOccurrences(of: "file://", with: "")) }) @@ -319,7 +321,7 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin { completion(nil , NSError(domain: "photo_gallery", code: 404, userInfo: nil)) } - private func getFile(mediumId: String, completion: @escaping (String?, Error?) -> Void) { + private func getFile(mediumId: String, mimeType: String?, completion: @escaping (String?, Error?) -> Void) { let manager = PHImageManager.default() let fetchOptions = PHFetchOptions() @@ -350,6 +352,14 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin { completion(nil, NSError(domain: "photo_gallery", code: 404, userInfo: nil)) return } + if mimeType != nil { + let type = self.extractMimeTypeFromUTI(uti: assetUTI) + if type != mimeType { + let path = self.cacheImage(asset: asset, data: imageData, mimeType: mimeType!) + completion(path, NSError(domain: "photo_gallery", code: 404, userInfo: nil)) + return + } + } let fileExt = self.extractFileExtensionFromUTI(uti: assetUTI) let filepath = self.exportPathForAsset(asset: asset, ext: fileExt) try! imageData.write(to: filepath, options: .atomic) @@ -383,6 +393,22 @@ public class SwiftPhotoGalleryPlugin: NSObject, FlutterPlugin { } } + private func cacheImage(asset: PHAsset, data: Data, mimeType: String) -> String? { + if mimeType == "image/jpeg" { + let filepath = self.exportPathForAsset(asset: asset, ext: ".jpeg") + let uiImage = UIImage(data: data) + try! uiImage?.jpegData(compressionQuality: 100)?.write(to: filepath, options: .atomic) + return filepath.absoluteString + } else if mimeType == "image/png" { + let filepath = self.exportPathForAsset(asset: asset, ext: ".png") + let uiImage = UIImage(data: data) + try! uiImage?.pngData()?.write(to: filepath, options: .atomic) + return filepath.absoluteString + } else { + return nil + } + } + private func getMediumFromAsset(asset: PHAsset) -> [String: Any?] { let mimeType = self.extractMimeTypeFromAsset(asset: asset) return [ diff --git a/lib/photo_gallery.dart b/lib/photo_gallery.dart index abf8aea..eeb5505 100644 --- a/lib/photo_gallery.dart +++ b/lib/photo_gallery.dart @@ -103,11 +103,14 @@ class PhotoGallery { static Future getFile({ required String mediumId, MediumType? mediumType, + String? mimeType, }) async { final path = await _channel.invokeMethod('getFile', { 'mediumId': mediumId, 'mediumType': mediumTypeToJson(mediumType), - }) as String; + 'mimeType': mimeType, + }) as String?; + if (path == null) throw "Cannot get file $mediumId with type $mimeType"; return File(path); } diff --git a/lib/src/image_providers/photo_provider.dart b/lib/src/image_providers/photo_provider.dart index 7f52305..9f78235 100644 --- a/lib/src/image_providers/photo_provider.dart +++ b/lib/src/image_providers/photo_provider.dart @@ -4,9 +4,11 @@ part of photogallery; class PhotoProvider extends ImageProvider { PhotoProvider({ required this.mediumId, + this.mimeType, }); final String mediumId; + final String? mimeType; @override ImageStreamCompleter load(key, decode) { @@ -22,7 +24,7 @@ class PhotoProvider extends ImageProvider { Future _loadAsync(PhotoProvider key, DecoderCallback decode) async { assert(key == this); final file = await PhotoGallery.getFile( - mediumId: mediumId, mediumType: MediumType.image); + mediumId: mediumId, mediumType: MediumType.image, mimeType: mimeType); final bytes = await file.readAsBytes();