diff --git a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/BitmapUtil.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/BitmapUtil.kt index 6b931b8f..9b81460f 100644 --- a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/BitmapUtil.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/BitmapUtil.kt @@ -21,224 +21,214 @@ fun Bitmap.aspectRatio() = width / height.toFloat() */ @OptIn(ExperimentalContracts::class) inline fun Bitmap.use(block: (Bitmap) -> T): T { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - try { - return block(this) - } finally { - recycle() - } + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + return block(this) + } finally { + recycle() + } } enum class BitmapResizeMethod { - FIT, FILL, + FIT, FILL, } interface BitmapUtil { - companion object { - fun loadImageFixed( - context: Context, uri: Uri, targetW: Int, targetH: Int - ): Bitmap { - val opt = loadImageBounds(context, uri) - val subsample = calcBitmapSubsample( - opt.outWidth, - opt.outHeight, - targetW, - targetH, - BitmapResizeMethod.FILL - ) - if (subsample > 1) { - logD( - TAG, - "Subsample image to fixed: $subsample ${opt.outWidth}x${opt.outHeight} -> ${targetW}x$targetH" - ) - } - val outOpt = BitmapFactory.Options().apply { - inSampleSize = subsample - } - val bitmap = loadImage(context, uri, outOpt) - if (subsample > 1) { - logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}") - } - return Bitmap.createScaledBitmap(bitmap, targetW, targetH, true) - } + companion object { + fun loadImageFixed( + context: Context, uri: Uri, targetW: Int, targetH: Int + ): Bitmap { + val opt = loadImageBounds(context, uri) + val subsample = calcBitmapSubsample( + opt.outWidth, opt.outHeight, targetW, targetH, + BitmapResizeMethod.FILL + ) + if (subsample > 1) { + logD( + TAG, + "Subsample image to fixed: $subsample ${opt.outWidth}x${opt.outHeight} -> ${targetW}x$targetH" + ) + } + val outOpt = BitmapFactory.Options().apply { + inSampleSize = subsample + } + val bitmap = loadImage(context, uri, outOpt) + if (subsample > 1) { + logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}") + } + return Bitmap.createScaledBitmap(bitmap, targetW, targetH, true) + } - /** - * Load a bitmap - * - * If @c resizeMethod == FIT, make sure the size of the bitmap can fit - * inside the bound defined by @c targetW and @c targetH, i.e., - * bitmap.w <= @c targetW and bitmap.h <= @c targetH - * - * If @c resizeMethod == FILL, make sure the size of the bitmap can - * completely fill the bound defined by @c targetW and @c targetH, i.e., - * bitmap.w >= @c targetW and bitmap.h >= @c targetH - * - * If bitmap is smaller than the bound and @c shouldUpscale == true, it - * will be upscaled - * - * @param context - * @param uri - * @param targetW - * @param targetH - * @param resizeMethod - * @param isAllowSwapSide - * @param shouldUpscale - * @return - */ - fun loadImage( - context: Context, - uri: Uri, - targetW: Int, - targetH: Int, - resizeMethod: BitmapResizeMethod, - isAllowSwapSide: Boolean = false, - shouldUpscale: Boolean = true, - shouldFixOrientation: Boolean = false, - ): Bitmap { - val opt = loadImageBounds(context, uri) - val shouldSwapSide = - isAllowSwapSide && opt.outWidth != opt.outHeight && (opt.outWidth >= opt.outHeight) != (targetW >= targetH) - val dstW = if (shouldSwapSide) targetH else targetW - val dstH = if (shouldSwapSide) targetW else targetH - val subsample = calcBitmapSubsample( - opt.outWidth, opt.outHeight, dstW, dstH, resizeMethod - ) - if (subsample > 1) { - logD( - TAG, - "Subsample image to ${resizeMethod.name}: $subsample ${opt.outWidth}x${opt.outHeight} -> ${dstW}x$dstH" + (if (shouldSwapSide) " (swapped)" else "") - ) - } - val outOpt = BitmapFactory.Options().apply { - inSampleSize = subsample - } - val bitmap = loadImage(context, uri, outOpt) - if (subsample > 1) { - logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}") - } - if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) { - return if (shouldFixOrientation) { - fixOrientation(context, uri, bitmap) - } else { - bitmap - } - } - val result = when (resizeMethod) { - BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap( - bitmap, - minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()), - minOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), - true - ) + /** + * Load a bitmap + * + * If @c resizeMethod == FIT, make sure the size of the bitmap can fit + * inside the bound defined by @c targetW and @c targetH, i.e., + * bitmap.w <= @c targetW and bitmap.h <= @c targetH + * + * If @c resizeMethod == FILL, make sure the size of the bitmap can + * completely fill the bound defined by @c targetW and @c targetH, i.e., + * bitmap.w >= @c targetW and bitmap.h >= @c targetH + * + * If bitmap is smaller than the bound and @c shouldUpscale == true, it + * will be upscaled + * + * @param context + * @param uri + * @param targetW + * @param targetH + * @param resizeMethod + * @param isAllowSwapSide + * @param shouldUpscale + * @return + */ + fun loadImage( + context: Context, + uri: Uri, + targetW: Int, + targetH: Int, + resizeMethod: BitmapResizeMethod, + isAllowSwapSide: Boolean = false, + shouldUpscale: Boolean = true, + shouldFixOrientation: Boolean = false, + ): Bitmap { + val opt = loadImageBounds(context, uri) + val shouldSwapSide = + isAllowSwapSide && opt.outWidth != opt.outHeight && (opt.outWidth >= opt.outHeight) != (targetW >= targetH) + val dstW = if (shouldSwapSide) targetH else targetW + val dstH = if (shouldSwapSide) targetW else targetH + val subsample = calcBitmapSubsample( + opt.outWidth, opt.outHeight, dstW, dstH, resizeMethod + ) + if (subsample > 1) { + logD( + TAG, + "Subsample image to ${resizeMethod.name}: $subsample ${opt.outWidth}x${opt.outHeight} -> ${dstW}x$dstH" + (if (shouldSwapSide) " (swapped)" else "") + ) + } + val outOpt = BitmapFactory.Options().apply { + inSampleSize = subsample + } + val bitmap = loadImage(context, uri, outOpt) + if (subsample > 1) { + logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}") + } + if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) { + return if (shouldFixOrientation) { + fixOrientation(context, uri, bitmap) + } else { + bitmap + } + } + val result = when (resizeMethod) { + BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap( + bitmap, minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()), + minOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), true + ) - BitmapResizeMethod.FILL -> Bitmap.createScaledBitmap( - bitmap, - maxOf(dstW, (dstH * bitmap.aspectRatio()).toInt()), - maxOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), - true - ) - } - return if (shouldFixOrientation) { - fixOrientation(context, uri, result) - } else { - result - } - } + BitmapResizeMethod.FILL -> Bitmap.createScaledBitmap( + bitmap, maxOf(dstW, (dstH * bitmap.aspectRatio()).toInt()), + maxOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), true + ) + } + return if (shouldFixOrientation) { + fixOrientation(context, uri, result) + } else { + result + } + } - /** - * Rotate the image to its visible orientation - */ - private fun fixOrientation( - context: Context, uri: Uri, bitmap: Bitmap - ): Bitmap { - return try { - openUriInputStream(context, uri)!!.use { - val iExif = ExifInterface(it) - val orientation = - iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1) - logI( - TAG, - "[fixOrientation] Input file orientation: $orientation" - ) - val rotate = when (orientation) { - ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE -> 90f + /** + * Rotate the image to its visible orientation + */ + private fun fixOrientation( + context: Context, uri: Uri, bitmap: Bitmap + ): Bitmap { + return try { + openUriInputStream(context, uri)!!.use { + val iExif = ExifInterface(it) + val orientation = + iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1) + logI( + TAG, + "[fixOrientation] Input file orientation: $orientation" + ) + val rotate = when (orientation) { + ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE -> 90f - ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180f + ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180f - ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE -> 270f + ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE -> 270f - ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> 0f - else -> return bitmap - } - val mat = Matrix() - mat.postRotate(rotate) - if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || orientation == ExifInterface.ORIENTATION_TRANSVERSE || orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || orientation == ExifInterface.ORIENTATION_TRANSPOSE) { - mat.postScale(-1f, 1f) - } - Bitmap.createBitmap( - bitmap, 0, 0, bitmap.width, bitmap.height, mat, true - ) - } - } catch (e: Throwable) { - logE( - TAG, - "[fixOrientation] Failed fixing, assume normal orientation", - e - ) - bitmap - } - } + ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> 0f + else -> return bitmap + } + val mat = Matrix() + mat.postRotate(rotate) + if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || orientation == ExifInterface.ORIENTATION_TRANSVERSE || orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || orientation == ExifInterface.ORIENTATION_TRANSPOSE) { + mat.postScale(-1f, 1f) + } + Bitmap.createBitmap( + bitmap, 0, 0, bitmap.width, bitmap.height, mat, true + ) + } + } catch (e: Throwable) { + logE( + TAG, + "[fixOrientation] Failed fixing, assume normal orientation", + e + ) + bitmap + } + } - private fun openUriInputStream( - context: Context, uri: Uri - ): InputStream? { - return if (UriUtil.isAssetUri(uri)) { - context.assets.open(UriUtil.getAssetUriPath(uri)) - } else { - context.contentResolver.openInputStream(uri) - } - } + private fun openUriInputStream( + context: Context, uri: Uri + ): InputStream? { + return if (UriUtil.isAssetUri(uri)) { + context.assets.open(UriUtil.getAssetUriPath(uri)) + } else { + context.contentResolver.openInputStream(uri) + } + } - private fun loadImageBounds( - context: Context, uri: Uri - ): BitmapFactory.Options { - openUriInputStream(context, uri)!!.use { - val opt = BitmapFactory.Options().apply { - inJustDecodeBounds = true - } - BitmapFactory.decodeStream(it, null, opt) - return opt - } - } + private fun loadImageBounds( + context: Context, uri: Uri + ): BitmapFactory.Options { + openUriInputStream(context, uri)!!.use { + val opt = BitmapFactory.Options().apply { + inJustDecodeBounds = true + } + BitmapFactory.decodeStream(it, null, opt) + return opt + } + } - private fun loadImage( - context: Context, uri: Uri, opt: BitmapFactory.Options - ): Bitmap { - openUriInputStream(context, uri)!!.use { - return BitmapFactory.decodeStream(it, null, opt)!! - } - } + private fun loadImage( + context: Context, uri: Uri, opt: BitmapFactory.Options + ): Bitmap { + openUriInputStream(context, uri)!!.use { + return BitmapFactory.decodeStream(it, null, opt)!! + } + } - private fun calcBitmapSubsample( - originalW: Int, - originalH: Int, - targetW: Int, - targetH: Int, - resizeMethod: BitmapResizeMethod - ): Int { - return when (resizeMethod) { - BitmapResizeMethod.FIT -> maxOf( - originalW / targetW, originalH / targetH - ) + private fun calcBitmapSubsample( + originalW: Int, originalH: Int, targetW: Int, targetH: Int, + resizeMethod: BitmapResizeMethod + ): Int { + return when (resizeMethod) { + BitmapResizeMethod.FIT -> maxOf( + originalW / targetW, originalH / targetH + ) - BitmapResizeMethod.FILL -> minOf( - originalW / targetW, originalH / targetH - ) - } - } + BitmapResizeMethod.FILL -> minOf( + originalW / targetW, originalH / targetH + ) + } + } - private const val TAG = "BitmapUtil" - } + private const val TAG = "BitmapUtil" + } } diff --git a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/MediaStoreUtil.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/MediaStoreUtil.kt index af991205..71449651 100644 --- a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/MediaStoreUtil.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/MediaStoreUtil.kt @@ -113,10 +113,10 @@ interface MediaStoreUtil { throw PermissionException("Permission not granted") } - @Suppress("Deprecation") - val path = Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOWNLOADS - ) + @Suppress("Deprecation") val path = + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ) val prefix = if (subDir != null) "$subDir/" else "" var file = File(path, prefix + filename) val baseFilename = file.nameWithoutExtension @@ -142,8 +142,8 @@ interface MediaStoreUtil { private fun triggerMediaScan(context: Context, uri: Uri) { val scanIntent = Intent().apply { - @Suppress("Deprecation") - action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE + @Suppress("Deprecation") action = + Intent.ACTION_MEDIA_SCANNER_SCAN_FILE data = uri } context.sendBroadcast(scanIntent) diff --git a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/PermissionUtil.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/PermissionUtil.kt index d83c1bd6..58c935d6 100644 --- a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/PermissionUtil.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/PermissionUtil.kt @@ -8,35 +8,35 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat interface PermissionUtil { - companion object { - const val REQUEST_CODE = K.PERMISSION_REQUEST_CODE + companion object { + const val REQUEST_CODE = K.PERMISSION_REQUEST_CODE - fun request( - activity: Activity, vararg permissions: String - ) { - ActivityCompat.requestPermissions( - activity, permissions, REQUEST_CODE - ) - } + fun request( + activity: Activity, vararg permissions: String + ) { + ActivityCompat.requestPermissions( + activity, permissions, REQUEST_CODE + ) + } - fun hasReadExternalStorage(context: Context): Boolean { - return ContextCompat.checkSelfPermission( - context, Manifest.permission.READ_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED - } + fun hasReadExternalStorage(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, Manifest.permission.READ_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED + } - fun requestReadExternalStorage(activity: Activity) = request( - activity, Manifest.permission.READ_EXTERNAL_STORAGE - ) + fun requestReadExternalStorage(activity: Activity) = request( + activity, Manifest.permission.READ_EXTERNAL_STORAGE + ) - fun hasWriteExternalStorage(context: Context): Boolean { - return ContextCompat.checkSelfPermission( - context, Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PackageManager.PERMISSION_GRANTED - } + fun hasWriteExternalStorage(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, Manifest.permission.WRITE_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED + } - fun requestWriteExternalStorage(activity: Activity) = request( - activity, Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } + fun requestWriteExternalStorage(activity: Activity) = request( + activity, Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + } } diff --git a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Rgba8Image.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Rgba8Image.kt index ffcc8930..f70338b2 100644 --- a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Rgba8Image.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Rgba8Image.kt @@ -7,38 +7,37 @@ import java.nio.ByteBuffer * Container of pixel data stored in RGBA format */ class Rgba8Image( - val pixel: ByteArray, val width: Int, val height: Int + val pixel: ByteArray, val width: Int, val height: Int ) { - companion object { - fun fromJson(json: Map) = Rgba8Image( - json["pixel"] as ByteArray, - json["width"] as Int, - json["height"] as Int - ) + companion object { + fun fromJson(json: Map) = Rgba8Image( + json["pixel"] as ByteArray, json["width"] as Int, + json["height"] as Int + ) - fun fromBitmap(src: Bitmap): Rgba8Image { - assert(src.config == Bitmap.Config.ARGB_8888) - val buffer = ByteBuffer.allocate(src.width * src.height * 4).also { - src.copyPixelsToBuffer(it) - } - return Rgba8Image(buffer.array(), src.width, src.height) - } - } + fun fromBitmap(src: Bitmap): Rgba8Image { + assert(src.config == Bitmap.Config.ARGB_8888) + val buffer = ByteBuffer.allocate(src.width * src.height * 4).also { + src.copyPixelsToBuffer(it) + } + return Rgba8Image(buffer.array(), src.width, src.height) + } + } - fun toJson() = mapOf( - "pixel" to pixel, - "width" to width, - "height" to height, - ) + fun toJson() = mapOf( + "pixel" to pixel, + "width" to width, + "height" to height, + ) - fun toBitmap(): Bitmap { - return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - .apply { - copyPixelsFromBuffer(ByteBuffer.wrap(pixel)) - } - } + fun toBitmap(): Bitmap { + return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + .apply { + copyPixelsFromBuffer(ByteBuffer.wrap(pixel)) + } + } - init { - assert(pixel.size == width * height * 4) - } + init { + assert(pixel.size == width * height * 4) + } } diff --git a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Util.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Util.kt index 592cd02e..4dd9083e 100644 --- a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Util.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Util.kt @@ -7,13 +7,11 @@ import java.io.Serializable import java.net.HttpURLConnection fun getPendingIntentFlagImmutable(): Int { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - PendingIntent.FLAG_IMMUTABLE else 0 + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_IMMUTABLE else 0 } fun getPendingIntentFlagMutable(): Int { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - PendingIntent.FLAG_MUTABLE else 0 + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0 } inline fun HttpURLConnection.use(block: (HttpURLConnection) -> T): T { diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Event.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Event.kt index 4e9d3922..d647891c 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Event.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Event.kt @@ -5,9 +5,9 @@ import android.net.Uri internal interface MessageEvent internal data class ImageProcessorCompletedEvent( - val result: Uri, + val result: Uri, ) : MessageEvent internal data class ImageProcessorFailedEvent( - val exception: Throwable, + val exception: Throwable, ) : MessageEvent diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Exception.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Exception.kt index b58a0d3b..56023aec 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Exception.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Exception.kt @@ -1,6 +1,6 @@ package com.nkming.nc_photos.np_platform_image_processor internal class HttpException(statusCode: Int, message: String) : - Exception(message) + Exception(message) internal class NativeException(message: String) : Exception(message) diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorChannelHandler.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorChannelHandler.kt index 43f9bd9b..83c82354 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorChannelHandler.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorChannelHandler.kt @@ -21,386 +21,282 @@ import io.flutter.plugin.common.MethodChannel import java.io.Serializable internal class ImageProcessorChannelHandler(context: Context) : - MethodChannel.MethodCallHandler, EventChannel.StreamHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/image_processor_method" + MethodChannel.MethodCallHandler, EventChannel.StreamHandler { + companion object { + const val METHOD_CHANNEL = "${K.LIB_ID}/image_processor_method" - private const val TAG = "ImageProcessorChannelHandler" - } + private const val TAG = "ImageProcessorChannelHandler" + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "zeroDce" -> { - try { - zeroDce( - call.argument("fileUrl")!!, - call.argument("headers"), - call.argument("filename")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("isSaveToServer")!!, - call.argument("iteration")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "zeroDce" -> { + try { + zeroDce( + call.argument("fileUrl")!!, call.argument("headers"), + call.argument("filename")!!, + call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, + call.argument("iteration")!!, result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - "deepLab3Portrait" -> { - try { - deepLab3Portrait( - call.argument("fileUrl")!!, - call.argument("headers"), - call.argument("filename")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("isSaveToServer")!!, - call.argument("radius")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + "deepLab3Portrait" -> { + try { + deepLab3Portrait( + call.argument("fileUrl")!!, call.argument("headers"), + call.argument("filename")!!, + call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, + call.argument("radius")!!, result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - "esrgan" -> { - try { - esrgan( - call.argument("fileUrl")!!, - call.argument("headers"), - call.argument("filename")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("isSaveToServer")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + "esrgan" -> { + try { + esrgan( + call.argument("fileUrl")!!, call.argument("headers"), + call.argument("filename")!!, + call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - "arbitraryStyleTransfer" -> { - try { - arbitraryStyleTransfer( - call.argument("fileUrl")!!, - call.argument("headers"), - call.argument("filename")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("isSaveToServer")!!, - call.argument("styleUri")!!, - call.argument("weight")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + "arbitraryStyleTransfer" -> { + try { + arbitraryStyleTransfer( + call.argument("fileUrl")!!, call.argument("headers"), + call.argument("filename")!!, + call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, + call.argument("styleUri")!!, call.argument("weight")!!, + result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - "deepLab3ColorPop" -> { - try { - deepLab3ColorPop( - call.argument("fileUrl")!!, - call.argument("headers"), - call.argument("filename")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("isSaveToServer")!!, - call.argument("weight")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + "deepLab3ColorPop" -> { + try { + deepLab3ColorPop( + call.argument("fileUrl")!!, call.argument("headers"), + call.argument("filename")!!, + call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, + call.argument("weight")!!, result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - "neurOp" -> { - try { - neurOp( - call.argument("fileUrl")!!, - call.argument("headers"), - call.argument("filename")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("isSaveToServer")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + "neurOp" -> { + try { + neurOp( + call.argument("fileUrl")!!, call.argument("headers"), + call.argument("filename")!!, + call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - "filter" -> { - try { - filter( - call.argument("fileUrl")!!, - call.argument("headers"), - call.argument("filename")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("isSaveToServer")!!, - call.argument("filters")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + "filter" -> { + try { + filter( + call.argument("fileUrl")!!, call.argument("headers"), + call.argument("filename")!!, + call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, + call.argument("filters")!!, result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - "filterPreview" -> { - try { - filterPreview( - call.argument("rgba8")!!, - call.argument("filters")!!, - result - ) - } catch (e: Throwable) { - logE(TAG, "Uncaught exception", e) - result.error("systemException", e.toString(), null) - } - } + "filterPreview" -> { + try { + filterPreview( + call.argument("rgba8")!!, call.argument("filters")!!, + result + ) + } catch (e: Throwable) { + logE(TAG, "Uncaught exception", e) + result.error("systemException", e.toString(), null) + } + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - override fun onListen(arguments: Any?, events: EventChannel.EventSink) { - eventSink = events - } + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + eventSink = events + } - override fun onCancel(arguments: Any?) { - eventSink = null - } + override fun onCancel(arguments: Any?) { + eventSink = null + } - private fun zeroDce( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - iteration: Int, - result: MethodChannel.Result - ) = method(fileUrl, - headers, - filename, - maxWidth, - maxHeight, - isSaveToServer, - ImageProcessorService.METHOD_ZERO_DCE, - result, - onIntent = { - it.putExtra(ImageProcessorService.EXTRA_ITERATION, iteration) - }) + private fun zeroDce( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, iteration: Int, + result: MethodChannel.Result + ) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, + ImageProcessorService.METHOD_ZERO_DCE, result, onIntent = { + it.putExtra(ImageProcessorService.EXTRA_ITERATION, iteration) + }) - private fun deepLab3Portrait( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - radius: Int, - result: MethodChannel.Result - ) = method(fileUrl, - headers, - filename, - maxWidth, - maxHeight, - isSaveToServer, - ImageProcessorService.METHOD_DEEP_LAP_PORTRAIT, - result, - onIntent = { - it.putExtra(ImageProcessorService.EXTRA_RADIUS, radius) - }) + private fun deepLab3Portrait( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, radius: Int, + result: MethodChannel.Result + ) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, + ImageProcessorService.METHOD_DEEP_LAP_PORTRAIT, result, onIntent = { + it.putExtra(ImageProcessorService.EXTRA_RADIUS, radius) + }) - private fun esrgan( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - result: MethodChannel.Result - ) = method( - fileUrl, - headers, - filename, - maxWidth, - maxHeight, - isSaveToServer, - ImageProcessorService.METHOD_ESRGAN, - result - ) + private fun esrgan( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, + result: MethodChannel.Result + ) = method( + fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, + ImageProcessorService.METHOD_ESRGAN, result + ) - private fun arbitraryStyleTransfer( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - styleUri: String, - weight: Float, - result: MethodChannel.Result - ) = method(fileUrl, - headers, - filename, - maxWidth, - maxHeight, - isSaveToServer, - ImageProcessorService.METHOD_ARBITRARY_STYLE_TRANSFER, - result, - onIntent = { - it.putExtra( - ImageProcessorService.EXTRA_STYLE_URI, Uri.parse(styleUri) - ) - it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight) - }) + private fun arbitraryStyleTransfer( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, + styleUri: String, weight: Float, result: MethodChannel.Result + ) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, + ImageProcessorService.METHOD_ARBITRARY_STYLE_TRANSFER, result, + onIntent = { + it.putExtra( + ImageProcessorService.EXTRA_STYLE_URI, Uri.parse(styleUri) + ) + it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight) + }) - private fun deepLab3ColorPop( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - weight: Float, - result: MethodChannel.Result - ) = method(fileUrl, - headers, - filename, - maxWidth, - maxHeight, - isSaveToServer, - ImageProcessorService.METHOD_DEEP_LAP_COLOR_POP, - result, - onIntent = { - it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight) - }) + private fun deepLab3ColorPop( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, weight: Float, + result: MethodChannel.Result + ) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, + ImageProcessorService.METHOD_DEEP_LAP_COLOR_POP, result, onIntent = { + it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight) + }) - private fun neurOp( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - result: MethodChannel.Result - ) = method( - fileUrl, - headers, - filename, - maxWidth, - maxHeight, - isSaveToServer, - ImageProcessorService.METHOD_NEUR_OP, - result - ) + private fun neurOp( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, + result: MethodChannel.Result + ) = method( + fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, + ImageProcessorService.METHOD_NEUR_OP, result + ) - private fun filter( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - filters: List>, - result: MethodChannel.Result - ) { - // convert to serializable - val l = arrayListOf() - filters.mapTo(l, { HashMap(it) }) - method(fileUrl, - headers, - filename, - maxWidth, - maxHeight, - isSaveToServer, - ImageProcessorService.METHOD_FILTER, - result, - onIntent = { - it.putExtra(ImageProcessorService.EXTRA_FILTERS, l) - }) - } + private fun filter( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, + filters: List>, result: MethodChannel.Result + ) { + // convert to serializable + val l = arrayListOf() + filters.mapTo(l, { HashMap(it) }) + method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, + ImageProcessorService.METHOD_FILTER, result, onIntent = { + it.putExtra(ImageProcessorService.EXTRA_FILTERS, l) + }) + } - private fun filterPreview( - rgba8: Map, - filters: List>, - result: MethodChannel.Result - ) { - var img = Rgba8Image.fromJson(rgba8) - for (f in filters.map(ImageFilter::fromJson)) { - img = f.apply(img) - } - result.success(img.toJson()) - } + private fun filterPreview( + rgba8: Map, filters: List>, + result: MethodChannel.Result + ) { + var img = Rgba8Image.fromJson(rgba8) + for (f in filters.map(ImageFilter::fromJson)) { + img = f.apply(img) + } + result.success(img.toJson()) + } - private fun method( - fileUrl: String, - headers: Map?, - filename: String, - maxWidth: Int, - maxHeight: Int, - isSaveToServer: Boolean, - method: String, - result: MethodChannel.Result, - onIntent: ((Intent) -> Unit)? = null - ) { - val intent = Intent(context, ImageProcessorService::class.java).apply { - putExtra(ImageProcessorService.EXTRA_METHOD, method) - putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl) - putExtra(ImageProcessorService.EXTRA_HEADERS, - headers?.let { HashMap(it) }) - putExtra(ImageProcessorService.EXTRA_FILENAME, filename) - putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth) - putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight) - putExtra( - ImageProcessorService.EXTRA_IS_SAVE_TO_SERVER, isSaveToServer - ) - onIntent?.invoke(this) - } - ContextCompat.startForegroundService(context, intent) - result.success(null) - } + private fun method( + fileUrl: String, headers: Map?, filename: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, method: String, + result: MethodChannel.Result, onIntent: ((Intent) -> Unit)? = null + ) { + val intent = Intent(context, ImageProcessorService::class.java).apply { + putExtra(ImageProcessorService.EXTRA_METHOD, method) + putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl) + putExtra(ImageProcessorService.EXTRA_HEADERS, + headers?.let { HashMap(it) }) + putExtra(ImageProcessorService.EXTRA_FILENAME, filename) + putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth) + putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight) + putExtra( + ImageProcessorService.EXTRA_IS_SAVE_TO_SERVER, isSaveToServer + ) + onIntent?.invoke(this) + } + ContextCompat.startForegroundService(context, intent) + result.success(null) + } - private val context = context - private var eventSink: EventChannel.EventSink? = null + private val context = context + private var eventSink: EventChannel.EventSink? = null } internal interface ImageFilter { - companion object { - fun fromJson(json: Map): ImageFilter { - return when (json["type"]) { - "brightness" -> Brightness((json["weight"] as Double).toFloat()) - "contrast" -> Contrast((json["weight"] as Double).toFloat()) - "whitePoint" -> WhitePoint((json["weight"] as Double).toFloat()) - "blackPoint" -> BlackPoint((json["weight"] as Double).toFloat()) - "saturation" -> Saturation((json["weight"] as Double).toFloat()) - "warmth" -> Warmth((json["weight"] as Double).toFloat()) - "tint" -> Tint((json["weight"] as Double).toFloat()) - "orientation" -> Orientation(json["degree"] as Int) - "crop" -> Crop( - json["top"] as Double, - json["left"] as Double, - json["bottom"] as Double, - json["right"] as Double - ) + companion object { + fun fromJson(json: Map): ImageFilter { + return when (json["type"]) { + "brightness" -> Brightness((json["weight"] as Double).toFloat()) + "contrast" -> Contrast((json["weight"] as Double).toFloat()) + "whitePoint" -> WhitePoint((json["weight"] as Double).toFloat()) + "blackPoint" -> BlackPoint((json["weight"] as Double).toFloat()) + "saturation" -> Saturation((json["weight"] as Double).toFloat()) + "warmth" -> Warmth((json["weight"] as Double).toFloat()) + "tint" -> Tint((json["weight"] as Double).toFloat()) + "orientation" -> Orientation(json["degree"] as Int) + "crop" -> Crop( + json["top"] as Double, json["left"] as Double, + json["bottom"] as Double, json["right"] as Double + ) - else -> throw IllegalArgumentException( - "Unknown type: ${json["type"]}" - ) - } - } - } + else -> throw IllegalArgumentException( + "Unknown type: ${json["type"]}" + ) + } + } + } - fun apply(rgba8: Rgba8Image): Rgba8Image + fun apply(rgba8: Rgba8Image): Rgba8Image } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorService.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorService.kt index 69bc4af8..f2f7b6ed 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorService.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorService.kt @@ -26,958 +26,958 @@ import java.net.HttpURLConnection import java.net.URL internal class ImageProcessorService : Service() { - companion object { - const val EXTRA_METHOD = "method" - const val METHOD_ZERO_DCE = "zero-dce" - const val METHOD_DEEP_LAP_PORTRAIT = "DeepLab3Portrait" - const val METHOD_ESRGAN = "Esrgan" - const val METHOD_ARBITRARY_STYLE_TRANSFER = "ArbitraryStyleTransfer" - const val METHOD_DEEP_LAP_COLOR_POP = "DeepLab3ColorPop" - const val METHOD_NEUR_OP = "NeurOp" - const val METHOD_FILTER = "Filter" - const val EXTRA_FILE_URL = "fileUrl" - const val EXTRA_HEADERS = "headers" - const val EXTRA_FILENAME = "filename" - const val EXTRA_MAX_WIDTH = "maxWidth" - const val EXTRA_MAX_HEIGHT = "maxHeight" - const val EXTRA_IS_SAVE_TO_SERVER = "isSaveToServer" - const val EXTRA_RADIUS = "radius" - const val EXTRA_ITERATION = "iteration" - const val EXTRA_STYLE_URI = "styleUri" - const val EXTRA_WEIGHT = "weight" - const val EXTRA_FILTERS = "filters" + companion object { + const val EXTRA_METHOD = "method" + const val METHOD_ZERO_DCE = "zero-dce" + const val METHOD_DEEP_LAP_PORTRAIT = "DeepLab3Portrait" + const val METHOD_ESRGAN = "Esrgan" + const val METHOD_ARBITRARY_STYLE_TRANSFER = "ArbitraryStyleTransfer" + const val METHOD_DEEP_LAP_COLOR_POP = "DeepLab3ColorPop" + const val METHOD_NEUR_OP = "NeurOp" + const val METHOD_FILTER = "Filter" + const val EXTRA_FILE_URL = "fileUrl" + const val EXTRA_HEADERS = "headers" + const val EXTRA_FILENAME = "filename" + const val EXTRA_MAX_WIDTH = "maxWidth" + const val EXTRA_MAX_HEIGHT = "maxHeight" + const val EXTRA_IS_SAVE_TO_SERVER = "isSaveToServer" + const val EXTRA_RADIUS = "radius" + const val EXTRA_ITERATION = "iteration" + const val EXTRA_STYLE_URI = "styleUri" + const val EXTRA_WEIGHT = "weight" + const val EXTRA_FILTERS = "filters" - private const val ACTION_CANCEL = "cancel" + private const val ACTION_CANCEL = "cancel" - private const val NOTIFICATION_ID = - K.IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID - private const val RESULT_NOTIFICATION_ID = - K.IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID - private const val RESULT_FAILED_NOTIFICATION_ID = - K.IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID - private const val CHANNEL_ID = "ImageProcessorService" + private const val NOTIFICATION_ID = + K.IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID + private const val RESULT_NOTIFICATION_ID = + K.IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID + private const val RESULT_FAILED_NOTIFICATION_ID = + K.IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID + private const val CHANNEL_ID = "ImageProcessorService" - const val TAG = "ImageProcessorService" - } + const val TAG = "ImageProcessorService" + } - override fun onBind(intent: Intent?): IBinder? = null + override fun onBind(intent: Intent?): IBinder? = null - @SuppressLint("WakelockTimeout") - override fun onCreate() { - logI(TAG, "[onCreate] Service created") - super.onCreate() - wakeLock.acquire() - createNotificationChannel() - cleanUp() - } + @SuppressLint("WakelockTimeout") + override fun onCreate() { + logI(TAG, "[onCreate] Service created") + super.onCreate() + wakeLock.acquire() + createNotificationChannel() + cleanUp() + } - override fun onDestroy() { - logI(TAG, "[onDestroy] Service destroyed") - wakeLock.release() - super.onDestroy() - } + override fun onDestroy() { + logI(TAG, "[onDestroy] Service destroyed") + wakeLock.release() + super.onDestroy() + } - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - if (!isForeground) { - try { - startForeground(NOTIFICATION_ID, buildNotification()) - isForeground = true - } catch (e: Throwable) { - // ??? - logE(TAG, "[onStartCommand] Failed while startForeground", e) - } - } + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + if (!isForeground) { + try { + startForeground(NOTIFICATION_ID, buildNotification()) + isForeground = true + } catch (e: Throwable) { + // ??? + logE(TAG, "[onStartCommand] Failed while startForeground", e) + } + } - if ((flags and START_FLAG_REDELIVERY) != 0) { - logW(TAG, "[onStartCommand] Redelivered intent, service crashed?") - // add a short grace period to let user cancel the queue - addCommand(ImageProcessorGracePeriodCommand()) - } + if ((flags and START_FLAG_REDELIVERY) != 0) { + logW(TAG, "[onStartCommand] Redelivered intent, service crashed?") + // add a short grace period to let user cancel the queue + addCommand(ImageProcessorGracePeriodCommand()) + } - when (intent.action) { - ACTION_CANCEL -> onCancel(startId) - else -> onNewImage(intent, startId) - } - return START_REDELIVER_INTENT - } + when (intent.action) { + ACTION_CANCEL -> onCancel(startId) + else -> onNewImage(intent, startId) + } + return START_REDELIVER_INTENT + } - private fun onCancel(startId: Int) { - logI(TAG, "[onCancel] Cancel requested") - cmdTask?.cancel(false) - stopSelf(startId) - } + private fun onCancel(startId: Int) { + logI(TAG, "[onCancel] Cancel requested") + cmdTask?.cancel(false) + stopSelf(startId) + } - private fun onNewImage(intent: Intent, startId: Int) { - assert(intent.hasExtra(EXTRA_METHOD)) - assert(intent.hasExtra(EXTRA_FILE_URL)) + private fun onNewImage(intent: Intent, startId: Int) { + assert(intent.hasExtra(EXTRA_METHOD)) + assert(intent.hasExtra(EXTRA_FILE_URL)) - val method = intent.getStringExtra(EXTRA_METHOD) - when (method) { - METHOD_ZERO_DCE -> onZeroDce(startId, intent.extras!!) - METHOD_DEEP_LAP_PORTRAIT -> onDeepLapPortrait( - startId, intent.extras!! - ) + val method = intent.getStringExtra(EXTRA_METHOD) + when (method) { + METHOD_ZERO_DCE -> onZeroDce(startId, intent.extras!!) + METHOD_DEEP_LAP_PORTRAIT -> onDeepLapPortrait( + startId, intent.extras!! + ) - METHOD_ESRGAN -> onEsrgan(startId, intent.extras!!) - METHOD_ARBITRARY_STYLE_TRANSFER -> onArbitraryStyleTransfer( - startId, intent.extras!! - ) + METHOD_ESRGAN -> onEsrgan(startId, intent.extras!!) + METHOD_ARBITRARY_STYLE_TRANSFER -> onArbitraryStyleTransfer( + startId, intent.extras!! + ) - METHOD_DEEP_LAP_COLOR_POP -> onDeepLapColorPop( - startId, intent.extras!! - ) + METHOD_DEEP_LAP_COLOR_POP -> onDeepLapColorPop( + startId, intent.extras!! + ) - METHOD_NEUR_OP -> onNeurOp(startId, intent.extras!!) - METHOD_FILTER -> onFilter(startId, intent.extras!!) - else -> { - logE(TAG, "Unknown method: $method") - // we can't call stopSelf here as it'll stop the service even if - // there are commands running in the bg - addCommand( - ImageProcessorDummyCommand( - ImageProcessorImageCommand.Params( - startId, "null", null, "", 0, 0, false - ) - ) - ) - } - } - } + METHOD_NEUR_OP -> onNeurOp(startId, intent.extras!!) + METHOD_FILTER -> onFilter(startId, intent.extras!!) + else -> { + logE(TAG, "Unknown method: $method") + // we can't call stopSelf here as it'll stop the service even if + // there are commands running in the bg + addCommand( + ImageProcessorDummyCommand( + ImageProcessorImageCommand.Params( + startId, "null", null, "", 0, 0, false + ) + ) + ) + } + } + } - private fun onZeroDce(startId: Int, extras: Bundle) { - return onMethod(startId, extras, { params -> - ImageProcessorZeroDceCommand( - params, extras.getIntOrNull(EXTRA_ITERATION) - ) - }) - } + private fun onZeroDce(startId: Int, extras: Bundle) { + return onMethod(startId, extras, { params -> + ImageProcessorZeroDceCommand( + params, extras.getIntOrNull(EXTRA_ITERATION) + ) + }) + } - private fun onDeepLapPortrait(startId: Int, extras: Bundle) { - return onMethod(startId, extras, { params -> - ImageProcessorDeepLapPortraitCommand( - params, extras.getIntOrNull(EXTRA_RADIUS) - ) - }) - } + private fun onDeepLapPortrait(startId: Int, extras: Bundle) { + return onMethod(startId, extras, { params -> + ImageProcessorDeepLapPortraitCommand( + params, extras.getIntOrNull(EXTRA_RADIUS) + ) + }) + } - private fun onEsrgan(startId: Int, extras: Bundle) { - return onMethod(startId, extras, - { params -> ImageProcessorEsrganCommand(params) }) - } + private fun onEsrgan(startId: Int, extras: Bundle) { + return onMethod( + startId, extras, { params -> ImageProcessorEsrganCommand(params) }) + } - private fun onArbitraryStyleTransfer(startId: Int, extras: Bundle) { - return onMethod(startId, extras, { params -> - ImageProcessorArbitraryStyleTransferCommand( - params, extras.getParcelable(EXTRA_STYLE_URI)!!, - extras.getFloat(EXTRA_WEIGHT) - ) - }) - } + private fun onArbitraryStyleTransfer(startId: Int, extras: Bundle) { + return onMethod(startId, extras, { params -> + ImageProcessorArbitraryStyleTransferCommand( + params, extras.getParcelable(EXTRA_STYLE_URI)!!, + extras.getFloat(EXTRA_WEIGHT) + ) + }) + } - private fun onDeepLapColorPop(startId: Int, extras: Bundle) { - return onMethod( - startId, extras, - { params -> - ImageProcessorDeepLapColorPopCommand( - params, extras.getFloat(EXTRA_WEIGHT) - ) - }, - ) - } + private fun onDeepLapColorPop(startId: Int, extras: Bundle) { + return onMethod( + startId, extras, + { params -> + ImageProcessorDeepLapColorPopCommand( + params, extras.getFloat(EXTRA_WEIGHT) + ) + }, + ) + } - private fun onNeurOp(startId: Int, extras: Bundle) { - return onMethod( - startId, extras, { params -> ImageProcessorNeurOpCommand(params) }, - ) - } + private fun onNeurOp(startId: Int, extras: Bundle) { + return onMethod( + startId, extras, { params -> ImageProcessorNeurOpCommand(params) }, + ) + } - private fun onFilter(startId: Int, extras: Bundle) { - val filters = extras.getSerializable(EXTRA_FILTERS)!! - .asType>() - .map { ImageFilter.fromJson(it.asType>()) } + private fun onFilter(startId: Int, extras: Bundle) { + val filters = extras.getSerializable(EXTRA_FILTERS)!! + .asType>() + .map { ImageFilter.fromJson(it.asType>()) } - val fileUrl = extras.getString(EXTRA_FILE_URL)!! + val fileUrl = extras.getString(EXTRA_FILE_URL)!! - @Suppress("Unchecked_cast") val headers = - extras.getSerializable(EXTRA_HEADERS) as HashMap? - val filename = extras.getString(EXTRA_FILENAME)!! - val maxWidth = extras.getInt(EXTRA_MAX_WIDTH) - val maxHeight = extras.getInt(EXTRA_MAX_HEIGHT) - val isSaveToServer = extras.getBoolean(EXTRA_IS_SAVE_TO_SERVER) - addCommand( - ImageProcessorFilterCommand( - ImageProcessorImageCommand.Params( - startId, fileUrl, headers, filename, maxWidth, maxHeight, - isSaveToServer - ), filters - ) - ) - } + @Suppress("Unchecked_cast") val headers = + extras.getSerializable(EXTRA_HEADERS) as HashMap? + val filename = extras.getString(EXTRA_FILENAME)!! + val maxWidth = extras.getInt(EXTRA_MAX_WIDTH) + val maxHeight = extras.getInt(EXTRA_MAX_HEIGHT) + val isSaveToServer = extras.getBoolean(EXTRA_IS_SAVE_TO_SERVER) + addCommand( + ImageProcessorFilterCommand( + ImageProcessorImageCommand.Params( + startId, fileUrl, headers, filename, maxWidth, maxHeight, + isSaveToServer + ), filters + ) + ) + } - /** - * Handle methods without arguments - * - * @param startId - * @param extras - * @param builder Build the command - */ - private fun onMethod( - startId: Int, extras: Bundle, - builder: (ImageProcessorImageCommand.Params) -> ImageProcessorImageCommand - ) { - val fileUrl = extras.getString(EXTRA_FILE_URL)!! + /** + * Handle methods without arguments + * + * @param startId + * @param extras + * @param builder Build the command + */ + private fun onMethod( + startId: Int, extras: Bundle, + builder: (ImageProcessorImageCommand.Params) -> ImageProcessorImageCommand + ) { + val fileUrl = extras.getString(EXTRA_FILE_URL)!! - @Suppress("Unchecked_cast") val headers = - extras.getSerializable(EXTRA_HEADERS) as HashMap? - val filename = extras.getString(EXTRA_FILENAME)!! - val maxWidth = extras.getInt(EXTRA_MAX_WIDTH) - val maxHeight = extras.getInt(EXTRA_MAX_HEIGHT) - val isSaveToServer = extras.getBoolean(EXTRA_IS_SAVE_TO_SERVER) - addCommand( - builder( - ImageProcessorImageCommand.Params( - startId, fileUrl, headers, filename, maxWidth, maxHeight, - isSaveToServer - ) - ) - ) - } + @Suppress("Unchecked_cast") val headers = + extras.getSerializable(EXTRA_HEADERS) as HashMap? + val filename = extras.getString(EXTRA_FILENAME)!! + val maxWidth = extras.getInt(EXTRA_MAX_WIDTH) + val maxHeight = extras.getInt(EXTRA_MAX_HEIGHT) + val isSaveToServer = extras.getBoolean(EXTRA_IS_SAVE_TO_SERVER) + addCommand( + builder( + ImageProcessorImageCommand.Params( + startId, fileUrl, headers, filename, maxWidth, maxHeight, + isSaveToServer + ) + ) + ) + } - private fun createNotificationChannel() { - val channel = NotificationChannelCompat.Builder( - CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW - ).run { - setName("Image processing") - setDescription("Enhance images in the background") - build() - } - notificationManager.createNotificationChannel(channel) - } + private fun createNotificationChannel() { + val channel = NotificationChannelCompat.Builder( + CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW + ).run { + setName("Image processing") + setDescription("Enhance images in the background") + build() + } + notificationManager.createNotificationChannel(channel) + } - private fun buildNotification(content: String? = null): Notification { - val cancelIntent = - Intent(this, ImageProcessorService::class.java).apply { - action = ACTION_CANCEL - } - val cancelPendingIntent = PendingIntent.getService( - this, 0, cancelIntent, getPendingIntentFlagImmutable() - ) - return NotificationCompat.Builder(this, CHANNEL_ID).run { - setSmallIcon(R.drawable.outline_auto_fix_high_white_24) - setContentTitle("Processing image") - if (content != null) setContentText(content) - addAction( - 0, getString(android.R.string.cancel), cancelPendingIntent - ) - build() - } - } + private fun buildNotification(content: String? = null): Notification { + val cancelIntent = + Intent(this, ImageProcessorService::class.java).apply { + action = ACTION_CANCEL + } + val cancelPendingIntent = PendingIntent.getService( + this, 0, cancelIntent, getPendingIntentFlagImmutable() + ) + return NotificationCompat.Builder(this, CHANNEL_ID).run { + setSmallIcon(R.drawable.outline_auto_fix_high_white_24) + setContentTitle("Processing image") + if (content != null) setContentText(content) + addAction( + 0, getString(android.R.string.cancel), cancelPendingIntent + ) + build() + } + } - private fun buildResultNotification(result: Uri): Notification { - val intent = Intent().apply { - `package` = packageName - component = ComponentName( - packageName, "com.nkming.nc_photos.MainActivity" - ) - action = K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT - putExtra(K.EXTRA_IMAGE_RESULT_URI, result) - } - val pi = PendingIntent.getActivity( - this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() - ) - return NotificationCompat.Builder(this, CHANNEL_ID).run { - setSmallIcon(R.drawable.outline_image_white_24) - setContentTitle("Successfully processed image") - setContentText("Tap to view the result") - setContentIntent(pi) - setAutoCancel(true) - build() - } - } + private fun buildResultNotification(result: Uri): Notification { + val intent = Intent().apply { + `package` = packageName + component = ComponentName( + packageName, "com.nkming.nc_photos.MainActivity" + ) + action = K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT + putExtra(K.EXTRA_IMAGE_RESULT_URI, result) + } + val pi = PendingIntent.getActivity( + this, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() + ) + return NotificationCompat.Builder(this, CHANNEL_ID).run { + setSmallIcon(R.drawable.outline_image_white_24) + setContentTitle("Successfully processed image") + setContentText("Tap to view the result") + setContentIntent(pi) + setAutoCancel(true) + build() + } + } - private fun buildResultFailedNotification( - exception: Throwable - ): Notification { - return NotificationCompat.Builder(this, CHANNEL_ID).run { - setSmallIcon(R.drawable.outline_error_outline_white_24) - setContentTitle("Failed processing image") - setContentText(exception.message) - build() - } - } + private fun buildResultFailedNotification( + exception: Throwable + ): Notification { + return NotificationCompat.Builder(this, CHANNEL_ID).run { + setSmallIcon(R.drawable.outline_error_outline_white_24) + setContentTitle("Failed processing image") + setContentText(exception.message) + build() + } + } - private fun buildGracePeriodNotification(): Notification { - val cancelIntent = - Intent(this, ImageProcessorService::class.java).apply { - action = ACTION_CANCEL - } - val cancelPendingIntent = PendingIntent.getService( - this, 0, cancelIntent, getPendingIntentFlagImmutable() - ) - return NotificationCompat.Builder(this, CHANNEL_ID).run { - setSmallIcon(R.drawable.outline_auto_fix_high_white_24) - setContentTitle("Preparing to restart photo processing") - addAction( - 0, getString(android.R.string.cancel), cancelPendingIntent - ) - build() - } - } + private fun buildGracePeriodNotification(): Notification { + val cancelIntent = + Intent(this, ImageProcessorService::class.java).apply { + action = ACTION_CANCEL + } + val cancelPendingIntent = PendingIntent.getService( + this, 0, cancelIntent, getPendingIntentFlagImmutable() + ) + return NotificationCompat.Builder(this, CHANNEL_ID).run { + setSmallIcon(R.drawable.outline_auto_fix_high_white_24) + setContentTitle("Preparing to restart photo processing") + addAction( + 0, getString(android.R.string.cancel), cancelPendingIntent + ) + build() + } + } - private fun addCommand(cmd: ImageProcessorCommand) { - cmds.add(cmd) - if (cmdTask == null) { - runCommand() - } - } + private fun addCommand(cmd: ImageProcessorCommand) { + cmds.add(cmd) + if (cmdTask == null) { + runCommand() + } + } - private fun runCommand() { - val cmd = cmds.first() - if (cmd is ImageProcessorImageCommand) { - runCommand(cmd) - } else if (cmd is ImageProcessorGracePeriodCommand) { - runCommand(cmd) - } - } + private fun runCommand() { + val cmd = cmds.first() + if (cmd is ImageProcessorImageCommand) { + runCommand(cmd) + } else if (cmd is ImageProcessorGracePeriodCommand) { + runCommand(cmd) + } + } - @SuppressLint("StaticFieldLeak") - private fun runCommand(cmd: ImageProcessorImageCommand) { - notificationManager.notify( - NOTIFICATION_ID, buildNotification(cmd.filename) - ) - cmdTask = object : ImageProcessorCommandTask(applicationContext) { - override fun onPostExecute(result: MessageEvent) { - notifyResult(result, cmd.isSaveToServer) - cmds.removeFirst() - stopSelf(cmd.startId) - cmdTask = null - @Suppress( - "Deprecation" - ) if (cmds.isNotEmpty() && !isCancelled) { - runCommand() - } - } - }.apply { - @Suppress("Deprecation") executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR, cmd - ) - } - } + @SuppressLint("StaticFieldLeak") + private fun runCommand(cmd: ImageProcessorImageCommand) { + notificationManager.notify( + NOTIFICATION_ID, buildNotification(cmd.filename) + ) + cmdTask = object : ImageProcessorCommandTask(applicationContext) { + override fun onPostExecute(result: MessageEvent) { + notifyResult(result, cmd.isSaveToServer) + cmds.removeFirst() + stopSelf(cmd.startId) + cmdTask = null + @Suppress( + "Deprecation" + ) if (cmds.isNotEmpty() && !isCancelled) { + runCommand() + } + } + }.apply { + @Suppress("Deprecation") executeOnExecutor( + AsyncTask.THREAD_POOL_EXECUTOR, cmd + ) + } + } - @SuppressLint("StaticFieldLeak") - private fun runCommand( - @Suppress("UNUSED_PARAMETER") cmd: ImageProcessorGracePeriodCommand - ) { - notificationManager.notify( - NOTIFICATION_ID, buildGracePeriodNotification() - ) - @Suppress("Deprecation") cmdTask = - object : AsyncTask(), AsyncTaskCancellable { - override fun doInBackground(vararg params: Unit?) { - // 10 seconds - for (i in 0 until 20) { - if (isCancelled) { - return - } - Thread.sleep(500) - } - } + @SuppressLint("StaticFieldLeak") + private fun runCommand( + @Suppress("UNUSED_PARAMETER") cmd: ImageProcessorGracePeriodCommand + ) { + notificationManager.notify( + NOTIFICATION_ID, buildGracePeriodNotification() + ) + @Suppress("Deprecation") cmdTask = + object : AsyncTask(), AsyncTaskCancellable { + override fun doInBackground(vararg params: Unit?) { + // 10 seconds + for (i in 0 until 20) { + if (isCancelled) { + return + } + Thread.sleep(500) + } + } - override fun onPostExecute(result: Unit?) { - cmdTask = null - cmds.removeFirst() - if (cmds.isNotEmpty() && !isCancelled) { - runCommand() - } - } - }.apply { - executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) - } - } + override fun onPostExecute(result: Unit?) { + cmdTask = null + cmds.removeFirst() + if (cmds.isNotEmpty() && !isCancelled) { + runCommand() + } + } + }.apply { + executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + } - private fun notifyResult(event: MessageEvent, shouldFireEvent: Boolean) { - if (event is ImageProcessorCompletedEvent) { - if (shouldFireEvent) { - // NativeEventChannelHandler.fire( - // ImageProcessorUploadSuccessEvent() - // ) - } - notificationManager.notify( - RESULT_NOTIFICATION_ID, buildResultNotification(event.result) - ) - } else if (event is ImageProcessorFailedEvent) { - notificationManager.notify( - RESULT_FAILED_NOTIFICATION_ID, - buildResultFailedNotification(event.exception) - ) - } - } + private fun notifyResult(event: MessageEvent, shouldFireEvent: Boolean) { + if (event is ImageProcessorCompletedEvent) { + if (shouldFireEvent) { + // NativeEventChannelHandler.fire( + // ImageProcessorUploadSuccessEvent() + // ) + } + notificationManager.notify( + RESULT_NOTIFICATION_ID, buildResultNotification(event.result) + ) + } else if (event is ImageProcessorFailedEvent) { + notificationManager.notify( + RESULT_FAILED_NOTIFICATION_ID, + buildResultFailedNotification(event.exception) + ) + } + } - /** - * Clean up temp files in case the service ended prematurely last time - */ - private fun cleanUp() { - try { - getTempDir(this).deleteRecursively() - } catch (e: Throwable) { - logE(TAG, "[cleanUp] Failed while cleanUp", e) - } - } + /** + * Clean up temp files in case the service ended prematurely last time + */ + private fun cleanUp() { + try { + getTempDir(this).deleteRecursively() + } catch (e: Throwable) { + logE(TAG, "[cleanUp] Failed while cleanUp", e) + } + } - private var isForeground = false - private val cmds = mutableListOf() - private var cmdTask: AsyncTaskCancellable? = null + private var isForeground = false + private val cmds = mutableListOf() + private var cmdTask: AsyncTaskCancellable? = null - private val notificationManager by lazy { - NotificationManagerCompat.from(this) - } - private val wakeLock: PowerManager.WakeLock by lazy { - (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, "nc-photos:ImageProcessorService" - ).apply { - setReferenceCounted(false) - } - } + private val notificationManager by lazy { + NotificationManagerCompat.from(this) + } + private val wakeLock: PowerManager.WakeLock by lazy { + (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "nc-photos:ImageProcessorService" + ).apply { + setReferenceCounted(false) + } + } } private interface ImageProcessorCommand private abstract class ImageProcessorImageCommand( - val params: Params, + val params: Params, ) : ImageProcessorCommand { - class Params( - val startId: Int, - val fileUrl: String, - val headers: Map?, - val filename: String, - val maxWidth: Int, - val maxHeight: Int, - val isSaveToServer: Boolean, - ) + class Params( + val startId: Int, + val fileUrl: String, + val headers: Map?, + val filename: String, + val maxWidth: Int, + val maxHeight: Int, + val isSaveToServer: Boolean, + ) - abstract fun apply(context: Context, fileUri: Uri): Bitmap - abstract fun isEnhanceCommand(): Boolean + abstract fun apply(context: Context, fileUri: Uri): Bitmap + abstract fun isEnhanceCommand(): Boolean - val startId: Int - get() = params.startId - val fileUrl: String - get() = params.fileUrl - val headers: Map? - get() = params.headers - val filename: String - get() = params.filename - val maxWidth: Int - get() = params.maxWidth - val maxHeight: Int - get() = params.maxHeight - val isSaveToServer: Boolean - get() = params.isSaveToServer + val startId: Int + get() = params.startId + val fileUrl: String + get() = params.fileUrl + val headers: Map? + get() = params.headers + val filename: String + get() = params.filename + val maxWidth: Int + get() = params.maxWidth + val maxHeight: Int + get() = params.maxHeight + val isSaveToServer: Boolean + get() = params.isSaveToServer } private class ImageProcessorDummyCommand( - params: Params, + params: Params, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - throw UnsupportedOperationException() - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + throw UnsupportedOperationException() + } - override fun isEnhanceCommand() = true + override fun isEnhanceCommand() = true } private class ImageProcessorZeroDceCommand( - params: Params, - val iteration: Int?, + params: Params, + val iteration: Int?, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return ZeroDce(context, maxWidth, maxHeight, iteration ?: 8).infer( - fileUri - ) - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + return ZeroDce(context, maxWidth, maxHeight, iteration ?: 8).infer( + fileUri + ) + } - override fun isEnhanceCommand() = true + override fun isEnhanceCommand() = true } private class ImageProcessorDeepLapPortraitCommand( - params: Params, - val radius: Int?, + params: Params, + val radius: Int?, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return DeepLab3Portrait( - context, maxWidth, maxHeight, radius ?: 16 - ).infer(fileUri) - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + return DeepLab3Portrait( + context, maxWidth, maxHeight, radius ?: 16 + ).infer(fileUri) + } - override fun isEnhanceCommand() = true + override fun isEnhanceCommand() = true } private class ImageProcessorEsrganCommand( - params: Params, + params: Params, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return Esrgan(context, maxWidth, maxHeight).infer(fileUri) - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + return Esrgan(context, maxWidth, maxHeight).infer(fileUri) + } - override fun isEnhanceCommand() = true + override fun isEnhanceCommand() = true } private class ImageProcessorArbitraryStyleTransferCommand( - params: Params, - val styleUri: Uri, - val weight: Float, + params: Params, + val styleUri: Uri, + val weight: Float, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return ArbitraryStyleTransfer( - context, maxWidth, maxHeight, styleUri, weight - ).infer(fileUri) - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + return ArbitraryStyleTransfer( + context, maxWidth, maxHeight, styleUri, weight + ).infer(fileUri) + } - override fun isEnhanceCommand() = true + override fun isEnhanceCommand() = true } private class ImageProcessorDeepLapColorPopCommand( - params: Params, - val weight: Float, + params: Params, + val weight: Float, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return DeepLab3ColorPop(context, maxWidth, maxHeight, weight).infer( - fileUri - ) - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + return DeepLab3ColorPop(context, maxWidth, maxHeight, weight).infer( + fileUri + ) + } - override fun isEnhanceCommand() = true + override fun isEnhanceCommand() = true } private class ImageProcessorNeurOpCommand( - params: Params, + params: Params, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return NeurOp(context, maxWidth, maxHeight).infer(fileUri) - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + return NeurOp(context, maxWidth, maxHeight).infer(fileUri) + } - override fun isEnhanceCommand() = true + override fun isEnhanceCommand() = true } private class ImageProcessorFilterCommand( - params: Params, - val filters: List, + params: Params, + val filters: List, ) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return ImageFilterProcessor( - context, maxWidth, maxHeight, filters - ).apply(fileUri) - } + override fun apply(context: Context, fileUri: Uri): Bitmap { + return ImageFilterProcessor( + context, maxWidth, maxHeight, filters + ).apply(fileUri) + } - override fun isEnhanceCommand() = false + override fun isEnhanceCommand() = false } private class ImageProcessorGracePeriodCommand : ImageProcessorCommand @Suppress("Deprecation") private open class ImageProcessorCommandTask(context: Context) : - AsyncTask(), - AsyncTaskCancellable { - companion object { - private val exifTagOfInterests = listOf( - ExifInterface.TAG_IMAGE_DESCRIPTION, - ExifInterface.TAG_MAKE, - ExifInterface.TAG_MODEL, - // while processing, we'll correct the orientation, if we copy the - // value to the resulting image, the orientation will be wrong + AsyncTask(), + AsyncTaskCancellable { + companion object { + private val exifTagOfInterests = listOf( + ExifInterface.TAG_IMAGE_DESCRIPTION, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + // while processing, we'll correct the orientation, if we copy the + // value to the resulting image, the orientation will be wrong // ExifInterface.TAG_ORIENTATION, - ExifInterface.TAG_X_RESOLUTION, - ExifInterface.TAG_Y_RESOLUTION, - ExifInterface.TAG_DATETIME, - ExifInterface.TAG_ARTIST, - ExifInterface.TAG_COPYRIGHT, - ExifInterface.TAG_EXPOSURE_TIME, - ExifInterface.TAG_F_NUMBER, - ExifInterface.TAG_EXPOSURE_PROGRAM, - ExifInterface.TAG_SPECTRAL_SENSITIVITY, - ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, - ExifInterface.TAG_OECF, - ExifInterface.TAG_SENSITIVITY_TYPE, - ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, - ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, - ExifInterface.TAG_ISO_SPEED, - ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, - ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, - ExifInterface.TAG_EXIF_VERSION, - ExifInterface.TAG_DATETIME_ORIGINAL, - ExifInterface.TAG_DATETIME_DIGITIZED, - ExifInterface.TAG_OFFSET_TIME, - ExifInterface.TAG_OFFSET_TIME_ORIGINAL, - ExifInterface.TAG_OFFSET_TIME_DIGITIZED, - ExifInterface.TAG_SHUTTER_SPEED_VALUE, - ExifInterface.TAG_APERTURE_VALUE, - ExifInterface.TAG_BRIGHTNESS_VALUE, - ExifInterface.TAG_EXPOSURE_BIAS_VALUE, - ExifInterface.TAG_MAX_APERTURE_VALUE, - ExifInterface.TAG_SUBJECT_DISTANCE, - ExifInterface.TAG_METERING_MODE, - ExifInterface.TAG_LIGHT_SOURCE, - ExifInterface.TAG_FLASH, - ExifInterface.TAG_FOCAL_LENGTH, - ExifInterface.TAG_SUBJECT_AREA, - ExifInterface.TAG_MAKER_NOTE, - ExifInterface.TAG_USER_COMMENT, - ExifInterface.TAG_SUBSEC_TIME, - ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, - ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, - ExifInterface.TAG_FLASHPIX_VERSION, - ExifInterface.TAG_FLASH_ENERGY, - ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, - ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, - ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, - ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, - ExifInterface.TAG_SUBJECT_LOCATION, - ExifInterface.TAG_EXPOSURE_INDEX, - ExifInterface.TAG_SENSING_METHOD, - ExifInterface.TAG_FILE_SOURCE, - ExifInterface.TAG_SCENE_TYPE, - ExifInterface.TAG_CFA_PATTERN, - ExifInterface.TAG_CUSTOM_RENDERED, - ExifInterface.TAG_EXPOSURE_MODE, - ExifInterface.TAG_WHITE_BALANCE, - ExifInterface.TAG_DIGITAL_ZOOM_RATIO, - ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, - ExifInterface.TAG_SCENE_CAPTURE_TYPE, - ExifInterface.TAG_GAIN_CONTROL, - ExifInterface.TAG_CONTRAST, - ExifInterface.TAG_SATURATION, - ExifInterface.TAG_SHARPNESS, - ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, - ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, - ExifInterface.TAG_IMAGE_UNIQUE_ID, - ExifInterface.TAG_CAMERA_OWNER_NAME, - ExifInterface.TAG_BODY_SERIAL_NUMBER, - ExifInterface.TAG_LENS_SPECIFICATION, - ExifInterface.TAG_LENS_MAKE, - ExifInterface.TAG_LENS_MODEL, - ExifInterface.TAG_GAMMA, - ExifInterface.TAG_GPS_VERSION_ID, - ExifInterface.TAG_GPS_LATITUDE_REF, - ExifInterface.TAG_GPS_LATITUDE, - ExifInterface.TAG_GPS_LONGITUDE_REF, - ExifInterface.TAG_GPS_LONGITUDE, - ExifInterface.TAG_GPS_ALTITUDE_REF, - ExifInterface.TAG_GPS_ALTITUDE, - ExifInterface.TAG_GPS_TIMESTAMP, - ExifInterface.TAG_GPS_SATELLITES, - ExifInterface.TAG_GPS_STATUS, - ExifInterface.TAG_GPS_MEASURE_MODE, - ExifInterface.TAG_GPS_DOP, - ExifInterface.TAG_GPS_SPEED_REF, - ExifInterface.TAG_GPS_SPEED, - ExifInterface.TAG_GPS_TRACK_REF, - ExifInterface.TAG_GPS_TRACK, - ExifInterface.TAG_GPS_IMG_DIRECTION_REF, - ExifInterface.TAG_GPS_IMG_DIRECTION, - ExifInterface.TAG_GPS_MAP_DATUM, - ExifInterface.TAG_GPS_DEST_LATITUDE_REF, - ExifInterface.TAG_GPS_DEST_LATITUDE, - ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, - ExifInterface.TAG_GPS_DEST_LONGITUDE, - ExifInterface.TAG_GPS_DEST_BEARING_REF, - ExifInterface.TAG_GPS_DEST_BEARING, - ExifInterface.TAG_GPS_DEST_DISTANCE_REF, - ExifInterface.TAG_GPS_DEST_DISTANCE, - ExifInterface.TAG_GPS_PROCESSING_METHOD, - ExifInterface.TAG_GPS_AREA_INFORMATION, - ExifInterface.TAG_GPS_DATESTAMP, - ExifInterface.TAG_GPS_DIFFERENTIAL, - ExifInterface.TAG_GPS_H_POSITIONING_ERROR, - ) + ExifInterface.TAG_X_RESOLUTION, + ExifInterface.TAG_Y_RESOLUTION, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_ARTIST, + ExifInterface.TAG_COPYRIGHT, + ExifInterface.TAG_EXPOSURE_TIME, + ExifInterface.TAG_F_NUMBER, + ExifInterface.TAG_EXPOSURE_PROGRAM, + ExifInterface.TAG_SPECTRAL_SENSITIVITY, + ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, + ExifInterface.TAG_OECF, + ExifInterface.TAG_SENSITIVITY_TYPE, + ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, + ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, + ExifInterface.TAG_ISO_SPEED, + ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, + ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, + ExifInterface.TAG_EXIF_VERSION, + ExifInterface.TAG_DATETIME_ORIGINAL, + ExifInterface.TAG_DATETIME_DIGITIZED, + ExifInterface.TAG_OFFSET_TIME, + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, + ExifInterface.TAG_SHUTTER_SPEED_VALUE, + ExifInterface.TAG_APERTURE_VALUE, + ExifInterface.TAG_BRIGHTNESS_VALUE, + ExifInterface.TAG_EXPOSURE_BIAS_VALUE, + ExifInterface.TAG_MAX_APERTURE_VALUE, + ExifInterface.TAG_SUBJECT_DISTANCE, + ExifInterface.TAG_METERING_MODE, + ExifInterface.TAG_LIGHT_SOURCE, + ExifInterface.TAG_FLASH, + ExifInterface.TAG_FOCAL_LENGTH, + ExifInterface.TAG_SUBJECT_AREA, + ExifInterface.TAG_MAKER_NOTE, + ExifInterface.TAG_USER_COMMENT, + ExifInterface.TAG_SUBSEC_TIME, + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, + ExifInterface.TAG_FLASHPIX_VERSION, + ExifInterface.TAG_FLASH_ENERGY, + ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, + ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, + ExifInterface.TAG_SUBJECT_LOCATION, + ExifInterface.TAG_EXPOSURE_INDEX, + ExifInterface.TAG_SENSING_METHOD, + ExifInterface.TAG_FILE_SOURCE, + ExifInterface.TAG_SCENE_TYPE, + ExifInterface.TAG_CFA_PATTERN, + ExifInterface.TAG_CUSTOM_RENDERED, + ExifInterface.TAG_EXPOSURE_MODE, + ExifInterface.TAG_WHITE_BALANCE, + ExifInterface.TAG_DIGITAL_ZOOM_RATIO, + ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, + ExifInterface.TAG_SCENE_CAPTURE_TYPE, + ExifInterface.TAG_GAIN_CONTROL, + ExifInterface.TAG_CONTRAST, + ExifInterface.TAG_SATURATION, + ExifInterface.TAG_SHARPNESS, + ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, + ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, + ExifInterface.TAG_IMAGE_UNIQUE_ID, + ExifInterface.TAG_CAMERA_OWNER_NAME, + ExifInterface.TAG_BODY_SERIAL_NUMBER, + ExifInterface.TAG_LENS_SPECIFICATION, + ExifInterface.TAG_LENS_MAKE, + ExifInterface.TAG_LENS_MODEL, + ExifInterface.TAG_GAMMA, + ExifInterface.TAG_GPS_VERSION_ID, + ExifInterface.TAG_GPS_LATITUDE_REF, + ExifInterface.TAG_GPS_LATITUDE, + ExifInterface.TAG_GPS_LONGITUDE_REF, + ExifInterface.TAG_GPS_LONGITUDE, + ExifInterface.TAG_GPS_ALTITUDE_REF, + ExifInterface.TAG_GPS_ALTITUDE, + ExifInterface.TAG_GPS_TIMESTAMP, + ExifInterface.TAG_GPS_SATELLITES, + ExifInterface.TAG_GPS_STATUS, + ExifInterface.TAG_GPS_MEASURE_MODE, + ExifInterface.TAG_GPS_DOP, + ExifInterface.TAG_GPS_SPEED_REF, + ExifInterface.TAG_GPS_SPEED, + ExifInterface.TAG_GPS_TRACK_REF, + ExifInterface.TAG_GPS_TRACK, + ExifInterface.TAG_GPS_IMG_DIRECTION_REF, + ExifInterface.TAG_GPS_IMG_DIRECTION, + ExifInterface.TAG_GPS_MAP_DATUM, + ExifInterface.TAG_GPS_DEST_LATITUDE_REF, + ExifInterface.TAG_GPS_DEST_LATITUDE, + ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, + ExifInterface.TAG_GPS_DEST_LONGITUDE, + ExifInterface.TAG_GPS_DEST_BEARING_REF, + ExifInterface.TAG_GPS_DEST_BEARING, + ExifInterface.TAG_GPS_DEST_DISTANCE_REF, + ExifInterface.TAG_GPS_DEST_DISTANCE, + ExifInterface.TAG_GPS_PROCESSING_METHOD, + ExifInterface.TAG_GPS_AREA_INFORMATION, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_GPS_DIFFERENTIAL, + ExifInterface.TAG_GPS_H_POSITIONING_ERROR, + ) - private const val TAG = "ImageProcessorCommandTask" - } + private const val TAG = "ImageProcessorCommandTask" + } - override fun doInBackground( - vararg params: ImageProcessorImageCommand? - ): MessageEvent { - val cmd = params[0]!! - return try { - val outUri = handleCommand(cmd) - System.gc() - ImageProcessorCompletedEvent(outUri) - } catch (e: Throwable) { - logE(TAG, "[doInBackground] Failed while handleCommand", e) - ImageProcessorFailedEvent(e) - } - } + override fun doInBackground( + vararg params: ImageProcessorImageCommand? + ): MessageEvent { + val cmd = params[0]!! + return try { + val outUri = handleCommand(cmd) + System.gc() + ImageProcessorCompletedEvent(outUri) + } catch (e: Throwable) { + logE(TAG, "[doInBackground] Failed while handleCommand", e) + ImageProcessorFailedEvent(e) + } + } - private fun handleCommand(cmd: ImageProcessorImageCommand): Uri { - val file = downloadFile(cmd.fileUrl, cmd.headers) - handleCancel() + private fun handleCommand(cmd: ImageProcessorImageCommand): Uri { + val file = downloadFile(cmd.fileUrl, cmd.headers) + handleCancel() - // special case for lossless rotation - if (cmd is ImageProcessorFilterCommand) { - if (shouldTryLosslessRotate(cmd, cmd.filename)) { - val filter = cmd.filters.first() as Orientation - try { - return loselessRotate( - filter.degree, file, cmd.filename, cmd - ) - } catch (e: Throwable) { - logE( - TAG, - "[handleCommand] Lossless rotation has failed, fallback to lossy", - e - ) - } - } - } + // special case for lossless rotation + if (cmd is ImageProcessorFilterCommand) { + if (shouldTryLosslessRotate(cmd, cmd.filename)) { + val filter = cmd.filters.first() as Orientation + try { + return loselessRotate( + filter.degree, file, cmd.filename, cmd + ) + } catch (e: Throwable) { + logE( + TAG, + "[handleCommand] Lossless rotation has failed, fallback to lossy", + e + ) + } + } + } - return try { - val fileUri = Uri.fromFile(file) - val output = measureTime(TAG, "[handleCommand] Elapsed time", { - cmd.apply(context, fileUri) - }) - handleCancel() - saveBitmap(output, cmd.filename, file, cmd) - } finally { - file.delete() - } - } + return try { + val fileUri = Uri.fromFile(file) + val output = measureTime(TAG, "[handleCommand] Elapsed time", { + cmd.apply(context, fileUri) + }) + handleCancel() + saveBitmap(output, cmd.filename, file, cmd) + } finally { + file.delete() + } + } - private fun shouldTryLosslessRotate( - cmd: ImageProcessorFilterCommand, srcFilename: String - ): Boolean { - try { - if (cmd.filters.size != 1) { - return false - } - if (cmd.filters.first() !is Orientation) { - return false - } - // we can't use the content resolver here because the file we just - // downloaded does not exist in the media store - val ext = srcFilename.split('.').last() - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) - logD(TAG, "[shouldTryLosslessRotate] ext: $ext -> mime: $mime") - return mime == "image/jpeg" - } catch (e: Throwable) { - logE(TAG, "[shouldTryLosslessRotate] Uncaught exception", e) - return false - } - } + private fun shouldTryLosslessRotate( + cmd: ImageProcessorFilterCommand, srcFilename: String + ): Boolean { + try { + if (cmd.filters.size != 1) { + return false + } + if (cmd.filters.first() !is Orientation) { + return false + } + // we can't use the content resolver here because the file we just + // downloaded does not exist in the media store + val ext = srcFilename.split('.').last() + val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) + logD(TAG, "[shouldTryLosslessRotate] ext: $ext -> mime: $mime") + return mime == "image/jpeg" + } catch (e: Throwable) { + logE(TAG, "[shouldTryLosslessRotate] Uncaught exception", e) + return false + } + } - private fun loselessRotate( - degree: Int, srcFile: File, outFilename: String, - cmd: ImageProcessorImageCommand - ): Uri { - logI(TAG, "[loselessRotate] $outFilename") - val outFile = File.createTempFile("out", null, getTempDir(context)) - try { - srcFile.copyTo(outFile, overwrite = true) - val iExif = ExifInterface(srcFile) - val oExif = ExifInterface(outFile) - copyExif(iExif, oExif) - LosslessRotator()(degree, iExif, oExif) - oExif.saveAttributes() + private fun loselessRotate( + degree: Int, srcFile: File, outFilename: String, + cmd: ImageProcessorImageCommand + ): Uri { + logI(TAG, "[loselessRotate] $outFilename") + val outFile = File.createTempFile("out", null, getTempDir(context)) + try { + srcFile.copyTo(outFile, overwrite = true) + val iExif = ExifInterface(srcFile) + val oExif = ExifInterface(outFile) + copyExif(iExif, oExif) + LosslessRotator()(degree, iExif, oExif) + oExif.saveAttributes() - handleCancel() - val persister = getPersister(cmd.isSaveToServer) - return persister.persist(cmd, outFile) - } finally { - outFile.delete() - } - } + handleCancel() + val persister = getPersister(cmd.isSaveToServer) + return persister.persist(cmd, outFile) + } finally { + outFile.delete() + } + } - private fun downloadFile( - fileUrl: String, headers: Map? - ): File { - logI(TAG, "[downloadFile] $fileUrl") - return (URL(fileUrl).openConnection() as HttpURLConnection).apply { - requestMethod = "GET" - instanceFollowRedirects = true - connectTimeout = 8000 - readTimeout = 15000 - for (entry in (headers ?: mapOf()).entries) { - setRequestProperty(entry.key, entry.value) - } - }.use { - val responseCode = it.responseCode - if (responseCode / 100 == 2) { - val file = File.createTempFile("img", null, getTempDir(context)) - file.outputStream().use { oStream -> - it.inputStream.copyTo(oStream) - } - file - } else { - logE( - TAG, - "[downloadFile] Failed downloading file: HTTP$responseCode" - ) - throw HttpException( - responseCode, "Failed downloading file (HTTP$responseCode)" - ) - } - } - } + private fun downloadFile( + fileUrl: String, headers: Map? + ): File { + logI(TAG, "[downloadFile] $fileUrl") + return (URL(fileUrl).openConnection() as HttpURLConnection).apply { + requestMethod = "GET" + instanceFollowRedirects = true + connectTimeout = 8000 + readTimeout = 15000 + for (entry in (headers ?: mapOf()).entries) { + setRequestProperty(entry.key, entry.value) + } + }.use { + val responseCode = it.responseCode + if (responseCode / 100 == 2) { + val file = File.createTempFile("img", null, getTempDir(context)) + file.outputStream().use { oStream -> + it.inputStream.copyTo(oStream) + } + file + } else { + logE( + TAG, + "[downloadFile] Failed downloading file: HTTP$responseCode" + ) + throw HttpException( + responseCode, "Failed downloading file (HTTP$responseCode)" + ) + } + } + } - private fun saveBitmap( - bitmap: Bitmap, filename: String, srcFile: File, - cmd: ImageProcessorImageCommand - ): Uri { - logI(TAG, "[saveBitmap] $filename") - val outFile = File.createTempFile("out", null, getTempDir(context)) - try { - outFile.outputStream().use { - bitmap.compress(Bitmap.CompressFormat.JPEG, 85, it) - } + private fun saveBitmap( + bitmap: Bitmap, filename: String, srcFile: File, + cmd: ImageProcessorImageCommand + ): Uri { + logI(TAG, "[saveBitmap] $filename") + val outFile = File.createTempFile("out", null, getTempDir(context)) + try { + outFile.outputStream().use { + bitmap.compress(Bitmap.CompressFormat.JPEG, 85, it) + } - // then copy the EXIF tags - try { - val iExif = ExifInterface(srcFile) - val oExif = ExifInterface(outFile) - copyExif(iExif, oExif) - oExif.saveAttributes() - } catch (e: Throwable) { - logE(TAG, "[copyExif] Failed while saving EXIF", e) - } + // then copy the EXIF tags + try { + val iExif = ExifInterface(srcFile) + val oExif = ExifInterface(outFile) + copyExif(iExif, oExif) + oExif.saveAttributes() + } catch (e: Throwable) { + logE(TAG, "[copyExif] Failed while saving EXIF", e) + } - val persister = getPersister(cmd.isSaveToServer) - return persister.persist(cmd, outFile) - } finally { - outFile.delete() - } - } + val persister = getPersister(cmd.isSaveToServer) + return persister.persist(cmd, outFile) + } finally { + outFile.delete() + } + } - private fun copyExif(from: ExifInterface, to: ExifInterface) { - // only a subset will be copied over - for (t in exifTagOfInterests) { - try { - from.getAttribute(t)?.let { to.setAttribute(t, it) } - } catch (e: Throwable) { - logE(TAG, "[copyExif] Failed while copying tag: $t", e) - } - } - } + private fun copyExif(from: ExifInterface, to: ExifInterface) { + // only a subset will be copied over + for (t in exifTagOfInterests) { + try { + from.getAttribute(t)?.let { to.setAttribute(t, it) } + } catch (e: Throwable) { + logE(TAG, "[copyExif] Failed while copying tag: $t", e) + } + } + } - private fun handleCancel() { - if (isCancelled) { - logI(TAG, "[handleCancel] Canceled") - throw InterruptedException() - } - } + private fun handleCancel() { + if (isCancelled) { + logI(TAG, "[handleCancel] Canceled") + throw InterruptedException() + } + } - private fun getPersister(isSaveToServer: Boolean): EnhancedFilePersister { - return if (isSaveToServer) { - EnhancedFileServerPersisterWithFallback(context) - } else { - EnhancedFileDevicePersister(context) - } - } + private fun getPersister(isSaveToServer: Boolean): EnhancedFilePersister { + return if (isSaveToServer) { + EnhancedFileServerPersisterWithFallback(context) + } else { + EnhancedFileDevicePersister(context) + } + } - @SuppressLint("StaticFieldLeak") - private val context = context + @SuppressLint("StaticFieldLeak") + private val context = context } private interface AsyncTaskCancellable { - fun cancel(a: Boolean): Boolean + fun cancel(a: Boolean): Boolean } private fun getTempDir(context: Context): File { - val f = File(context.cacheDir, "imageProcessor") - if (!f.exists()) { - f.mkdirs() - } else if (!f.isDirectory) { - f.delete() - f.mkdirs() - } - return f + val f = File(context.cacheDir, "imageProcessor") + if (!f.exists()) { + f.mkdirs() + } else if (!f.isDirectory) { + f.delete() + f.mkdirs() + } + return f } private interface EnhancedFilePersister { - fun persist(cmd: ImageProcessorImageCommand, file: File): Uri + fun persist(cmd: ImageProcessorImageCommand, file: File): Uri } private class EnhancedFileDevicePersister(context: Context) : - EnhancedFilePersister { - override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri { - val uri = MediaStoreUtil.copyFileToDownload( - context, Uri.fromFile(file), cmd.filename, - "Photos (for Nextcloud)/${getSubDir(cmd)}" - ) - return uri - } + EnhancedFilePersister { + override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri { + val uri = MediaStoreUtil.copyFileToDownload( + context, Uri.fromFile(file), cmd.filename, + "Photos (for Nextcloud)/${getSubDir(cmd)}" + ) + return uri + } - private fun getSubDir(cmd: ImageProcessorImageCommand): String { - return if (!cmd.isEnhanceCommand()) { - "Edited Photos" - } else { - "Enhanced Photos" - } - } + private fun getSubDir(cmd: ImageProcessorImageCommand): String { + return if (!cmd.isEnhanceCommand()) { + "Edited Photos" + } else { + "Enhanced Photos" + } + } - val context = context + val context = context } private class EnhancedFileServerPersister : EnhancedFilePersister { - companion object { - const val TAG = "EnhancedFileServerPersister" - } + companion object { + const val TAG = "EnhancedFileServerPersister" + } - override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri { - val ext = cmd.fileUrl.substringAfterLast('.', "") - val url = if (ext.contains('/')) { - // no ext - "${cmd.fileUrl}_${getSuffix(cmd)}.jpg" - } else { - "${cmd.fileUrl.substringBeforeLast('.', "")}_${getSuffix(cmd)}.jpg" - } - logI(TAG, "[persist] Persist file to server: $url") - (URL(url).openConnection() as HttpURLConnection).apply { - requestMethod = "PUT" - instanceFollowRedirects = true - connectTimeout = 8000 - for (entry in (cmd.headers ?: mapOf()).entries) { - setRequestProperty(entry.key, entry.value) - } - }.use { - file.inputStream() - .use { iStream -> iStream.copyTo(it.outputStream) } - val responseCode = it.responseCode - if (responseCode / 100 != 2) { - logE(TAG, "[persist] Failed uploading file: HTTP$responseCode") - throw HttpException( - responseCode, "Failed uploading file (HTTP$responseCode)" - ) - } - } - return Uri.parse(url) - } + override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri { + val ext = cmd.fileUrl.substringAfterLast('.', "") + val url = if (ext.contains('/')) { + // no ext + "${cmd.fileUrl}_${getSuffix(cmd)}.jpg" + } else { + "${cmd.fileUrl.substringBeforeLast('.', "")}_${getSuffix(cmd)}.jpg" + } + logI(TAG, "[persist] Persist file to server: $url") + (URL(url).openConnection() as HttpURLConnection).apply { + requestMethod = "PUT" + instanceFollowRedirects = true + connectTimeout = 8000 + for (entry in (cmd.headers ?: mapOf()).entries) { + setRequestProperty(entry.key, entry.value) + } + }.use { + file.inputStream() + .use { iStream -> iStream.copyTo(it.outputStream) } + val responseCode = it.responseCode + if (responseCode / 100 != 2) { + logE(TAG, "[persist] Failed uploading file: HTTP$responseCode") + throw HttpException( + responseCode, "Failed uploading file (HTTP$responseCode)" + ) + } + } + return Uri.parse(url) + } - private fun getSuffix(cmd: ImageProcessorImageCommand): String { - val epoch = System.currentTimeMillis() / 1000 - return if (!cmd.isEnhanceCommand()) { - "edited_$epoch" - } else { - "enhanced_$epoch" - } - } + private fun getSuffix(cmd: ImageProcessorImageCommand): String { + val epoch = System.currentTimeMillis() / 1000 + return if (!cmd.isEnhanceCommand()) { + "edited_$epoch" + } else { + "enhanced_$epoch" + } + } } private class EnhancedFileServerPersisterWithFallback(context: Context) : - EnhancedFilePersister { - companion object { - const val TAG = "EnhancedFileServerPersisterWithFallback" - } + EnhancedFilePersister { + companion object { + const val TAG = "EnhancedFileServerPersisterWithFallback" + } - override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri { - try { - return server.persist(cmd, file) - } catch (e: Throwable) { - logW( - TAG, - "[persist] Failed while persisting to server, switch to fallback", - e - ) - } - return fallback.persist(cmd, file) - } + override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri { + try { + return server.persist(cmd, file) + } catch (e: Throwable) { + logW( + TAG, + "[persist] Failed while persisting to server, switch to fallback", + e + ) + } + return fallback.persist(cmd, file) + } - private val server = EnhancedFileServerPersister() - private val fallback = EnhancedFileDevicePersister(context) + private val server = EnhancedFileServerPersister() + private val fallback = EnhancedFileDevicePersister(context) } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/K.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/K.kt index e1c3dabb..e0b43aa8 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/K.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/K.kt @@ -1,16 +1,16 @@ package com.nkming.nc_photos.np_platform_image_processor internal interface K { - companion object { - const val LIB_ID = "com.nkming.nc_photos.np_platform_image_processor" + companion object { + const val LIB_ID = "com.nkming.nc_photos.np_platform_image_processor" - const val IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID = 5000 - const val IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID = 5001 - const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002 + const val IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID = 5000 + const val IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID = 5001 + const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002 - const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT = - "${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT" + const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT = + "${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT" - const val EXTRA_IMAGE_RESULT_URI = "${LIB_ID}.EXTRA_IMAGE_RESULT_URI" - } + const val EXTRA_IMAGE_RESULT_URI = "${LIB_ID}.EXTRA_IMAGE_RESULT_URI" + } } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEvent.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEvent.kt index a9362352..49a9e209 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEvent.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEvent.kt @@ -2,14 +2,14 @@ package com.nkming.nc_photos.np_platform_image_processor // To be removed internal interface NativeEvent { - fun getId(): String - fun getData(): String? = null + fun getId(): String + fun getData(): String? = null } internal class ImageProcessorUploadSuccessEvent : NativeEvent { - companion object { - const val id = "ImageProcessorUploadSuccessEvent" - } + companion object { + const val id = "ImageProcessorUploadSuccessEvent" + } - override fun getId() = id + override fun getId() = id } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEventChannelHandler.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEventChannelHandler.kt index 65524a31..9c2afc75 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEventChannelHandler.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEventChannelHandler.kt @@ -5,68 +5,68 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler, - EventChannel.StreamHandler { - companion object { - const val EVENT_CHANNEL = "${K.LIB_ID}/native_event" - const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method" + EventChannel.StreamHandler { + companion object { + const val EVENT_CHANNEL = "${K.LIB_ID}/native_event" + const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method" - /** - * Fire native events on the native side - */ - fun fire(eventObj: NativeEvent) { - synchronized(eventSinks) { - for (s in eventSinks.values) { - s.success(buildMap { - put("event", eventObj.getId()) - eventObj.getData()?.also { put("data", it) } - }) - } - } - } + /** + * Fire native events on the native side + */ + fun fire(eventObj: NativeEvent) { + synchronized(eventSinks) { + for (s in eventSinks.values) { + s.success(buildMap { + put("event", eventObj.getId()) + eventObj.getData()?.also { put("data", it) } + }) + } + } + } - private val eventSinks = mutableMapOf() - private var nextId = 0 - } + private val eventSinks = mutableMapOf() + private var nextId = 0 + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "fire" -> { - try { - fire( - call.argument("event")!!, call.argument("data"), result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "fire" -> { + try { + fire( + call.argument("event")!!, call.argument("data"), result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } + } + } - override fun onListen(arguments: Any?, events: EventChannel.EventSink) { - synchronized(eventSinks) { - eventSinks[id] = events - } - } + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + synchronized(eventSinks) { + eventSinks[id] = events + } + } - override fun onCancel(arguments: Any?) { - synchronized(eventSinks) { - eventSinks.remove(id) - } - } + override fun onCancel(arguments: Any?) { + synchronized(eventSinks) { + eventSinks.remove(id) + } + } - private fun fire( - event: String, data: String?, result: MethodChannel.Result - ) { - synchronized(eventSinks) { - for (s in eventSinks.values) { - s.success(buildMap { - put("event", event) - if (data != null) put("data", data) - }) - } - } - result.success(null) - } + private fun fire( + event: String, data: String?, result: MethodChannel.Result + ) { + synchronized(eventSinks) { + for (s in eventSinks.values) { + s.success(buildMap { + put("event", event) + if (data != null) put("data", data) + }) + } + } + result.success(null) + } - private val id = nextId++ + private val id = nextId++ } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NpPlatformImageProcessorPlugin.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NpPlatformImageProcessorPlugin.kt index 2682e0d9..ae15dc83 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NpPlatformImageProcessorPlugin.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NpPlatformImageProcessorPlugin.kt @@ -6,52 +6,51 @@ import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodChannel class NpPlatformImageProcessorPlugin : FlutterPlugin { - companion object { - init { - System.loadLibrary("np_platform_image_processor") - } + companion object { + init { + System.loadLibrary("np_platform_image_processor") + } - const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT = - K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT - const val EXTRA_IMAGE_RESULT_URI = - K.EXTRA_IMAGE_RESULT_URI - } + const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT = + K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT + const val EXTRA_IMAGE_RESULT_URI = K.EXTRA_IMAGE_RESULT_URI + } - override fun onAttachedToEngine( - @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding - ) { - imageProcessorMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - ImageProcessorChannelHandler.METHOD_CHANNEL - ) - imageProcessorMethodChannel.setMethodCallHandler( - ImageProcessorChannelHandler( - flutterPluginBinding.applicationContext - ) - ) + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + imageProcessorMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + ImageProcessorChannelHandler.METHOD_CHANNEL + ) + imageProcessorMethodChannel.setMethodCallHandler( + ImageProcessorChannelHandler( + flutterPluginBinding.applicationContext + ) + ) - val nativeEventHandler = NativeEventChannelHandler() - nativeEventChannel = EventChannel( - flutterPluginBinding.binaryMessenger, - NativeEventChannelHandler.EVENT_CHANNEL - ) - nativeEventChannel.setStreamHandler(nativeEventHandler) - nativeEventMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - NativeEventChannelHandler.METHOD_CHANNEL - ) - nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler) - } + val nativeEventHandler = NativeEventChannelHandler() + nativeEventChannel = EventChannel( + flutterPluginBinding.binaryMessenger, + NativeEventChannelHandler.EVENT_CHANNEL + ) + nativeEventChannel.setStreamHandler(nativeEventHandler) + nativeEventMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + NativeEventChannelHandler.METHOD_CHANNEL + ) + nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler) + } - override fun onDetachedFromEngine( - @NonNull binding: FlutterPlugin.FlutterPluginBinding - ) { - imageProcessorMethodChannel.setMethodCallHandler(null) - nativeEventChannel.setStreamHandler(null) - nativeEventMethodChannel.setMethodCallHandler(null) - } + override fun onDetachedFromEngine( + @NonNull binding: FlutterPlugin.FlutterPluginBinding + ) { + imageProcessorMethodChannel.setMethodCallHandler(null) + nativeEventChannel.setStreamHandler(null) + nativeEventMethodChannel.setMethodCallHandler(null) + } - private lateinit var imageProcessorMethodChannel: MethodChannel - private lateinit var nativeEventChannel: EventChannel - private lateinit var nativeEventMethodChannel: MethodChannel + private lateinit var imageProcessorMethodChannel: MethodChannel + private lateinit var nativeEventChannel: EventChannel + private lateinit var nativeEventMethodChannel: MethodChannel } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ArbitraryStyleTransfer.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ArbitraryStyleTransfer.kt index 9d9e4ac9..9bd619ab 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ArbitraryStyleTransfer.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ArbitraryStyleTransfer.kt @@ -10,79 +10,62 @@ import com.nkming.nc_photos.np_android_core.logI import com.nkming.nc_photos.np_android_core.use internal class ArbitraryStyleTransfer( - context: Context, - maxWidth: Int, - maxHeight: Int, - styleUri: Uri, - weight: Float + context: Context, maxWidth: Int, maxHeight: Int, styleUri: Uri, + weight: Float ) { - companion object { - const val TAG = "ArbitraryStyleTransfer" - } + companion object { + const val TAG = "ArbitraryStyleTransfer" + } - fun infer(imageUri: Uri): Bitmap { - val width: Int - val height: Int - val rgb8Image = BitmapUtil.loadImage( - context, - imageUri, - maxWidth, - maxHeight, - BitmapResizeMethod.FIT, - isAllowSwapSide = true, - shouldUpscale = false, - shouldFixOrientation = true - ).use { - width = it.width - height = it.height - TfLiteHelper.bitmapToRgb8Array(it) - } - val rgb8Style = BitmapUtil.loadImage( - context, - styleUri, - 256, - 256, - BitmapResizeMethod.FILL, - isAllowSwapSide = false, - shouldUpscale = true - ).use { - val styleBitmap = if (it.width != 256 || it.height != 256) { - val x = (it.width - 256) / 2 - val y = (it.height - 256) / 2 - logI( - TAG, - "[infer] Resize and crop style image: ${it.width}x${it.height} -> 256x256 ($x, $y)" - ) - // crop - Bitmap.createBitmap(it, x, y, 256, 256) - } else { - it - } - styleBitmap.use { - TfLiteHelper.bitmapToRgb8Array(styleBitmap) - } - } - val am = context.assets + fun infer(imageUri: Uri): Bitmap { + val width: Int + val height: Int + val rgb8Image = BitmapUtil.loadImage( + context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true + ).use { + width = it.width + height = it.height + TfLiteHelper.bitmapToRgb8Array(it) + } + val rgb8Style = BitmapUtil.loadImage( + context, styleUri, 256, 256, BitmapResizeMethod.FILL, + isAllowSwapSide = false, shouldUpscale = true + ).use { + val styleBitmap = if (it.width != 256 || it.height != 256) { + val x = (it.width - 256) / 2 + val y = (it.height - 256) / 2 + logI( + TAG, + "[infer] Resize and crop style image: ${it.width}x${it.height} -> 256x256 ($x, $y)" + ) + // crop + Bitmap.createBitmap(it, x, y, 256, 256) + } else { + it + } + styleBitmap.use { + TfLiteHelper.bitmapToRgb8Array(styleBitmap) + } + } + val am = context.assets - return inferNative( - am, rgb8Image, width, height, rgb8Style, weight - ).let { - TfLiteHelper.rgb8ArrayToBitmap(it, width, height) - } - } + return inferNative( + am, rgb8Image, width, height, rgb8Style, weight + ).let { + TfLiteHelper.rgb8ArrayToBitmap(it, width, height) + } + } - private external fun inferNative( - am: AssetManager, - image: ByteArray, - width: Int, - height: Int, - style: ByteArray, - weight: Float - ): ByteArray + private external fun inferNative( + am: AssetManager, image: ByteArray, width: Int, height: Int, + style: ByteArray, weight: Float + ): ByteArray - private val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight - private val styleUri = styleUri - private val weight = weight + private val context = context + private val maxWidth = maxWidth + private val maxHeight = maxHeight + private val styleUri = styleUri + private val weight = weight } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/BlackPoint.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/BlackPoint.kt index 923a390f..49ff9831 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/BlackPoint.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/BlackPoint.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class BlackPoint(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Brightness.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Brightness.kt index f6a6d12d..bbe9ee4e 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Brightness.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Brightness.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class Brightness(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Contrast.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Contrast.kt index b92574df..3e25963b 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Contrast.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Contrast.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class Contrast(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Cool.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Cool.kt index 25c80169..883e552d 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Cool.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Cool.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class Cool(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Crop.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Crop.kt index 44c81f44..61d7fa52 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Crop.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Crop.kt @@ -5,27 +5,22 @@ import com.nkming.nc_photos.np_platform_image_processor.ImageFilter import java.lang.Integer.max internal class Crop( - val top: Double, val left: Double, val bottom: Double, val right: Double + val top: Double, val left: Double, val bottom: Double, val right: Double ) : ImageFilter { - override fun apply(rgba8: Rgba8Image): Rgba8Image { - // prevent w/h == 0 - val width = max((rgba8.width * (right - left)).toInt(), 1) - val height = max((rgba8.height * (bottom - top)).toInt(), 1) - val top = (rgba8.height * top).toInt() - val left = (rgba8.width * left).toInt() - val data = applyNative( - rgba8.pixel, rgba8.width, rgba8.height, top, left, width, height - ) - return Rgba8Image(data, width, height) - } + override fun apply(rgba8: Rgba8Image): Rgba8Image { + // prevent w/h == 0 + val width = max((rgba8.width * (right - left)).toInt(), 1) + val height = max((rgba8.height * (bottom - top)).toInt(), 1) + val top = (rgba8.height * top).toInt() + val left = (rgba8.width * left).toInt() + val data = applyNative( + rgba8.pixel, rgba8.width, rgba8.height, top, left, width, height + ) + return Rgba8Image(data, width, height) + } - private external fun applyNative( - rgba8: ByteArray, - width: Int, - height: Int, - top: Int, - left: Int, - dstWidth: Int, - dstHeight: Int - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, top: Int, left: Int, + dstWidth: Int, dstHeight: Int + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/DeepLab3.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/DeepLab3.kt index 3b0d6dc5..74de5e3a 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/DeepLab3.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/DeepLab3.kt @@ -16,79 +16,66 @@ import com.nkming.nc_photos.np_android_core.use * See: https://github.com/tensorflow/models/tree/master/research/deeplab */ internal class DeepLab3Portrait( - context: Context, maxWidth: Int, maxHeight: Int, radius: Int + context: Context, maxWidth: Int, maxHeight: Int, radius: Int ) { - fun infer(imageUri: Uri): Bitmap { - val width: Int - val height: Int - val rgb8Image = BitmapUtil.loadImage( - context, - imageUri, - maxWidth, - maxHeight, - BitmapResizeMethod.FIT, - isAllowSwapSide = true, - shouldUpscale = false, - shouldFixOrientation = true - ).use { - width = it.width - height = it.height - TfLiteHelper.bitmapToRgb8Array(it) - } - val am = context.assets + fun infer(imageUri: Uri): Bitmap { + val width: Int + val height: Int + val rgb8Image = BitmapUtil.loadImage( + context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true + ).use { + width = it.width + height = it.height + TfLiteHelper.bitmapToRgb8Array(it) + } + val am = context.assets - return inferNative(am, rgb8Image, width, height, radius).let { - TfLiteHelper.rgb8ArrayToBitmap(it, width, height) - } - } + return inferNative(am, rgb8Image, width, height, radius).let { + TfLiteHelper.rgb8ArrayToBitmap(it, width, height) + } + } - private external fun inferNative( - am: AssetManager, image: ByteArray, width: Int, height: Int, radius: Int - ): ByteArray + private external fun inferNative( + am: AssetManager, image: ByteArray, width: Int, height: Int, radius: Int + ): ByteArray - private val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight - private val radius = radius + private val context = context + private val maxWidth = maxWidth + private val maxHeight = maxHeight + private val radius = radius } class DeepLab3ColorPop( - context: Context, maxWidth: Int, maxHeight: Int, weight: Float + context: Context, maxWidth: Int, maxHeight: Int, weight: Float ) { - fun infer(imageUri: Uri): Bitmap { - val width: Int - val height: Int - val rgb8Image = BitmapUtil.loadImage( - context, - imageUri, - maxWidth, - maxHeight, - BitmapResizeMethod.FIT, - isAllowSwapSide = true, - shouldUpscale = false, - shouldFixOrientation = true - ).use { - width = it.width - height = it.height - TfLiteHelper.bitmapToRgb8Array(it) - } - val am = context.assets + fun infer(imageUri: Uri): Bitmap { + val width: Int + val height: Int + val rgb8Image = BitmapUtil.loadImage( + context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true + ).use { + width = it.width + height = it.height + TfLiteHelper.bitmapToRgb8Array(it) + } + val am = context.assets - return inferNative(am, rgb8Image, width, height, weight).let { - TfLiteHelper.rgb8ArrayToBitmap(it, width, height) - } - } + return inferNative(am, rgb8Image, width, height, weight).let { + TfLiteHelper.rgb8ArrayToBitmap(it, width, height) + } + } - private external fun inferNative( - am: AssetManager, - image: ByteArray, - width: Int, - height: Int, - weight: Float - ): ByteArray + private external fun inferNative( + am: AssetManager, image: ByteArray, width: Int, height: Int, + weight: Float + ): ByteArray - private val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight - private val weight = weight + private val context = context + private val maxWidth = maxWidth + private val maxHeight = maxHeight + private val weight = weight } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Esrgan.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Esrgan.kt index 0db537bd..03ee9704 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Esrgan.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Esrgan.kt @@ -9,35 +9,30 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil import com.nkming.nc_photos.np_android_core.use internal class Esrgan(context: Context, maxWidth: Int, maxHeight: Int) { - fun infer(imageUri: Uri): Bitmap { - val width: Int - val height: Int - val rgb8Image = BitmapUtil.loadImage( - context, - imageUri, - maxWidth / 4, - maxHeight / 4, - BitmapResizeMethod.FIT, - isAllowSwapSide = true, - shouldUpscale = false, - shouldFixOrientation = true - ).use { - width = it.width - height = it.height - TfLiteHelper.bitmapToRgb8Array(it) - } - val am = context.assets + fun infer(imageUri: Uri): Bitmap { + val width: Int + val height: Int + val rgb8Image = BitmapUtil.loadImage( + context, imageUri, maxWidth / 4, maxHeight / 4, + BitmapResizeMethod.FIT, isAllowSwapSide = true, + shouldUpscale = false, shouldFixOrientation = true + ).use { + width = it.width + height = it.height + TfLiteHelper.bitmapToRgb8Array(it) + } + val am = context.assets - return inferNative(am, rgb8Image, width, height).let { - TfLiteHelper.rgb8ArrayToBitmap(it, width * 4, height * 4) - } - } + return inferNative(am, rgb8Image, width, height).let { + TfLiteHelper.rgb8ArrayToBitmap(it, width * 4, height * 4) + } + } - private external fun inferNative( - am: AssetManager, image: ByteArray, width: Int, height: Int - ): ByteArray + private external fun inferNative( + am: AssetManager, image: ByteArray, width: Int, height: Int + ): ByteArray - private val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight + private val context = context + private val maxWidth = maxWidth + private val maxHeight = maxHeight } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ImageFilterProcessor.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ImageFilterProcessor.kt index 4789cb20..75359bb4 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ImageFilterProcessor.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ImageFilterProcessor.kt @@ -10,34 +10,29 @@ import com.nkming.nc_photos.np_android_core.use import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class ImageFilterProcessor( - context: Context, maxWidth: Int, maxHeight: Int, filters: List + context: Context, maxWidth: Int, maxHeight: Int, filters: List ) { - companion object { - const val TAG = "ImageFilterProcessor" - } + companion object { + const val TAG = "ImageFilterProcessor" + } - fun apply(imageUri: Uri): Bitmap { - var img = BitmapUtil.loadImage( - context, - imageUri, - maxWidth, - maxHeight, - BitmapResizeMethod.FIT, - isAllowSwapSide = true, - shouldUpscale = false, - shouldFixOrientation = true - ).use { - Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height) - } + fun apply(imageUri: Uri): Bitmap { + var img = BitmapUtil.loadImage( + context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true + ).use { + Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height) + } - for (f in filters) { - img = f.apply(img) - } - return img.toBitmap() - } + for (f in filters) { + img = f.apply(img) + } + return img.toBitmap() + } - private val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight - private val filters = filters + private val context = context + private val maxWidth = maxWidth + private val maxHeight = maxHeight + private val filters = filters } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/LosslessRotator.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/LosslessRotator.kt index 295a0cc4..f0f400e9 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/LosslessRotator.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/LosslessRotator.kt @@ -8,75 +8,75 @@ import com.nkming.nc_photos.np_android_core.logI * that the viewer will rotate the image when displaying the image */ internal class LosslessRotator { - companion object { - const val TAG = "LosslessRotator" - } + companion object { + const val TAG = "LosslessRotator" + } - /** - * Set the Orientation tag in @a dstExif according to the value in - * @a srcExif - * - * @param degree Either 0, 90, 180, -90 or -180 - * @param srcExif ExifInterface of the src file - * @param dstExif ExifInterface of the dst file - */ - operator fun invoke( - degree: Int, srcExif: ExifInterface, dstExif: ExifInterface - ) { - assert(degree in listOf(0, 90, 180, -90, -180)) - val srcOrientation = - srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1) - val dstOrientation = rotateExifOrientationValue(srcOrientation, degree) - logI(TAG, "[invoke] $degree, $srcOrientation -> $dstOrientation") - dstExif.setAttribute( - ExifInterface.TAG_ORIENTATION, dstOrientation.toString() - ) - } + /** + * Set the Orientation tag in @a dstExif according to the value in + * @a srcExif + * + * @param degree Either 0, 90, 180, -90 or -180 + * @param srcExif ExifInterface of the src file + * @param dstExif ExifInterface of the dst file + */ + operator fun invoke( + degree: Int, srcExif: ExifInterface, dstExif: ExifInterface + ) { + assert(degree in listOf(0, 90, 180, -90, -180)) + val srcOrientation = + srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1) + val dstOrientation = rotateExifOrientationValue(srcOrientation, degree) + logI(TAG, "[invoke] $degree, $srcOrientation -> $dstOrientation") + dstExif.setAttribute( + ExifInterface.TAG_ORIENTATION, dstOrientation.toString() + ) + } - /** - * Return a new orientation representing the resulting value after rotating - * @a value - * - * @param value - * @param degree Either 0, 90, 180, -90 or -180 - * @return - */ - private fun rotateExifOrientationValue(value: Int, degree: Int): Int { - if (degree == 0) { - return value - } - var newValue = rotateExifOrientationValue90Ccw(value) - if (degree == 90) { - return newValue - } - newValue = rotateExifOrientationValue90Ccw(newValue) - if (degree == 180 || degree == -180) { - return newValue - } - newValue = rotateExifOrientationValue90Ccw(newValue) - return newValue - } + /** + * Return a new orientation representing the resulting value after rotating + * @a value + * + * @param value + * @param degree Either 0, 90, 180, -90 or -180 + * @return + */ + private fun rotateExifOrientationValue(value: Int, degree: Int): Int { + if (degree == 0) { + return value + } + var newValue = rotateExifOrientationValue90Ccw(value) + if (degree == 90) { + return newValue + } + newValue = rotateExifOrientationValue90Ccw(newValue) + if (degree == 180 || degree == -180) { + return newValue + } + newValue = rotateExifOrientationValue90Ccw(newValue) + return newValue + } - /** - * Return a new orientation representing the resulting value after rotating - * @a value for 90 degree CCW - * - * @param value - * @return - */ - private fun rotateExifOrientationValue90Ccw(value: Int): Int { - return when (value) { - 0, 1 -> 8 - 8 -> 3 - 3 -> 6 - 6 -> 1 - 2 -> 7 - 7 -> 4 - 4 -> 5 - 5 -> 2 - else -> throw IllegalArgumentException( - "Invalid EXIF Orientation value: $value" - ) - } - } + /** + * Return a new orientation representing the resulting value after rotating + * @a value for 90 degree CCW + * + * @param value + * @return + */ + private fun rotateExifOrientationValue90Ccw(value: Int): Int { + return when (value) { + 0, 1 -> 8 + 8 -> 3 + 3 -> 6 + 6 -> 1 + 2 -> 7 + 7 -> 4 + 4 -> 5 + 5 -> 2 + else -> throw IllegalArgumentException( + "Invalid EXIF Orientation value: $value" + ) + } + } } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/NeurOp.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/NeurOp.kt index f2b63482..8d9df485 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/NeurOp.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/NeurOp.kt @@ -9,35 +9,30 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil import com.nkming.nc_photos.np_android_core.use internal class NeurOp(context: Context, maxWidth: Int, maxHeight: Int) { - fun infer(imageUri: Uri): Bitmap { - val width: Int - val height: Int - val rgb8Image = BitmapUtil.loadImage( - context, - imageUri, - maxWidth, - maxHeight, - BitmapResizeMethod.FIT, - isAllowSwapSide = true, - shouldUpscale = false, - shouldFixOrientation = true - ).use { - width = it.width - height = it.height - TfLiteHelper.bitmapToRgb8Array(it) - } - val am = context.assets + fun infer(imageUri: Uri): Bitmap { + val width: Int + val height: Int + val rgb8Image = BitmapUtil.loadImage( + context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true + ).use { + width = it.width + height = it.height + TfLiteHelper.bitmapToRgb8Array(it) + } + val am = context.assets - return inferNative(am, rgb8Image, width, height).let { - TfLiteHelper.rgb8ArrayToBitmap(it, width, height) - } - } + return inferNative(am, rgb8Image, width, height).let { + TfLiteHelper.rgb8ArrayToBitmap(it, width, height) + } + } - private external fun inferNative( - am: AssetManager, image: ByteArray, width: Int, height: Int - ): ByteArray + private external fun inferNative( + am: AssetManager, image: ByteArray, width: Int, height: Int + ): ByteArray - private val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight + private val context = context + private val maxWidth = maxWidth + private val maxHeight = maxHeight } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Orientation.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Orientation.kt index 37bc1c2a..e20836ea 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Orientation.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Orientation.kt @@ -5,16 +5,15 @@ import com.nkming.nc_photos.np_platform_image_processor.ImageFilter import kotlin.math.abs internal class Orientation(val degree: Int) : ImageFilter { - override fun apply(rgba8: Rgba8Image): Rgba8Image { - val data = applyNative(rgba8.pixel, rgba8.width, rgba8.height, degree) - return Rgba8Image( - data, - if (abs(degree) == 90) rgba8.height else rgba8.width, - if (abs(degree) == 90) rgba8.width else rgba8.height - ) - } + override fun apply(rgba8: Rgba8Image): Rgba8Image { + val data = applyNative(rgba8.pixel, rgba8.width, rgba8.height, degree) + return Rgba8Image( + data, if (abs(degree) == 90) rgba8.height else rgba8.width, + if (abs(degree) == 90) rgba8.width else rgba8.height + ) + } - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, degree: Int - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, degree: Int + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Saturation.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Saturation.kt index 704eeebf..eae50449 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Saturation.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Saturation.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class Saturation(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/TfLiteHelper.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/TfLiteHelper.kt index beacf6c9..2bb69b86 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/TfLiteHelper.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/TfLiteHelper.kt @@ -5,80 +5,80 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import java.nio.IntBuffer internal interface TfLiteHelper { - companion object { - /** - * Convert an ARGB_8888 Android bitmap to a RGB8 byte array - * - * @param bitmap - * @return - */ - fun bitmapToRgb8Array(bitmap: Bitmap): ByteArray { - val buffer = IntBuffer.allocate(bitmap.width * bitmap.height) - bitmap.copyPixelsToBuffer(buffer) - val rgb8 = ByteArray(bitmap.width * bitmap.height * 3) - buffer.array().forEachIndexed { i, it -> - run { - rgb8[i * 3] = (it and 0xFF).toByte() - rgb8[i * 3 + 1] = (it shr 8 and 0xFF).toByte() - rgb8[i * 3 + 2] = (it shr 16 and 0xFF).toByte() - } - } - return rgb8 - } + companion object { + /** + * Convert an ARGB_8888 Android bitmap to a RGB8 byte array + * + * @param bitmap + * @return + */ + fun bitmapToRgb8Array(bitmap: Bitmap): ByteArray { + val buffer = IntBuffer.allocate(bitmap.width * bitmap.height) + bitmap.copyPixelsToBuffer(buffer) + val rgb8 = ByteArray(bitmap.width * bitmap.height * 3) + buffer.array().forEachIndexed { i, it -> + run { + rgb8[i * 3] = (it and 0xFF).toByte() + rgb8[i * 3 + 1] = (it shr 8 and 0xFF).toByte() + rgb8[i * 3 + 2] = (it shr 16 and 0xFF).toByte() + } + } + return rgb8 + } - /** - * Convert an ARGB_8888 Android bitmap to a RGBA byte array - * - * @param bitmap - * @return - */ - fun bitmapToRgba8Array(bitmap: Bitmap): ByteArray { - return Rgba8Image.fromBitmap(bitmap).pixel - } + /** + * Convert an ARGB_8888 Android bitmap to a RGBA byte array + * + * @param bitmap + * @return + */ + fun bitmapToRgba8Array(bitmap: Bitmap): ByteArray { + return Rgba8Image.fromBitmap(bitmap).pixel + } - /** - * Convert a RGB8 byte array to an ARGB_8888 Android bitmap - * - * @param rgb8 - * @param width - * @param height - * @return - */ - fun rgb8ArrayToBitmap( - rgb8: ByteArray, width: Int, height: Int - ): Bitmap { - val buffer = IntBuffer.allocate(width * height) - var i = 0 - var pixel = 0 - rgb8.forEach { - val value = it.toInt() and 0xFF - when (i++) { - 0 -> { - // A - pixel = 0xFF shl 24 - // R - pixel = pixel or value - } + /** + * Convert a RGB8 byte array to an ARGB_8888 Android bitmap + * + * @param rgb8 + * @param width + * @param height + * @return + */ + fun rgb8ArrayToBitmap( + rgb8: ByteArray, width: Int, height: Int + ): Bitmap { + val buffer = IntBuffer.allocate(width * height) + var i = 0 + var pixel = 0 + rgb8.forEach { + val value = it.toInt() and 0xFF + when (i++) { + 0 -> { + // A + pixel = 0xFF shl 24 + // R + pixel = pixel or value + } - 1 -> { - // G - pixel = pixel or (value shl 8) - } + 1 -> { + // G + pixel = pixel or (value shl 8) + } - 2 -> { - // B - pixel = pixel or (value shl 16) + 2 -> { + // B + pixel = pixel or (value shl 16) - buffer.put(pixel) - i = 0 - } - } - } - buffer.rewind() - val outputBitmap = - Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - outputBitmap.copyPixelsFromBuffer(buffer) - return outputBitmap - } - } + buffer.put(pixel) + i = 0 + } + } + } + buffer.rewind() + val outputBitmap = + Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + outputBitmap.copyPixelsFromBuffer(buffer) + return outputBitmap + } + } } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Tint.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Tint.kt index d504a826..857b8a32 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Tint.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Tint.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class Tint(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Warmth.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Warmth.kt index 0c746666..2df56d8a 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Warmth.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Warmth.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class Warmth(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/WhitePoint.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/WhitePoint.kt index 7487e46d..b0ba7a01 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/WhitePoint.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/WhitePoint.kt @@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image import com.nkming.nc_photos.np_platform_image_processor.ImageFilter internal class WhitePoint(val weight: Float) : ImageFilter { - override fun apply(rgba8: Rgba8Image) = Rgba8Image( - applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), - rgba8.width, - rgba8.height - ) + override fun apply(rgba8: Rgba8Image) = Rgba8Image( + applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), + rgba8.width, rgba8.height + ) - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, weight: Float - ): ByteArray + private external fun applyNative( + rgba8: ByteArray, width: Int, height: Int, weight: Float + ): ByteArray } diff --git a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ZeroDce.kt b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ZeroDce.kt index 43f41c91..5c44327b 100644 --- a/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ZeroDce.kt +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ZeroDce.kt @@ -9,45 +9,34 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil import com.nkming.nc_photos.np_android_core.use internal class ZeroDce( - context: Context, - maxWidth: Int, - maxHeight: Int, - iteration: Int + context: Context, maxWidth: Int, maxHeight: Int, iteration: Int ) { - fun infer(imageUri: Uri): Bitmap { - val width: Int - val height: Int - val rgb8Image = BitmapUtil.loadImage( - context, - imageUri, - maxWidth, - maxHeight, - BitmapResizeMethod.FIT, - isAllowSwapSide = true, - shouldUpscale = false, - shouldFixOrientation = true - ).use { - width = it.width - height = it.height - TfLiteHelper.bitmapToRgb8Array(it) - } - val am = context.assets + fun infer(imageUri: Uri): Bitmap { + val width: Int + val height: Int + val rgb8Image = BitmapUtil.loadImage( + context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true + ).use { + width = it.width + height = it.height + TfLiteHelper.bitmapToRgb8Array(it) + } + val am = context.assets - return inferNative(am, rgb8Image, width, height, iteration).let { - TfLiteHelper.rgb8ArrayToBitmap(it, width, height) - } - } + return inferNative(am, rgb8Image, width, height, iteration).let { + TfLiteHelper.rgb8ArrayToBitmap(it, width, height) + } + } - private external fun inferNative( - am: AssetManager, - image: ByteArray, - width: Int, - height: Int, - iteration: Int - ): ByteArray + private external fun inferNative( + am: AssetManager, image: ByteArray, width: Int, height: Int, + iteration: Int + ): ByteArray - private val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight - private val iteration = iteration + private val context = context + private val maxWidth = maxWidth + private val maxHeight = maxHeight + private val iteration = iteration } diff --git a/np_platform_lock/android/src/main/kotlin/com/nkming/nc_photos/np_platform_lock/LockChannelHandler.kt b/np_platform_lock/android/src/main/kotlin/com/nkming/nc_photos/np_platform_lock/LockChannelHandler.kt index 5bcd6d02..f4a105de 100644 --- a/np_platform_lock/android/src/main/kotlin/com/nkming/nc_photos/np_platform_lock/LockChannelHandler.kt +++ b/np_platform_lock/android/src/main/kotlin/com/nkming/nc_photos/np_platform_lock/LockChannelHandler.kt @@ -18,77 +18,76 @@ import io.flutter.plugin.common.MethodChannel * fun unlock(lockId: Int): Unit */ class LockChannelHandler : MethodChannel.MethodCallHandler { - companion object { - const val CHANNEL = "${K.LIB_ID}/lock" + companion object { + const val CHANNEL = "${K.LIB_ID}/lock" - private val locks = mutableMapOf() + private val locks = mutableMapOf() - private const val TAG = "LockChannelHandler" - } + private const val TAG = "LockChannelHandler" + } - /** - * Dismiss this handler instance - * - * All dangling locks locked via this instance will automatically be - * unlocked - */ - fun dismiss() { - for (id in _lockedIds) { - if (locks[id] == true) { - logW(TAG, "[dismiss] Automatically unlocking id: $id") - locks[id] = false - } - } - _lockedIds.clear() - } + /** + * Dismiss this handler instance + * + * All dangling locks locked via this instance will automatically be + * unlocked + */ + fun dismiss() { + for (id in _lockedIds) { + if (locks[id] == true) { + logW(TAG, "[dismiss] Automatically unlocking id: $id") + locks[id] = false + } + } + _lockedIds.clear() + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "tryLock" -> { - try { - tryLock(call.argument("lockId")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "tryLock" -> { + try { + tryLock(call.argument("lockId")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "unlock" -> { - try { - unlock(call.argument("lockId")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "unlock" -> { + try { + unlock(call.argument("lockId")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> { - result.notImplemented() - } - } - } + else -> { + result.notImplemented() + } + } + } - private fun tryLock(lockId: Int, result: MethodChannel.Result) { - if (locks[lockId] != true) { - locks[lockId] = true - _lockedIds.add(lockId) - result.success(true) - } else { - result.success(false) - } - } + private fun tryLock(lockId: Int, result: MethodChannel.Result) { + if (locks[lockId] != true) { + locks[lockId] = true + _lockedIds.add(lockId) + result.success(true) + } else { + result.success(false) + } + } - private fun unlock(lockId: Int, result: MethodChannel.Result) { - if (locks[lockId] == true) { - locks[lockId] = false - _lockedIds.remove(lockId) - result.success(null) - } else { - result.error( - "notLockedException", - "Cannot unlock without first locking", - null - ) - } - } + private fun unlock(lockId: Int, result: MethodChannel.Result) { + if (locks[lockId] == true) { + locks[lockId] = false + _lockedIds.remove(lockId) + result.success(null) + } else { + result.error( + "notLockedException", "Cannot unlock without first locking", + null + ) + } + } - private val _lockedIds = mutableListOf() + private val _lockedIds = mutableListOf() } diff --git a/np_platform_log/android/src/main/kotlin/com/nkming/nc_photos/np_platform_log/K.kt b/np_platform_log/android/src/main/kotlin/com/nkming/nc_photos/np_platform_log/K.kt index 2a77cf8c..22185b88 100644 --- a/np_platform_log/android/src/main/kotlin/com/nkming/nc_photos/np_platform_log/K.kt +++ b/np_platform_log/android/src/main/kotlin/com/nkming/nc_photos/np_platform_log/K.kt @@ -1,7 +1,7 @@ package com.nkming.nc_photos.np_platform_log internal interface K { - companion object { - const val LIB_ID = "com.nkming.nc_photos.np_platform_log" - } + companion object { + const val LIB_ID = "com.nkming.nc_photos.np_platform_log" + } } diff --git a/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/K.kt b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/K.kt index 495e747a..1a0b4159 100644 --- a/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/K.kt +++ b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/K.kt @@ -1,7 +1,7 @@ package com.nkming.nc_photos.np_platform_permission internal interface K { - companion object { - const val LIB_ID = "com.nkming.nc_photos.np_platform_permission" - } + companion object { + const val LIB_ID = "com.nkming.nc_photos.np_platform_permission" + } } diff --git a/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/NpPlatformPermissionPlugin.kt b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/NpPlatformPermissionPlugin.kt index 11ad0ac4..27b3e05a 100644 --- a/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/NpPlatformPermissionPlugin.kt +++ b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/NpPlatformPermissionPlugin.kt @@ -11,83 +11,83 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.PluginRegistry class NpPlatformPermissionPlugin : FlutterPlugin, ActivityAware, - PluginRegistry.RequestPermissionsResultListener { - companion object { - private const val TAG = "NpPlatformPermissionPlugin" - } + PluginRegistry.RequestPermissionsResultListener { + companion object { + private const val TAG = "NpPlatformPermissionPlugin" + } - override fun onAttachedToEngine( - @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding - ) { - permissionChannelHandler = - PermissionChannelHandler(flutterPluginBinding.applicationContext) - permissionChannel = EventChannel( - flutterPluginBinding.binaryMessenger, - PermissionChannelHandler.EVENT_CHANNEL - ) - permissionChannel.setStreamHandler(permissionChannelHandler) - permissionMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - PermissionChannelHandler.METHOD_CHANNEL - ) - permissionMethodChannel.setMethodCallHandler(permissionChannelHandler) - } + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + permissionChannelHandler = + PermissionChannelHandler(flutterPluginBinding.applicationContext) + permissionChannel = EventChannel( + flutterPluginBinding.binaryMessenger, + PermissionChannelHandler.EVENT_CHANNEL + ) + permissionChannel.setStreamHandler(permissionChannelHandler) + permissionMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + PermissionChannelHandler.METHOD_CHANNEL + ) + permissionMethodChannel.setMethodCallHandler(permissionChannelHandler) + } - override fun onDetachedFromEngine( - @NonNull binding: FlutterPlugin.FlutterPluginBinding - ) { - permissionChannel.setStreamHandler(null) - permissionMethodChannel.setMethodCallHandler(null) - } + override fun onDetachedFromEngine( + @NonNull binding: FlutterPlugin.FlutterPluginBinding + ) { + permissionChannel.setStreamHandler(null) + permissionMethodChannel.setMethodCallHandler(null) + } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - permissionChannelHandler.onAttachedToActivity(binding) - pluginBinding = binding - binding.addRequestPermissionsResultListener(this) - } + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + permissionChannelHandler.onAttachedToActivity(binding) + pluginBinding = binding + binding.addRequestPermissionsResultListener(this) + } - override fun onReattachedToActivityForConfigChanges( - binding: ActivityPluginBinding - ) { - permissionChannelHandler.onReattachedToActivityForConfigChanges(binding) - pluginBinding = binding - binding.addRequestPermissionsResultListener(this) - } + override fun onReattachedToActivityForConfigChanges( + binding: ActivityPluginBinding + ) { + permissionChannelHandler.onReattachedToActivityForConfigChanges(binding) + pluginBinding = binding + binding.addRequestPermissionsResultListener(this) + } - override fun onDetachedFromActivity() { - permissionChannelHandler.onDetachedFromActivity() - pluginBinding?.removeRequestPermissionsResultListener(this) - } + override fun onDetachedFromActivity() { + permissionChannelHandler.onDetachedFromActivity() + pluginBinding?.removeRequestPermissionsResultListener(this) + } - override fun onDetachedFromActivityForConfigChanges() { - permissionChannelHandler.onDetachedFromActivityForConfigChanges() - pluginBinding?.removeRequestPermissionsResultListener(this) - } + override fun onDetachedFromActivityForConfigChanges() { + permissionChannelHandler.onDetachedFromActivityForConfigChanges() + pluginBinding?.removeRequestPermissionsResultListener(this) + } - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray - ): Boolean { - return try { - when (requestCode) { - PermissionUtil.REQUEST_CODE -> { - permissionChannelHandler.onRequestPermissionsResult( - requestCode, permissions, grantResults - ) - } + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, grantResults: IntArray + ): Boolean { + return try { + when (requestCode) { + PermissionUtil.REQUEST_CODE -> { + permissionChannelHandler.onRequestPermissionsResult( + requestCode, permissions, grantResults + ) + } - else -> false - } - } catch (e: Throwable) { - logE( - TAG, "Failed while onActivityResult, requestCode=$requestCode" - ) - false - } - } + else -> false + } + } catch (e: Throwable) { + logE( + TAG, "Failed while onActivityResult, requestCode=$requestCode" + ) + false + } + } - private var pluginBinding: ActivityPluginBinding? = null + private var pluginBinding: ActivityPluginBinding? = null - private lateinit var permissionChannel: EventChannel - private lateinit var permissionMethodChannel: MethodChannel - private lateinit var permissionChannelHandler: PermissionChannelHandler + private lateinit var permissionChannel: EventChannel + private lateinit var permissionMethodChannel: MethodChannel + private lateinit var permissionChannelHandler: PermissionChannelHandler } diff --git a/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/PermissionChannelHandler.kt b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/PermissionChannelHandler.kt index 5a916e68..ffbd0690 100644 --- a/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/PermissionChannelHandler.kt +++ b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/PermissionChannelHandler.kt @@ -11,104 +11,104 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.PluginRegistry internal class PermissionChannelHandler(context: Context) : - MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware, - PluginRegistry.RequestPermissionsResultListener { - companion object { - const val EVENT_CHANNEL = "${K.LIB_ID}/permission" - const val METHOD_CHANNEL = "${K.LIB_ID}/permission_method" + MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware, + PluginRegistry.RequestPermissionsResultListener { + companion object { + const val EVENT_CHANNEL = "${K.LIB_ID}/permission" + const val METHOD_CHANNEL = "${K.LIB_ID}/permission_method" - private const val TAG = "PermissionChannelHandler" - } + private const val TAG = "PermissionChannelHandler" + } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activity = binding.activity - } + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + } - override fun onReattachedToActivityForConfigChanges( - binding: ActivityPluginBinding - ) { - activity = binding.activity - } + override fun onReattachedToActivityForConfigChanges( + binding: ActivityPluginBinding + ) { + activity = binding.activity + } - override fun onDetachedFromActivity() { - activity = null - } + override fun onDetachedFromActivity() { + activity = null + } - override fun onDetachedFromActivityForConfigChanges() { - activity = null - } + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray - ): Boolean { - return if (requestCode == PermissionUtil.REQUEST_CODE) { - eventSink?.success(buildMap { - put("event", "RequestPermissionsResult") - put( - "grantResults", - permissions.zip(grantResults.toTypedArray()).toMap() - ) - }) - true - } else { - false - } - } + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, grantResults: IntArray + ): Boolean { + return if (requestCode == PermissionUtil.REQUEST_CODE) { + eventSink?.success(buildMap { + put("event", "RequestPermissionsResult") + put( + "grantResults", + permissions.zip(grantResults.toTypedArray()).toMap() + ) + }) + true + } else { + false + } + } - override fun onListen(arguments: Any?, events: EventChannel.EventSink) { - eventSink = events - } + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + eventSink = events + } - override fun onCancel(arguments: Any?) { - eventSink = null - } + override fun onCancel(arguments: Any?) { + eventSink = null + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "request" -> { - try { - request(call.argument("permissions")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "request" -> { + try { + request(call.argument("permissions")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "hasWriteExternalStorage" -> { - try { - result.success( - PermissionUtil.hasWriteExternalStorage(context) - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "hasWriteExternalStorage" -> { + try { + result.success( + PermissionUtil.hasWriteExternalStorage(context) + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "hasReadExternalStorage" -> { - try { - result.success( - PermissionUtil.hasReadExternalStorage(context) - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "hasReadExternalStorage" -> { + try { + result.success( + PermissionUtil.hasReadExternalStorage(context) + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - private fun request( - permissions: List, result: MethodChannel.Result - ) { - if (activity == null) { - result.error("systemException", "Activity is not ready", null) - return - } - PermissionUtil.request(activity!!, *permissions.toTypedArray()) - result.success(null) - } + private fun request( + permissions: List, result: MethodChannel.Result + ) { + if (activity == null) { + result.error("systemException", "Activity is not ready", null) + return + } + PermissionUtil.request(activity!!, *permissions.toTypedArray()) + result.success(null) + } - private val context = context - private var activity: Activity? = null - private var eventSink: EventChannel.EventSink? = null + private val context = context + private var activity: Activity? = null + private var eventSink: EventChannel.EventSink? = null } diff --git a/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/ImageLoaderChannelHandler.kt b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/ImageLoaderChannelHandler.kt index ad5e59e5..8cc91f08 100644 --- a/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/ImageLoaderChannelHandler.kt +++ b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/ImageLoaderChannelHandler.kt @@ -10,70 +10,58 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel internal class ImageLoaderChannelHandler(context: Context) : - MethodChannel.MethodCallHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/image_loader_method" - } + MethodChannel.MethodCallHandler { + companion object { + const val METHOD_CHANNEL = "${K.LIB_ID}/image_loader_method" + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "loadUri" -> { - try { - loadUri( - call.argument("fileUri")!!, - call.argument("maxWidth")!!, - call.argument("maxHeight")!!, - call.argument("resizeMethod")!!, - call.argument("isAllowSwapSide")!!, - call.argument("shouldUpscale")!!, - call.argument("shouldFixOrientation")!!, - result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "loadUri" -> { + try { + loadUri( + call.argument("fileUri")!!, call.argument("maxWidth")!!, + call.argument("maxHeight")!!, + call.argument("resizeMethod")!!, + call.argument("isAllowSwapSide")!!, + call.argument("shouldUpscale")!!, + call.argument("shouldFixOrientation")!!, result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - /** - * Load and resize an image pointed by a uri - * - * @param fileUri - * @param maxWidth - * @param maxHeight - * @param resizeMethod - * @param isAllowSwapSide - * @param shouldUpscale - * @param shouldFixOrientation - * @param result - */ - private fun loadUri( - fileUri: String, - maxWidth: Int, - maxHeight: Int, - resizeMethod: Int, - isAllowSwapSide: Boolean, - shouldUpscale: Boolean, - shouldFixOrientation: Boolean, - result: MethodChannel.Result - ) { - val image = BitmapUtil.loadImage( - context, - Uri.parse(fileUri), - maxWidth, - maxHeight, - BitmapResizeMethod.values()[resizeMethod], - isAllowSwapSide, - shouldUpscale, - shouldFixOrientation - ).use { - Rgba8Image.fromBitmap(it) - } - result.success(image.toJson()) - } + /** + * Load and resize an image pointed by a uri + * + * @param fileUri + * @param maxWidth + * @param maxHeight + * @param resizeMethod + * @param isAllowSwapSide + * @param shouldUpscale + * @param shouldFixOrientation + * @param result + */ + private fun loadUri( + fileUri: String, maxWidth: Int, maxHeight: Int, resizeMethod: Int, + isAllowSwapSide: Boolean, shouldUpscale: Boolean, + shouldFixOrientation: Boolean, result: MethodChannel.Result + ) { + val image = BitmapUtil.loadImage( + context, Uri.parse(fileUri), maxWidth, maxHeight, + BitmapResizeMethod.values()[resizeMethod], isAllowSwapSide, + shouldUpscale, shouldFixOrientation + ).use { + Rgba8Image.fromBitmap(it) + } + result.success(image.toJson()) + } - private val context = context + private val context = context } diff --git a/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/K.kt b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/K.kt index cb64b73f..c185a756 100644 --- a/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/K.kt +++ b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/K.kt @@ -1,7 +1,7 @@ package com.nkming.nc_photos.np_platform_raw_image internal interface K { - companion object { - const val LIB_ID = "com.nkming.nc_photos.np_platform_raw_image" - } + companion object { + const val LIB_ID = "com.nkming.nc_photos.np_platform_raw_image" + } } diff --git a/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/NpPlatformRawImagePlugin.kt b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/NpPlatformRawImagePlugin.kt index 330f1f3d..37dd21cd 100644 --- a/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/NpPlatformRawImagePlugin.kt +++ b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/NpPlatformRawImagePlugin.kt @@ -5,19 +5,23 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodChannel class NpPlatformRawImagePlugin : FlutterPlugin { - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - val imageLoaderChannelHandler = - ImageLoaderChannelHandler(flutterPluginBinding.applicationContext) - imageLoaderMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - ImageLoaderChannelHandler.METHOD_CHANNEL - ) - imageLoaderMethodChannel.setMethodCallHandler(imageLoaderChannelHandler) - } + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + val imageLoaderChannelHandler = + ImageLoaderChannelHandler(flutterPluginBinding.applicationContext) + imageLoaderMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + ImageLoaderChannelHandler.METHOD_CHANNEL + ) + imageLoaderMethodChannel.setMethodCallHandler(imageLoaderChannelHandler) + } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - imageLoaderMethodChannel.setMethodCallHandler(null) - } + override fun onDetachedFromEngine( + @NonNull binding: FlutterPlugin.FlutterPluginBinding + ) { + imageLoaderMethodChannel.setMethodCallHandler(null) + } - private lateinit var imageLoaderMethodChannel: MethodChannel + private lateinit var imageLoaderMethodChannel: MethodChannel } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ContentUriChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ContentUriChannelHandler.kt index bbc5303b..1f76a097 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ContentUriChannelHandler.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ContentUriChannelHandler.kt @@ -11,65 +11,65 @@ import java.io.File import java.io.FileNotFoundException internal class ContentUriChannelHandler(context: Context) : - MethodChannel.MethodCallHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/content_uri_method" + MethodChannel.MethodCallHandler { + companion object { + const val METHOD_CHANNEL = "${K.LIB_ID}/content_uri_method" - private const val TAG = "ContentUriChannelHandler" - } + private const val TAG = "ContentUriChannelHandler" + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "readUri" -> { - try { - readUri(call.argument("uri")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "readUri" -> { + try { + readUri(call.argument("uri")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "getUriForFile" -> { - try { - getUriForFile(call.argument("filePath")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "getUriForFile" -> { + try { + getUriForFile(call.argument("filePath")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - private fun readUri(uri: String, result: MethodChannel.Result) { - val uriTyped = Uri.parse(uri) - try { - val bytes = if (UriUtil.isAssetUri(uriTyped)) { - context.assets.open(UriUtil.getAssetUriPath(uriTyped)).use { - it.readBytes() - } - } else { - context.contentResolver.openInputStream(uriTyped)!!.use { - it.readBytes() - } - } - result.success(bytes) - } catch (e: FileNotFoundException) { - result.error("fileNotFoundException", e.toString(), null) - } - } + private fun readUri(uri: String, result: MethodChannel.Result) { + val uriTyped = Uri.parse(uri) + try { + val bytes = if (UriUtil.isAssetUri(uriTyped)) { + context.assets.open(UriUtil.getAssetUriPath(uriTyped)).use { + it.readBytes() + } + } else { + context.contentResolver.openInputStream(uriTyped)!!.use { + it.readBytes() + } + } + result.success(bytes) + } catch (e: FileNotFoundException) { + result.error("fileNotFoundException", e.toString(), null) + } + } - private fun getUriForFile(filePath: String, result: MethodChannel.Result) { - try { - val file = File(filePath) - val contentUri = FileProvider.getUriForFile( - context, "${context.packageName}.fileprovider", file - ) - result.success(contentUri.toString()) - } catch (e: IllegalArgumentException) { - logE(TAG, "[getUriForFile] Unsupported file path: $filePath") - throw e - } - } + private fun getUriForFile(filePath: String, result: MethodChannel.Result) { + try { + val file = File(filePath) + val contentUri = FileProvider.getUriForFile( + context, "${context.packageName}.fileprovider", file + ) + result.success(contentUri.toString()) + } catch (e: IllegalArgumentException) { + logE(TAG, "[getUriForFile] Unsupported file path: $filePath") + throw e + } + } - private val context = context + private val context = context } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/K.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/K.kt index a0801db8..ee49896a 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/K.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/K.kt @@ -1,16 +1,16 @@ package com.nkming.nc_photos.plugin internal interface K { - companion object { - const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000 - const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000 + companion object { + const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000 + const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000 - const val MEDIA_STORE_DELETE_REQUEST_CODE = 11012 + const val MEDIA_STORE_DELETE_REQUEST_CODE = 11012 - const val LIB_ID = "com.nkming.nc_photos.plugin" + const val LIB_ID = "com.nkming.nc_photos.plugin" - const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL" + const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL" - const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID" - } + const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID" + } } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreChannelHandler.kt index e8b3c7f7..cd82ffd1 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreChannelHandler.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreChannelHandler.kt @@ -10,7 +10,6 @@ import android.provider.MediaStore import com.nkming.nc_photos.np_android_core.MediaStoreUtil import com.nkming.nc_photos.np_android_core.PermissionException import com.nkming.nc_photos.np_android_core.PermissionUtil -import com.nkming.nc_photos.np_android_core.logD import com.nkming.nc_photos.np_android_core.logE import com.nkming.nc_photos.np_android_core.logI import io.flutter.embedding.engine.plugins.activity.ActivityAware @@ -33,252 +32,250 @@ import java.io.File * fun queryFiles(relativePath: String): List */ internal class MediaStoreChannelHandler(context: Context) : - MethodChannel.MethodCallHandler, EventChannel.StreamHandler, - ActivityAware, PluginRegistry.ActivityResultListener { - companion object { - const val EVENT_CHANNEL = "${K.LIB_ID}/media_store" - const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method" + MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware, + PluginRegistry.ActivityResultListener { + companion object { + const val EVENT_CHANNEL = "${K.LIB_ID}/media_store" + const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method" - private const val TAG = "MediaStoreChannelHandler" - } + private const val TAG = "MediaStoreChannelHandler" + } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activity = binding.activity - } + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + } - override fun onReattachedToActivityForConfigChanges( - binding: ActivityPluginBinding - ) { - activity = binding.activity - } + override fun onReattachedToActivityForConfigChanges( + binding: ActivityPluginBinding + ) { + activity = binding.activity + } - override fun onDetachedFromActivity() { - activity = null - } + override fun onDetachedFromActivity() { + activity = null + } - override fun onDetachedFromActivityForConfigChanges() { - activity = null - } + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } - override fun onActivityResult( - requestCode: Int, resultCode: Int, data: Intent? - ): Boolean { - if (requestCode == K.MEDIA_STORE_DELETE_REQUEST_CODE) { - eventSink?.success(buildMap { - put("event", "DeleteRequestResult") - put("resultCode", resultCode) - }) - return true - } - return false - } + override fun onActivityResult( + requestCode: Int, resultCode: Int, data: Intent? + ): Boolean { + if (requestCode == K.MEDIA_STORE_DELETE_REQUEST_CODE) { + eventSink?.success(buildMap { + put("event", "DeleteRequestResult") + put("resultCode", resultCode) + }) + return true + } + return false + } - override fun onListen(arguments: Any?, events: EventChannel.EventSink) { - eventSink = events - } + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + eventSink = events + } - override fun onCancel(arguments: Any?) { - eventSink = null - } + override fun onCancel(arguments: Any?) { + eventSink = null + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "saveFileToDownload" -> { - try { - saveFileToDownload( - call.argument("content")!!, call.argument("filename")!!, - call.argument("subDir"), result - ) - } catch (e: Throwable) { - result.error("systemException", e.message, null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "saveFileToDownload" -> { + try { + saveFileToDownload( + call.argument("content")!!, call.argument("filename")!!, + call.argument("subDir"), result + ) + } catch (e: Throwable) { + result.error("systemException", e.message, null) + } + } - "copyFileToDownload" -> { - try { - copyFileToDownload( - call.argument("fromFile")!!, call.argument("filename"), - call.argument("subDir"), result - ) - } catch (e: Throwable) { - result.error("systemException", e.message, null) - } - } + "copyFileToDownload" -> { + try { + copyFileToDownload( + call.argument("fromFile")!!, call.argument("filename"), + call.argument("subDir"), result + ) + } catch (e: Throwable) { + result.error("systemException", e.message, null) + } + } - "queryFiles" -> { - try { - queryFiles(call.argument("relativePath")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.message, null) - } - } + "queryFiles" -> { + try { + queryFiles(call.argument("relativePath")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.message, null) + } + } - "deleteFiles" -> { - try { - deleteFiles(call.argument("uris")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.message, null) - } - } + "deleteFiles" -> { + try { + deleteFiles(call.argument("uris")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.message, null) + } + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - private fun saveFileToDownload( - content: ByteArray, filename: String, subDir: String?, - result: MethodChannel.Result - ) { - try { - val uri = MediaStoreUtil.saveFileToDownload( - context, content, filename, subDir - ) - result.success(uri.toString()) - } catch (e: PermissionException) { - activity?.let { PermissionUtil.requestWriteExternalStorage(it) } - result.error("permissionError", "Permission not granted", null) - } - } + private fun saveFileToDownload( + content: ByteArray, filename: String, subDir: String?, + result: MethodChannel.Result + ) { + try { + val uri = MediaStoreUtil.saveFileToDownload( + context, content, filename, subDir + ) + result.success(uri.toString()) + } catch (e: PermissionException) { + activity?.let { PermissionUtil.requestWriteExternalStorage(it) } + result.error("permissionError", "Permission not granted", null) + } + } - private fun copyFileToDownload( - fromFile: String, filename: String?, subDir: String?, - result: MethodChannel.Result - ) { - try { - val fromUri = inputToUri(fromFile) - val uri = MediaStoreUtil.copyFileToDownload( - context, fromUri, filename, subDir - ) - result.success(uri.toString()) - } catch (e: PermissionException) { - activity?.let { PermissionUtil.requestWriteExternalStorage(it) } - result.error("permissionError", "Permission not granted", null) - } - } + private fun copyFileToDownload( + fromFile: String, filename: String?, subDir: String?, + result: MethodChannel.Result + ) { + try { + val fromUri = inputToUri(fromFile) + val uri = MediaStoreUtil.copyFileToDownload( + context, fromUri, filename, subDir + ) + result.success(uri.toString()) + } catch (e: PermissionException) { + activity?.let { PermissionUtil.requestWriteExternalStorage(it) } + result.error("permissionError", "Permission not granted", null) + } + } - private fun queryFiles(relativePath: String, result: MethodChannel.Result) { - if (!PermissionUtil.hasReadExternalStorage(context)) { - activity?.let { PermissionUtil.requestReadExternalStorage(it) } - result.error("permissionError", "Permission not granted", null) - return - } + private fun queryFiles(relativePath: String, result: MethodChannel.Result) { + if (!PermissionUtil.hasReadExternalStorage(context)) { + activity?.let { PermissionUtil.requestReadExternalStorage(it) } + result.error("permissionError", "Permission not granted", null) + return + } - val pathColumnName: String - val pathArg: String - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - pathColumnName = MediaStore.Images.Media.RELATIVE_PATH - pathArg = "${relativePath}/%" - } else { - @Suppress("Deprecation") - pathColumnName = MediaStore.Images.Media.DATA - pathArg = "%/${relativePath}/%" - } - val projection = arrayOf( - MediaStore.Images.Media._ID, - MediaStore.Images.Media.DATE_MODIFIED, - MediaStore.Images.Media.MIME_TYPE, - MediaStore.Images.Media.DATE_TAKEN, - MediaStore.Images.Media.DISPLAY_NAME, - pathColumnName - ) - val selection = StringBuilder().apply { - append("${MediaStore.Images.Media.MIME_TYPE} LIKE ?") - append("AND $pathColumnName LIKE ?") - }.toString() - val selectionArgs = arrayOf("image/%", pathArg) - val files = context.contentResolver.query( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - projection, selection, selectionArgs, null - )!!.use { - val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID) - val dateModifiedColumn = - it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED) - val mimeTypeColumn = - it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE) - val dateTakenColumn = - it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN) - val displayNameColumn = - it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME) - val pathColumn = it.getColumnIndexOrThrow(pathColumnName) - val products = mutableListOf>() - while (it.moveToNext()) { - val id = it.getLong(idColumn) - val dateModified = it.getLong(dateModifiedColumn) - val mimeType = it.getString(mimeTypeColumn) - val dateTaken = it.getLong(dateTakenColumn) - val displayName = it.getString(displayNameColumn) - val path = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // RELATIVE_PATH - "${it.getString(pathColumn).trimEnd('/')}/$displayName" - } else { - // DATA - it.getString(pathColumn) - } - val contentUri = ContentUris.withAppendedId( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id - ) - products.add(buildMap { - put("uri", contentUri.toString()) - put("displayName", displayName) - put("path", path) - put("dateModified", dateModified * 1000) - put("mimeType", mimeType) - if (dateTaken != 0L) put("dateTaken", dateTaken) - }) - logD( - TAG, - "[queryEnhancedPhotos] Found $displayName, path=$path, uri=$contentUri" - ) - } - products - } - logI(TAG, "[queryEnhancedPhotos] Found ${files.size} files") - result.success(files) - } + val pathColumnName: String + val pathArg: String + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + pathColumnName = MediaStore.Images.Media.RELATIVE_PATH + pathArg = "${relativePath}/%" + } else { + @Suppress("Deprecation") pathColumnName = + MediaStore.Images.Media.DATA + pathArg = "%/${relativePath}/%" + } + val projection = arrayOf( + MediaStore.Images.Media._ID, MediaStore.Images.Media.DATE_MODIFIED, + MediaStore.Images.Media.MIME_TYPE, + MediaStore.Images.Media.DATE_TAKEN, + MediaStore.Images.Media.DISPLAY_NAME, pathColumnName + ) + val selection = StringBuilder().apply { + append("${MediaStore.Images.Media.MIME_TYPE} LIKE ?") + append("AND $pathColumnName LIKE ?") + }.toString() + val selectionArgs = arrayOf("image/%", pathArg) + val files = context.contentResolver.query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, + selectionArgs, null + )!!.use { + val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID) + val dateModifiedColumn = + it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED) + val mimeTypeColumn = + it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE) + val dateTakenColumn = + it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN) + val displayNameColumn = + it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME) + val pathColumn = it.getColumnIndexOrThrow(pathColumnName) + val products = mutableListOf>() + while (it.moveToNext()) { + val id = it.getLong(idColumn) + val dateModified = it.getLong(dateModifiedColumn) + val mimeType = it.getString(mimeTypeColumn) + val dateTaken = it.getLong(dateTakenColumn) + val displayName = it.getString(displayNameColumn) + val path = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // RELATIVE_PATH + "${it.getString(pathColumn).trimEnd('/')}/$displayName" + } else { + // DATA + it.getString(pathColumn) + } + val contentUri = ContentUris.withAppendedId( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id + ) + products.add(buildMap { + put("uri", contentUri.toString()) + put("displayName", displayName) + put("path", path) + put("dateModified", dateModified * 1000) + put("mimeType", mimeType) + if (dateTaken != 0L) put("dateTaken", dateTaken) + }) + logD( + TAG, + "[queryEnhancedPhotos] Found $displayName, path=$path, uri=$contentUri" + ) + } + products + } + logI(TAG, "[queryEnhancedPhotos] Found ${files.size} files") + result.success(files) + } - private fun deleteFiles(uris: List, result: MethodChannel.Result) { - val urisTyped = uris.map(Uri::parse) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val pi = MediaStore.createDeleteRequest( - context.contentResolver, urisTyped - ) - activity!!.startIntentSenderForResult( - pi.intentSender, K.MEDIA_STORE_DELETE_REQUEST_CODE, null, 0, 0, - 0 - ) - result.success(null) - } else { - if (!PermissionUtil.hasWriteExternalStorage(context)) { - activity?.let { PermissionUtil.requestWriteExternalStorage(it) } - result.error("permissionError", "Permission not granted", null) - return - } + private fun deleteFiles(uris: List, result: MethodChannel.Result) { + val urisTyped = uris.map(Uri::parse) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val pi = MediaStore.createDeleteRequest( + context.contentResolver, urisTyped + ) + activity!!.startIntentSenderForResult( + pi.intentSender, K.MEDIA_STORE_DELETE_REQUEST_CODE, null, 0, 0, + 0 + ) + result.success(null) + } else { + if (!PermissionUtil.hasWriteExternalStorage(context)) { + activity?.let { PermissionUtil.requestWriteExternalStorage(it) } + result.error("permissionError", "Permission not granted", null) + return + } - val failed = mutableListOf() - for (uri in urisTyped) { - try { - context.contentResolver.delete(uri, null, null) - } catch (e: Throwable) { - logE(TAG, "[deleteFiles] Failed while delete", e) - failed += uri.toString() - } - } - result.success(failed) - } - } + val failed = mutableListOf() + for (uri in urisTyped) { + try { + context.contentResolver.delete(uri, null, null) + } catch (e: Throwable) { + logE(TAG, "[deleteFiles] Failed while delete", e) + failed += uri.toString() + } + } + result.success(failed) + } + } - private fun inputToUri(fromFile: String): Uri { - val testUri = Uri.parse(fromFile) - return if (testUri.scheme == null) { - // is a file path - Uri.fromFile(File(fromFile)) - } else { - // is a uri - Uri.parse(fromFile) - } - } + private fun inputToUri(fromFile: String): Uri { + val testUri = Uri.parse(fromFile) + return if (testUri.scheme == null) { + // is a file path + Uri.fromFile(File(fromFile)) + } else { + // is a uri + Uri.parse(fromFile) + } + } - private val context = context - private var activity: Activity? = null - private var eventSink: EventChannel.EventSink? = null + private val context = context + private var activity: Activity? = null + private var eventSink: EventChannel.EventSink? = null } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt index fac5edad..4d554ebf 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NcPhotosPlugin.kt @@ -11,117 +11,117 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.PluginRegistry class NcPhotosPlugin : FlutterPlugin, ActivityAware, - PluginRegistry.ActivityResultListener { - companion object { - const val ACTION_DOWNLOAD_CANCEL = K.ACTION_DOWNLOAD_CANCEL - const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID + PluginRegistry.ActivityResultListener { + companion object { + const val ACTION_DOWNLOAD_CANCEL = K.ACTION_DOWNLOAD_CANCEL + const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID - private const val TAG = "NcPhotosPlugin" - } + private const val TAG = "NcPhotosPlugin" + } - override fun onAttachedToEngine( - @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding - ) { - notificationChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - NotificationChannelHandler.CHANNEL - ) - notificationChannel.setMethodCallHandler( - NotificationChannelHandler( - flutterPluginBinding.applicationContext - ) - ) + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + notificationChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + NotificationChannelHandler.CHANNEL + ) + notificationChannel.setMethodCallHandler( + NotificationChannelHandler( + flutterPluginBinding.applicationContext + ) + ) - mediaStoreChannelHandler = - MediaStoreChannelHandler(flutterPluginBinding.applicationContext) - mediaStoreChannel = EventChannel( - flutterPluginBinding.binaryMessenger, - MediaStoreChannelHandler.EVENT_CHANNEL - ) - mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler) - mediaStoreMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - MediaStoreChannelHandler.METHOD_CHANNEL - ) - mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler) + mediaStoreChannelHandler = + MediaStoreChannelHandler(flutterPluginBinding.applicationContext) + mediaStoreChannel = EventChannel( + flutterPluginBinding.binaryMessenger, + MediaStoreChannelHandler.EVENT_CHANNEL + ) + mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler) + mediaStoreMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + MediaStoreChannelHandler.METHOD_CHANNEL + ) + mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler) - contentUriMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - ContentUriChannelHandler.METHOD_CHANNEL - ) - contentUriMethodChannel.setMethodCallHandler( - ContentUriChannelHandler(flutterPluginBinding.applicationContext) - ) + contentUriMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + ContentUriChannelHandler.METHOD_CHANNEL + ) + contentUriMethodChannel.setMethodCallHandler( + ContentUriChannelHandler(flutterPluginBinding.applicationContext) + ) - val preferenceChannelHandler = - PreferenceChannelHandler(flutterPluginBinding.applicationContext) - preferenceMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - PreferenceChannelHandler.METHOD_CHANNEL - ) - preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler) - } + val preferenceChannelHandler = + PreferenceChannelHandler(flutterPluginBinding.applicationContext) + preferenceMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + PreferenceChannelHandler.METHOD_CHANNEL + ) + preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler) + } - override fun onDetachedFromEngine( - @NonNull binding: FlutterPlugin.FlutterPluginBinding - ) { - notificationChannel.setMethodCallHandler(null) - mediaStoreChannel.setStreamHandler(null) - mediaStoreMethodChannel.setMethodCallHandler(null) - contentUriMethodChannel.setMethodCallHandler(null) - preferenceMethodChannel.setMethodCallHandler(null) - } + override fun onDetachedFromEngine( + @NonNull binding: FlutterPlugin.FlutterPluginBinding + ) { + notificationChannel.setMethodCallHandler(null) + mediaStoreChannel.setStreamHandler(null) + mediaStoreMethodChannel.setMethodCallHandler(null) + contentUriMethodChannel.setMethodCallHandler(null) + preferenceMethodChannel.setMethodCallHandler(null) + } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - mediaStoreChannelHandler.onAttachedToActivity(binding) - pluginBinding = binding - binding.addActivityResultListener(this) - } + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + mediaStoreChannelHandler.onAttachedToActivity(binding) + pluginBinding = binding + binding.addActivityResultListener(this) + } - override fun onReattachedToActivityForConfigChanges( - binding: ActivityPluginBinding - ) { - mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding) - pluginBinding = binding - binding.addActivityResultListener(this) - } + override fun onReattachedToActivityForConfigChanges( + binding: ActivityPluginBinding + ) { + mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding) + pluginBinding = binding + binding.addActivityResultListener(this) + } - override fun onDetachedFromActivity() { - mediaStoreChannelHandler.onDetachedFromActivity() - pluginBinding?.removeActivityResultListener(this) - } + override fun onDetachedFromActivity() { + mediaStoreChannelHandler.onDetachedFromActivity() + pluginBinding?.removeActivityResultListener(this) + } - override fun onDetachedFromActivityForConfigChanges() { - mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges() - pluginBinding?.removeActivityResultListener(this) - } + override fun onDetachedFromActivityForConfigChanges() { + mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges() + pluginBinding?.removeActivityResultListener(this) + } - override fun onActivityResult( - requestCode: Int, resultCode: Int, data: Intent? - ): Boolean { - return try { - when (requestCode) { - K.MEDIA_STORE_DELETE_REQUEST_CODE -> { - mediaStoreChannelHandler.onActivityResult( - requestCode, resultCode, data - ) - } + override fun onActivityResult( + requestCode: Int, resultCode: Int, data: Intent? + ): Boolean { + return try { + when (requestCode) { + K.MEDIA_STORE_DELETE_REQUEST_CODE -> { + mediaStoreChannelHandler.onActivityResult( + requestCode, resultCode, data + ) + } - else -> false - } - } catch (e: Throwable) { - logE(TAG, "Failed while onActivityResult, requestCode=$requestCode") - false - } - } + else -> false + } + } catch (e: Throwable) { + logE(TAG, "Failed while onActivityResult, requestCode=$requestCode") + false + } + } - private var pluginBinding: ActivityPluginBinding? = null + private var pluginBinding: ActivityPluginBinding? = null - private lateinit var notificationChannel: MethodChannel - private lateinit var mediaStoreChannel: EventChannel - private lateinit var mediaStoreMethodChannel: MethodChannel - private lateinit var contentUriMethodChannel: MethodChannel - private lateinit var preferenceMethodChannel: MethodChannel + private lateinit var notificationChannel: MethodChannel + private lateinit var mediaStoreChannel: EventChannel + private lateinit var mediaStoreMethodChannel: MethodChannel + private lateinit var contentUriMethodChannel: MethodChannel + private lateinit var preferenceMethodChannel: MethodChannel - private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler + private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NotificationChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NotificationChannelHandler.kt index 7d0ca71f..9390193d 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NotificationChannelHandler.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NotificationChannelHandler.kt @@ -26,334 +26,323 @@ import kotlin.math.max * mimeTypes: List): Unit */ internal class NotificationChannelHandler(context: Context) : - MethodChannel.MethodCallHandler { - companion object { - const val CHANNEL = "${K.LIB_ID}/notification" + MethodChannel.MethodCallHandler { + companion object { + const val CHANNEL = "${K.LIB_ID}/notification" - fun getNextNotificationId(): Int { - if (++notificationId >= K.DOWNLOAD_NOTIFICATION_ID_MAX) { - notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN - } - return notificationId - } + fun getNextNotificationId(): Int { + if (++notificationId >= K.DOWNLOAD_NOTIFICATION_ID_MAX) { + notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN + } + return notificationId + } - const val DOWNLOAD_CHANNEL_ID = "download" - private var notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN - } + const val DOWNLOAD_CHANNEL_ID = "download" + private var notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN + } - init { - createDownloadChannel(context) - } + init { + createDownloadChannel(context) + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "notifyDownloadSuccessful" -> { - try { - notifyDownloadSuccessful( - call.argument("fileUris")!!, - call.argument("mimeTypes")!!, - call.argument("notificationId"), - result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "notifyDownloadSuccessful" -> { + try { + notifyDownloadSuccessful( + call.argument("fileUris")!!, + call.argument("mimeTypes")!!, + call.argument("notificationId"), result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "notifyDownloadProgress" -> { - try { - notifyDownloadProgress( - call.argument("progress")!!, - call.argument("max")!!, - call.argument("currentItemTitle"), - call.argument("notificationId"), - result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "notifyDownloadProgress" -> { + try { + notifyDownloadProgress( + call.argument("progress")!!, call.argument("max")!!, + call.argument("currentItemTitle"), + call.argument("notificationId"), result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "notifyLogSaveSuccessful" -> { - try { - notifyLogSaveSuccessful( - call.argument("fileUri")!!, result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "notifyLogSaveSuccessful" -> { + try { + notifyLogSaveSuccessful( + call.argument("fileUri")!!, result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "dismiss" -> { - try { - dismiss(call.argument("notificationId")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "dismiss" -> { + try { + dismiss(call.argument("notificationId")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> { - result.notImplemented() - } - } - } + else -> { + result.notImplemented() + } + } + } - private fun notifyDownloadSuccessful( - fileUris: List, - mimeTypes: List, - notificationId: Int?, - result: MethodChannel.Result - ) { - assert(fileUris.isNotEmpty()) - assert(fileUris.size == mimeTypes.size) - val uris = fileUris.map { Uri.parse(it) } - val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID) - .setSmallIcon(R.drawable.baseline_download_white_18) - .setWhen(System.currentTimeMillis()) - .setPriority(NotificationCompat.PRIORITY_HIGH).setSound( - RingtoneManager.getDefaultUri( - RingtoneManager.TYPE_NOTIFICATION - ) - ).setOnlyAlertOnce(false).setAutoCancel(true).setLocalOnly(true) + private fun notifyDownloadSuccessful( + fileUris: List, mimeTypes: List, notificationId: Int?, + result: MethodChannel.Result + ) { + assert(fileUris.isNotEmpty()) + assert(fileUris.size == mimeTypes.size) + val uris = fileUris.map { Uri.parse(it) } + val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID) + .setSmallIcon(R.drawable.baseline_download_white_18) + .setWhen(System.currentTimeMillis()) + .setPriority(NotificationCompat.PRIORITY_HIGH).setSound( + RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_NOTIFICATION + ) + ).setOnlyAlertOnce(false).setAutoCancel(true).setLocalOnly(true) - if (uris.size == 1) { - builder.setContentTitle( - _context.getString( - R.string.download_successful_notification_title - ) - ).setContentText( - _context.getString( - R.string.download_successful_notification_text - ) - ) + if (uris.size == 1) { + builder.setContentTitle( + _context.getString( + R.string.download_successful_notification_title + ) + ).setContentText( + _context.getString( + R.string.download_successful_notification_text + ) + ) - val openIntent = Intent().apply { - action = Intent.ACTION_VIEW - setDataAndType(uris[0], mimeTypes[0]) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - } - val openPendingIntent = PendingIntent.getActivity( - _context, 0, openIntent, - PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() - ) - builder.setContentIntent(openPendingIntent) + val openIntent = Intent().apply { + action = Intent.ACTION_VIEW + setDataAndType(uris[0], mimeTypes[0]) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + val openPendingIntent = PendingIntent.getActivity( + _context, 0, openIntent, + PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() + ) + builder.setContentIntent(openPendingIntent) - // show preview if available - if (mimeTypes[0]?.startsWith("image/") == true) { - val preview = loadNotificationImage(uris[0]) - if (preview != null) { - builder.setStyle( - NotificationCompat.BigPictureStyle() - .bigPicture(loadNotificationImage(uris[0])) - ) - } - } - } else { - builder.setContentTitle( - _context.getString( - R.string.download_multiple_successful_notification_title, - fileUris.size - ) - ) - } + // show preview if available + if (mimeTypes[0]?.startsWith("image/") == true) { + val preview = loadNotificationImage(uris[0]) + if (preview != null) { + builder.setStyle( + NotificationCompat.BigPictureStyle() + .bigPicture(loadNotificationImage(uris[0])) + ) + } + } + } else { + builder.setContentTitle( + _context.getString( + R.string.download_multiple_successful_notification_title, + fileUris.size + ) + ) + } - val shareIntent = if (uris.size == 1) Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, uris[0]) - type = mimeTypes[0] ?: "*/*" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - } else Intent().apply { - action = Intent.ACTION_SEND_MULTIPLE - putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris)) - type = - if (mimeTypes.all { - it?.startsWith( - "image/" - ) == true - }) "image/*" else "*/*" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - } - val shareChooser = Intent.createChooser( - shareIntent, _context.getString( - R.string.download_successful_notification_action_share_chooser - ) - ) - val sharePendingIntent = PendingIntent.getActivity( - _context, 1, shareChooser, - PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() - ) - builder.addAction( - 0, _context.getString( - R.string.download_successful_notification_action_share - ), sharePendingIntent - ) + val shareIntent = if (uris.size == 1) Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, uris[0]) + type = mimeTypes[0] ?: "*/*" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } else Intent().apply { + action = Intent.ACTION_SEND_MULTIPLE + putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris)) + type = if (mimeTypes.all { + it?.startsWith( + "image/" + ) == true + }) "image/*" else "*/*" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + val shareChooser = Intent.createChooser( + shareIntent, _context.getString( + R.string.download_successful_notification_action_share_chooser + ) + ) + val sharePendingIntent = PendingIntent.getActivity( + _context, 1, shareChooser, + PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() + ) + builder.addAction( + 0, _context.getString( + R.string.download_successful_notification_action_share + ), sharePendingIntent + ) - val id = notificationId ?: getNextNotificationId() - with(NotificationManagerCompat.from(_context)) { - notify(id, builder.build()) - } - result.success(id) - } + val id = notificationId ?: getNextNotificationId() + with(NotificationManagerCompat.from(_context)) { + notify(id, builder.build()) + } + result.success(id) + } - private fun notifyDownloadProgress( - progress: Int, - max: Int, - currentItemTitle: String?, - notificationId: Int?, - result: MethodChannel.Result - ) { - val id = notificationId ?: getNextNotificationId() - val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setWhen(System.currentTimeMillis()) - .setPriority(NotificationCompat.PRIORITY_HIGH).setSound( - RingtoneManager.getDefaultUri( - RingtoneManager.TYPE_NOTIFICATION - ) - ).setOnlyAlertOnce(true).setAutoCancel(false).setLocalOnly(true) - .setProgress(max, progress, false).setContentText("$progress/$max") - if (currentItemTitle == null) { - builder.setContentTitle( - _context.getString( - R.string.download_progress_notification_untitled_text - ) - ) - } else { - builder.setContentTitle( - _context.getString( - R.string.download_progress_notification_text, - currentItemTitle - ) - ) - } + private fun notifyDownloadProgress( + progress: Int, max: Int, currentItemTitle: String?, + notificationId: Int?, result: MethodChannel.Result + ) { + val id = notificationId ?: getNextNotificationId() + val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setWhen(System.currentTimeMillis()) + .setPriority(NotificationCompat.PRIORITY_HIGH).setSound( + RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_NOTIFICATION + ) + ).setOnlyAlertOnce(true).setAutoCancel(false).setLocalOnly(true) + .setProgress(max, progress, false).setContentText("$progress/$max") + if (currentItemTitle == null) { + builder.setContentTitle( + _context.getString( + R.string.download_progress_notification_untitled_text + ) + ) + } else { + builder.setContentTitle( + _context.getString( + R.string.download_progress_notification_text, + currentItemTitle + ) + ) + } - val cancelIntent = Intent().apply { - `package` = _context.packageName - action = K.ACTION_DOWNLOAD_CANCEL - putExtra(K.EXTRA_NOTIFICATION_ID, id) - } - val cancelPendingIntent = PendingIntent.getBroadcast( - _context, 0, cancelIntent, - PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() - ) - builder.addAction( - 0, _context.getString(android.R.string.cancel), cancelPendingIntent - ) + val cancelIntent = Intent().apply { + `package` = _context.packageName + action = K.ACTION_DOWNLOAD_CANCEL + putExtra(K.EXTRA_NOTIFICATION_ID, id) + } + val cancelPendingIntent = PendingIntent.getBroadcast( + _context, 0, cancelIntent, + PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() + ) + builder.addAction( + 0, _context.getString(android.R.string.cancel), cancelPendingIntent + ) - with(NotificationManagerCompat.from(_context)) { - notify(id, builder.build()) - } - result.success(id) - } + with(NotificationManagerCompat.from(_context)) { + notify(id, builder.build()) + } + result.success(id) + } - private fun notifyLogSaveSuccessful( - fileUri: String, result: MethodChannel.Result - ) { - val uri = Uri.parse(fileUri) - val mimeType = "text/plain" - val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID) - .setSmallIcon(R.drawable.baseline_download_white_18) - .setWhen(System.currentTimeMillis()) - .setPriority(NotificationCompat.PRIORITY_HIGH).setSound( - RingtoneManager.getDefaultUri( - RingtoneManager.TYPE_NOTIFICATION - ) - ).setAutoCancel(true).setLocalOnly(true).setTicker( - _context.getString( - R.string.log_save_successful_notification_title - ) - ).setContentTitle( - _context.getString( - R.string.log_save_successful_notification_title - ) - ).setContentText( - _context.getString( - R.string.log_save_successful_notification_text - ) - ) + private fun notifyLogSaveSuccessful( + fileUri: String, result: MethodChannel.Result + ) { + val uri = Uri.parse(fileUri) + val mimeType = "text/plain" + val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID) + .setSmallIcon(R.drawable.baseline_download_white_18) + .setWhen(System.currentTimeMillis()) + .setPriority(NotificationCompat.PRIORITY_HIGH).setSound( + RingtoneManager.getDefaultUri( + RingtoneManager.TYPE_NOTIFICATION + ) + ).setAutoCancel(true).setLocalOnly(true).setTicker( + _context.getString( + R.string.log_save_successful_notification_title + ) + ).setContentTitle( + _context.getString( + R.string.log_save_successful_notification_title + ) + ).setContentText( + _context.getString( + R.string.log_save_successful_notification_text + ) + ) - val openIntent = Intent().apply { - action = Intent.ACTION_VIEW - setDataAndType(uri, mimeType) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - } - val openPendingIntent = PendingIntent.getActivity( - _context, 0, openIntent, - PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() - ) - builder.setContentIntent(openPendingIntent) + val openIntent = Intent().apply { + action = Intent.ACTION_VIEW + setDataAndType(uri, mimeType) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + val openPendingIntent = PendingIntent.getActivity( + _context, 0, openIntent, + PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable() + ) + builder.setContentIntent(openPendingIntent) - // can't add the share action here because android will share the URI as - // plain text instead of treating it as a text file... + // can't add the share action here because android will share the URI as + // plain text instead of treating it as a text file... - val id = getNextNotificationId() - with(NotificationManagerCompat.from(_context)) { - notify(id, builder.build()) - } - result.success(id) - } + val id = getNextNotificationId() + with(NotificationManagerCompat.from(_context)) { + notify(id, builder.build()) + } + result.success(id) + } - private fun dismiss(notificationId: Int, result: MethodChannel.Result) { - with(NotificationManagerCompat.from(_context)) { - cancel(notificationId) - } - result.success(null) - } + private fun dismiss(notificationId: Int, result: MethodChannel.Result) { + with(NotificationManagerCompat.from(_context)) { + cancel(notificationId) + } + result.success(null) + } - private fun loadNotificationImage(fileUri: Uri): Bitmap? { - try { - val resolver = _context.applicationContext.contentResolver - resolver.openFileDescriptor(fileUri, "r").use { pfd -> - val metaOpts = BitmapFactory.Options().apply { - inJustDecodeBounds = true - } - BitmapFactory.decodeFileDescriptor( - pfd!!.fileDescriptor, null, metaOpts - ) - val longSide = max(metaOpts.outWidth, metaOpts.outHeight) - val opts = BitmapFactory.Options().apply { - // just a preview in the panel, useless to be in high res - inSampleSize = longSide / 720 - } - return BitmapFactory.decodeFileDescriptor( - pfd.fileDescriptor, null, opts - ) - } - } catch (e: Throwable) { - logE( - "NotificationChannelHandler::loadNotificationImage", - "Failed generating preview image", - e - ) - return null - } - } + private fun loadNotificationImage(fileUri: Uri): Bitmap? { + try { + val resolver = _context.applicationContext.contentResolver + resolver.openFileDescriptor(fileUri, "r").use { pfd -> + val metaOpts = BitmapFactory.Options().apply { + inJustDecodeBounds = true + } + BitmapFactory.decodeFileDescriptor( + pfd!!.fileDescriptor, null, metaOpts + ) + val longSide = max(metaOpts.outWidth, metaOpts.outHeight) + val opts = BitmapFactory.Options().apply { + // just a preview in the panel, useless to be in high res + inSampleSize = longSide / 720 + } + return BitmapFactory.decodeFileDescriptor( + pfd.fileDescriptor, null, opts + ) + } + } catch (e: Throwable) { + logE( + "NotificationChannelHandler::loadNotificationImage", + "Failed generating preview image", e + ) + return null + } + } - private fun createDownloadChannel(context: Context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val name = context.getString( - R.string.download_notification_channel_name - ) - val descriptionStr = context.getString( - R.string.download_notification_channel_description - ) - val channel = NotificationChannel( - DOWNLOAD_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH - ).apply { - description = descriptionStr - } + private fun createDownloadChannel(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val name = context.getString( + R.string.download_notification_channel_name + ) + val descriptionStr = context.getString( + R.string.download_notification_channel_description + ) + val channel = NotificationChannel( + DOWNLOAD_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH + ).apply { + description = descriptionStr + } - val manager = - context.getSystemService( - Context.NOTIFICATION_SERVICE - ) as NotificationManager - manager.createNotificationChannel(channel) - } - } + val manager = context.getSystemService( + Context.NOTIFICATION_SERVICE + ) as NotificationManager + manager.createNotificationChannel(channel) + } + } - private val _context = context + private val _context = context } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PreferenceChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PreferenceChannelHandler.kt index 39fd708a..b6c79a73 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PreferenceChannelHandler.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PreferenceChannelHandler.kt @@ -6,76 +6,68 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel internal class PreferenceChannelHandler(context: Context) : - MethodChannel.MethodCallHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/preference_method" - } + MethodChannel.MethodCallHandler { + companion object { + const val METHOD_CHANNEL = "${K.LIB_ID}/preference_method" + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "setBool" -> { - try { - setBool( - call.argument("prefName")!!, - call.argument("key")!!, - call.argument("value")!!, - result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "setBool" -> { + try { + setBool( + call.argument("prefName")!!, call.argument("key")!!, + call.argument("value")!!, result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "getBool" -> { - try { - getBool( - call.argument("prefName")!!, - call.argument("key")!!, - call.argument("defValue"), - result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "getBool" -> { + try { + getBool( + call.argument("prefName")!!, call.argument("key")!!, + call.argument("defValue"), result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - private fun setBool( - prefName: String, - key: String, - value: Boolean, - result: MethodChannel.Result - ) { - openPref(prefName).run { - edit().run { - putBoolean(key, value) - }.apply() - } - result.success(null) - } + private fun setBool( + prefName: String, key: String, value: Boolean, + result: MethodChannel.Result + ) { + openPref(prefName).run { + edit().run { + putBoolean(key, value) + }.apply() + } + result.success(null) + } - private fun getBool( - prefName: String, - key: String, - defValue: Boolean?, - result: MethodChannel.Result - ) { - val product = openPref(prefName).run { - if (contains(key)) { - getBoolean(key, false) - } else { - defValue - } - } - result.success(product) - } + private fun getBool( + prefName: String, key: String, defValue: Boolean?, + result: MethodChannel.Result + ) { + val product = openPref(prefName).run { + if (contains(key)) { + getBoolean(key, false) + } else { + defValue + } + } + result.success(product) + } - private fun openPref(prefName: String): SharedPreferences { - return context.getSharedPreferences(prefName, Context.MODE_PRIVATE) - } + private fun openPref(prefName: String): SharedPreferences { + return context.getSharedPreferences(prefName, Context.MODE_PRIVATE) + } - private val context = context + private val context = context }