From dbea0e0a55312da89b4693dd50377691b58706a0 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Fri, 1 Sep 2023 02:34:48 +0800 Subject: [PATCH] Refactor: extract image processor --- app/android/app/build.gradle | 2 +- .../main/kotlin/com/nkming/nc_photos/App.kt | 14 +- .../nc_photos/CustomHostnameVerifier.kt | 64 +- .../nc_photos/CustomKeyStoresTrustManager.kt | 142 +-- .../nc_photos/CustomSSLSocketFactory.kt | 154 +-- .../nc_photos/DownloadEventChannelHandler.kt | 62 +- .../src/main/kotlin/com/nkming/nc_photos/K.kt | 6 - .../com/nkming/nc_photos/MainActivity.kt | 210 ++-- .../nc_photos/SelfSignedCertChannelHandler.kt | 2 +- .../nkming/nc_photos/SelfSignedCertManager.kt | 138 +-- .../nkming/nc_photos/ShareChannelHandler.kt | 238 ++--- app/android/settings.gradle | 2 +- app/lib/mobile/android/permission_util.dart | 2 +- app/lib/widget/enhanced_photo_browser.dart | 1 + .../widget/handler/permission_handler.dart | 2 +- app/lib/widget/image_editor.dart | 3 +- .../widget/image_editor/color_toolbar.dart | 2 +- .../widget/image_editor/crop_controller.dart | 3 +- .../image_editor/transform_toolbar.dart | 2 +- app/lib/widget/image_enhancer.dart | 2 +- .../widget/settings/developer_settings.dart | 1 - app/pubspec.lock | 21 + app/pubspec.yaml | 6 + .../.gitignore | 0 .../build.gradle | 7 +- .../gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 np_android_core/settings.gradle | 1 + .../src/main/AndroidManifest.xml | 0 .../nc_photos/np_android_core/BitmapUtil.kt | 244 +++++ .../nc_photos/np_android_core/Exception.kt | 3 + .../com/nkming/nc_photos/np_android_core/K.kt | 7 + .../nkming/nc_photos/np_android_core}/Log.kt | 2 +- .../np_android_core}/MediaStoreUtil.kt | 2 +- .../np_android_core/PermissionUtil.kt | 42 + .../nc_photos/np_android_core/Rgba8Image.kt | 44 + .../nc_photos/np_android_core}/UriUtil.kt | 3 +- .../nkming/nc_photos/np_android_core}/Util.kt | 3 +- np_android_log/consumer-rules.pro | 0 np_android_log/proguard-rules.pro | 21 - np_android_log/settings.gradle | 1 - np_platform_image_processor/.gitignore | 30 + np_platform_image_processor/.metadata | 33 + .../analysis_options.yaml | 1 + .../android/.gitignore | 9 + .../android/build.gradle | 76 ++ .../headers/RenderScriptToolkit.h | 0 .../jni/arm64-v8a/librenderscript-toolkit.so | Bin .../armeabi-v7a/librenderscript-toolkit.so | Bin .../jni/x86_64/librenderscript-toolkit.so | Bin .../headers/tensorflow/lite/builtin_ops.h | 0 .../headers/tensorflow/lite/c/c_api.h | 0 .../tensorflow/lite/c/c_api_experimental.h | 0 .../headers/tensorflow/lite/c/c_api_types.h | 0 .../headers/tensorflow/lite/c/common.h | 0 .../jni/arm64-v8a/libtensorflowlite_jni.so | Bin .../jni/armeabi-v7a/libtensorflowlite_jni.so | Bin .../jni/x86_64/libtensorflowlite_jni.so | Bin .../android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../android/settings.gradle | 2 + .../android/src/main/AndroidManifest.xml | 11 + ...e-transfer-inceptionv3_dr_predict_1.tflite | Bin ...-transfer-inceptionv3_dr_transfer_1.tflite | Bin .../assets/tf/arbitrary-style-transfer/1.jpg | Bin .../assets/tf/arbitrary-style-transfer/2.jpg | Bin .../assets/tf/arbitrary-style-transfer/3.jpg | Bin .../assets/tf/arbitrary-style-transfer/4.jpg | Bin .../assets/tf/arbitrary-style-transfer/5.jpg | Bin .../assets/tf/arbitrary-style-transfer/6.jpg | Bin .../src/main/assets/tf/esrgan-tf2_1-dr.tflite | Bin ...te-model_mobilenetv2-dm05-coco_dr_1.tflite | Bin .../main/assets/tf/neurop_fivek_lite.tflite | Bin .../tf/zero_dce_lite_200x300_iter8_60.tflite | Bin .../android/src/main/cpp/CMakeLists.txt | 10 +- .../src/main/cpp/arbitrary_style_transfer.cpp | 4 +- .../src/main/cpp/arbitrary_style_transfer.h | 2 +- .../src/main/cpp/core/filter/brightness.cpp | 0 .../src/main/cpp/core/filter/color_levels.cpp | 0 .../src/main/cpp/core/filter/contrast.cpp | 0 .../src/main/cpp/core/filter/curve.cpp | 0 .../android/src/main/cpp/core/filter/curve.h | 0 .../src/main/cpp/core/filter/filters.h | 0 .../src/main/cpp/core/filter/hslhsv.cpp | 0 .../android/src/main/cpp/core/filter/hslhsv.h | 0 .../src/main/cpp/core/filter/saturation.cpp | 0 .../src/main/cpp/core/filter/saturation.h | 0 .../android/src/main/cpp/core/filter/tint.cpp | 0 .../src/main/cpp/core/filter/warmth.cpp | 0 .../android/src/main/cpp/core/filter/yuv.cpp | 0 .../android/src/main/cpp/core/filter/yuv.h | 0 .../src/main/cpp/core/lib/base_resample.h | 0 .../src/main/cpp/core/lib/spline/LICENSE | 0 .../src/main/cpp/core/lib/spline/spline.cpp | 0 .../src/main/cpp/core/lib/spline/spline.h | 0 .../android/src/main/cpp/core/log.h | 0 .../android/src/main/cpp/core/math_util.h | 0 .../android/src/main/cpp/deep_lap_3.cpp | 6 +- .../android/src/main/cpp/deep_lap_3.h | 4 +- .../android/src/main/cpp/esrgan.cpp | 4 +- .../android/src/main/cpp/esrgan.h | 2 +- .../android/src/main/cpp/exception.cpp | 0 .../android/src/main/cpp/exception.h | 0 .../src/main/cpp/filter/brightness.cpp | 4 +- .../src/main/cpp/filter/color_levels.cpp | 6 +- .../android/src/main/cpp/filter/contrast.cpp | 4 +- .../android/src/main/cpp/filter/crop.cpp | 4 +- .../src/main/cpp/filter/orientation.cpp | 4 +- .../src/main/cpp/filter/saturation.cpp | 4 +- .../android/src/main/cpp/filter/tint.cpp | 4 +- .../android/src/main/cpp/filter/warmth.cpp | 4 +- .../android/src/main/cpp/image_splitter.cpp | 4 +- .../android/src/main/cpp/image_splitter.h | 4 +- .../android/src/main/cpp/log.h | 0 .../android/src/main/cpp/math_util.h | 4 +- .../android/src/main/cpp/neur_op.cpp | 4 +- .../android/src/main/cpp/neur_op.h | 2 +- .../android/src/main/cpp/stopwatch.cpp | 0 .../android/src/main/cpp/stopwatch.h | 0 .../android/src/main/cpp/tflite_wrapper.cpp | 2 +- .../android/src/main/cpp/tflite_wrapper.h | 4 +- .../android/src/main/cpp/util.cpp | 6 +- .../android/src/main/cpp/util.h | 4 +- .../android/src/main/cpp/zero_dce.cpp | 4 +- .../android/src/main/cpp/zero_dce.h | 2 +- .../np_platform_image_processor/Event.kt | 13 + .../np_platform_image_processor/Exception.kt | 6 + .../ImageProcessorChannelHandler.kt | 406 ++++++++ .../ImageProcessorService.kt | 983 +++++++++++++++++ .../np_platform_image_processor/K.kt | 16 + .../NativeEvent.kt | 15 + .../NativeEventChannelHandler.kt | 72 ++ .../NpPlatformImageProcessorPlugin.kt | 57 + .../processor/ArbitraryStyleTransfer.kt | 88 ++ .../processor/BlackPoint.kt | 16 + .../processor/Brightness.kt | 16 + .../processor/Contrast.kt | 16 + .../processor/Cool.kt | 16 + .../processor/Crop.kt | 31 + .../processor/DeepLab3.kt | 94 ++ .../processor/Esrgan.kt | 43 + .../processor/ImageFilterProcessor.kt | 43 + .../processor/LosslessRotator.kt | 82 ++ .../processor/NeurOp.kt | 43 + .../processor/Orientation.kt | 20 + .../processor/Saturation.kt | 16 + .../processor/TfLiteHelper.kt | 84 ++ .../processor/Tint.kt | 16 + .../processor/Warmth.kt | 16 + .../processor/WhitePoint.kt | 16 + .../processor/ZeroDce.kt | 53 + .../outline_auto_fix_high_white_24.png | Bin .../outline_error_outline_white_24.png | Bin .../drawable-hdpi/outline_image_white_24.png | Bin .../outline_auto_fix_high_white_24.png | Bin .../outline_error_outline_white_24.png | Bin .../drawable-mdpi/outline_image_white_24.png | Bin .../outline_auto_fix_high_white_24.png | Bin .../outline_error_outline_white_24.png | Bin .../drawable-xhdpi/outline_image_white_24.png | Bin .../outline_auto_fix_high_white_24.png | Bin .../outline_error_outline_white_24.png | Bin .../outline_image_white_24.png | Bin .../outline_auto_fix_high_white_24.png | Bin .../outline_error_outline_white_24.png | Bin .../outline_image_white_24.png | Bin .../lib/np_platform_image_processor.dart | 3 + .../lib/src/image_processor.dart | 10 +- np_platform_image_processor/lib/src/k.dart | 1 + np_platform_image_processor/pubspec.yaml | 27 + np_platform_lock/android/build.gradle | 2 +- np_platform_lock/android/settings.gradle | 2 +- .../np_platform_lock/LockChannelHandler.kt | 132 +-- np_platform_permission/.gitignore | 30 + np_platform_permission/.metadata | 30 + np_platform_permission/analysis_options.yaml | 1 + np_platform_permission/android/.gitignore | 9 + np_platform_permission/android/build.gradle | 54 + .../android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + np_platform_permission/android/gradlew | 240 +++++ np_platform_permission/android/gradlew.bat | 91 ++ .../android/settings.gradle | 2 + .../android/src/main/AndroidManifest.xml | 3 + .../nc_photos/np_platform_permission/K.kt | 7 + .../NpPlatformPermissionPlugin.kt | 93 ++ .../PermissionChannelHandler.kt | 114 ++ .../lib/np_platform_permission.dart | 3 + np_platform_permission/lib/src/k.dart | 1 + .../lib/src/permission.dart | 2 +- np_platform_permission/pubspec.yaml | 25 + np_platform_raw_image/.gitignore | 30 + np_platform_raw_image/.metadata | 30 + np_platform_raw_image/analysis_options.yaml | 1 + np_platform_raw_image/android/.gitignore | 9 + np_platform_raw_image/android/build.gradle | 53 + .../gradle/wrapper/gradle-wrapper.properties | 5 + np_platform_raw_image/android/gradlew | 240 +++++ np_platform_raw_image/android/gradlew.bat | 91 ++ np_platform_raw_image/android/settings.gradle | 2 + .../android/src/main/AndroidManifest.xml | 3 + .../ImageLoaderChannelHandler.kt | 79 ++ .../nc_photos/np_platform_raw_image/K.kt | 7 + .../NpPlatformRawImagePlugin.kt | 23 + .../lib/np_platform_raw_image.dart | 4 + .../lib/src/image_loader.dart | 4 +- np_platform_raw_image/lib/src/k.dart | 1 + .../lib/src/rgba8_image.dart | 0 np_platform_raw_image/pubspec.yaml | 24 + plugin/android/build.gradle | 14 +- plugin/android/settings.gradle | 2 +- plugin/android/src/main/AndroidManifest.xml | 8 - .../com/nkming/nc_photos/plugin/BitmapUtil.kt | 255 ----- .../plugin/ContentUriChannelHandler.kt | 113 +- .../com/nkming/nc_photos/plugin/Event.kt | 13 - .../com/nkming/nc_photos/plugin/Exception.kt | 7 - .../plugin/ImageLoaderChannelHandler.kt | 67 -- .../plugin/ImageProcessorChannelHandler.kt | 319 ------ .../nc_photos/plugin/ImageProcessorService.kt | 986 ------------------ .../kotlin/com/nkming/nc_photos/plugin/K.kt | 25 +- .../nc_photos/plugin/LogcatChannelHandler.kt | 50 +- .../plugin/MediaStoreChannelHandler.kt | 464 +++++---- .../nkming/nc_photos/plugin/NativeEvent.kt | 14 - .../plugin/NativeEventChannelHandler.kt | 72 -- .../nkming/nc_photos/plugin/NcPhotosPlugin.kt | 309 ++---- .../plugin/NotificationChannelHandler.kt | 617 +++++------ .../plugin/PermissionChannelHandler.kt | 113 -- .../nkming/nc_photos/plugin/PermissionUtil.kt | 36 - .../plugin/PreferenceChannelHandler.kt | 128 +-- .../image_processor/ArbitraryStyleTransfer.kt | 71 -- .../plugin/image_processor/BlackPoint.kt | 14 - .../plugin/image_processor/Brightness.kt | 14 - .../plugin/image_processor/Contrast.kt | 14 - .../nc_photos/plugin/image_processor/Cool.kt | 14 - .../nc_photos/plugin/image_processor/Crop.kt | 25 - .../plugin/image_processor/DeepLab3.kt | 81 -- .../plugin/image_processor/Esrgan.kt | 38 - .../nc_photos/plugin/image_processor/Image.kt | 33 - .../image_processor/ImageFilterProcessor.kt | 37 - .../plugin/image_processor/LosslessRotator.kt | 82 -- .../plugin/image_processor/NeurOp.kt | 38 - .../plugin/image_processor/Orientation.kt | 18 - .../plugin/image_processor/Saturation.kt | 14 - .../plugin/image_processor/TfLiteHelper.kt | 84 -- .../nc_photos/plugin/image_processor/Tint.kt | 14 - .../plugin/image_processor/Warmth.kt | 14 - .../plugin/image_processor/WhitePoint.kt | 14 - .../plugin/image_processor/ZeroDce.kt | 40 - plugin/lib/nc_photos_plugin.dart | 4 - 249 files changed, 5644 insertions(+), 4139 deletions(-) delete mode 100644 app/android/app/src/main/kotlin/com/nkming/nc_photos/K.kt rename {np_android_log => np_android_core}/.gitignore (100%) rename {np_android_log => np_android_core}/build.gradle (77%) rename {np_android_log => np_android_core}/gradle.properties (100%) rename {np_android_log => np_android_core}/gradle/wrapper/gradle-wrapper.properties (100%) create mode 100644 np_android_core/settings.gradle rename {np_android_log => np_android_core}/src/main/AndroidManifest.xml (100%) create mode 100644 np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/BitmapUtil.kt create mode 100644 np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Exception.kt create mode 100644 np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/K.kt rename {np_android_log/src/main/java/com/nkming/nc_photos/np_android_log => np_android_core/src/main/java/com/nkming/nc_photos/np_android_core}/Log.kt (96%) rename {plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin => np_android_core/src/main/java/com/nkming/nc_photos/np_android_core}/MediaStoreUtil.kt (98%) create mode 100644 np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/PermissionUtil.kt create mode 100644 np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Rgba8Image.kt rename {plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin => np_android_core/src/main/java/com/nkming/nc_photos/np_android_core}/UriUtil.kt (92%) rename {plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin => np_android_core/src/main/java/com/nkming/nc_photos/np_android_core}/Util.kt (92%) delete mode 100644 np_android_log/consumer-rules.pro delete mode 100644 np_android_log/proguard-rules.pro delete mode 100644 np_android_log/settings.gradle create mode 100644 np_platform_image_processor/.gitignore create mode 100644 np_platform_image_processor/.metadata create mode 100644 np_platform_image_processor/analysis_options.yaml create mode 100644 np_platform_image_processor/android/.gitignore create mode 100644 np_platform_image_processor/android/build.gradle rename {plugin => np_platform_image_processor}/android/dependency/renderscript-intrinsics-replacement-toolkit/headers/RenderScriptToolkit.h (100%) rename {plugin => np_platform_image_processor}/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/arm64-v8a/librenderscript-toolkit.so (100%) rename {plugin => np_platform_image_processor}/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/armeabi-v7a/librenderscript-toolkit.so (100%) rename {plugin => np_platform_image_processor}/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/x86_64/librenderscript-toolkit.so (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/headers/tensorflow/lite/builtin_ops.h (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api.h (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_experimental.h (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_types.h (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/headers/tensorflow/lite/c/common.h (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/jni/arm64-v8a/libtensorflowlite_jni.so (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/jni/armeabi-v7a/libtensorflowlite_jni.so (100%) rename {plugin => np_platform_image_processor}/android/dependency/tensorflowlite/jni/x86_64/libtensorflowlite_jni.so (100%) create mode 100644 np_platform_image_processor/android/gradle.properties create mode 100644 np_platform_image_processor/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 np_platform_image_processor/android/settings.gradle create mode 100644 np_platform_image_processor/android/src/main/AndroidManifest.xml rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_predict_1.tflite (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_transfer_1.tflite (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer/1.jpg (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer/2.jpg (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer/3.jpg (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer/4.jpg (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer/5.jpg (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/arbitrary-style-transfer/6.jpg (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/esrgan-tf2_1-dr.tflite (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/lite-model_mobilenetv2-dm05-coco_dr_1.tflite (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/neurop_fivek_lite.tflite (100%) rename {plugin => np_platform_image_processor}/android/src/main/assets/tf/zero_dce_lite_200x300_iter8_60.tflite (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/CMakeLists.txt (92%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/arbitrary_style_transfer.cpp (98%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/arbitrary_style_transfer.h (71%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/brightness.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/color_levels.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/contrast.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/curve.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/curve.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/filters.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/hslhsv.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/hslhsv.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/saturation.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/saturation.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/tint.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/warmth.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/yuv.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/filter/yuv.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/lib/base_resample.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/lib/spline/LICENSE (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/lib/spline/spline.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/lib/spline/spline.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/log.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/core/math_util.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/deep_lap_3.cpp (98%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/deep_lap_3.h (66%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/esrgan.cpp (97%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/esrgan.h (72%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/exception.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/exception.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/brightness.cpp (89%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/color_levels.cpp (89%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/contrast.cpp (89%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/crop.cpp (94%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/orientation.cpp (97%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/saturation.cpp (89%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/tint.cpp (89%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/filter/warmth.cpp (89%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/image_splitter.cpp (98%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/image_splitter.h (97%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/log.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/math_util.h (78%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/neur_op.cpp (96%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/neur_op.h (72%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/stopwatch.cpp (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/stopwatch.h (100%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/tflite_wrapper.cpp (99%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/tflite_wrapper.h (95%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/util.cpp (98%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/util.h (98%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/zero_dce.cpp (97%) rename {plugin => np_platform_image_processor}/android/src/main/cpp/zero_dce.h (73%) create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Event.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Exception.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorChannelHandler.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorService.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/K.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEvent.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEventChannelHandler.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NpPlatformImageProcessorPlugin.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ArbitraryStyleTransfer.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/BlackPoint.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Brightness.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Contrast.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Cool.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Crop.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/DeepLab3.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Esrgan.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ImageFilterProcessor.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/LosslessRotator.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/NeurOp.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Orientation.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Saturation.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/TfLiteHelper.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Tint.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Warmth.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/WhitePoint.kt create mode 100644 np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ZeroDce.kt rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-hdpi/outline_auto_fix_high_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-hdpi/outline_error_outline_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-hdpi/outline_image_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-mdpi/outline_auto_fix_high_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-mdpi/outline_error_outline_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-mdpi/outline_image_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xhdpi/outline_auto_fix_high_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xhdpi/outline_error_outline_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xhdpi/outline_image_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xxhdpi/outline_auto_fix_high_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xxhdpi/outline_error_outline_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xxhdpi/outline_image_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xxxhdpi/outline_auto_fix_high_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xxxhdpi/outline_error_outline_white_24.png (100%) rename {plugin => np_platform_image_processor}/android/src/main/res/drawable-xxxhdpi/outline_image_white_24.png (100%) create mode 100644 np_platform_image_processor/lib/np_platform_image_processor.dart rename {plugin => np_platform_image_processor}/lib/src/image_processor.dart (96%) create mode 100644 np_platform_image_processor/lib/src/k.dart create mode 100644 np_platform_image_processor/pubspec.yaml create mode 100644 np_platform_permission/.gitignore create mode 100644 np_platform_permission/.metadata create mode 100644 np_platform_permission/analysis_options.yaml create mode 100644 np_platform_permission/android/.gitignore create mode 100644 np_platform_permission/android/build.gradle create mode 100644 np_platform_permission/android/gradle.properties create mode 100644 np_platform_permission/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 np_platform_permission/android/gradlew create mode 100644 np_platform_permission/android/gradlew.bat create mode 100644 np_platform_permission/android/settings.gradle create mode 100644 np_platform_permission/android/src/main/AndroidManifest.xml create mode 100644 np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/K.kt create mode 100644 np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/NpPlatformPermissionPlugin.kt create mode 100644 np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/PermissionChannelHandler.kt create mode 100644 np_platform_permission/lib/np_platform_permission.dart create mode 100644 np_platform_permission/lib/src/k.dart rename {plugin => np_platform_permission}/lib/src/permission.dart (96%) create mode 100644 np_platform_permission/pubspec.yaml create mode 100644 np_platform_raw_image/.gitignore create mode 100644 np_platform_raw_image/.metadata create mode 100644 np_platform_raw_image/analysis_options.yaml create mode 100644 np_platform_raw_image/android/.gitignore create mode 100644 np_platform_raw_image/android/build.gradle create mode 100644 np_platform_raw_image/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 np_platform_raw_image/android/gradlew create mode 100644 np_platform_raw_image/android/gradlew.bat create mode 100644 np_platform_raw_image/android/settings.gradle create mode 100644 np_platform_raw_image/android/src/main/AndroidManifest.xml create mode 100644 np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/ImageLoaderChannelHandler.kt create mode 100644 np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/K.kt create mode 100644 np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/NpPlatformRawImagePlugin.kt create mode 100644 np_platform_raw_image/lib/np_platform_raw_image.dart rename {plugin => np_platform_raw_image}/lib/src/image_loader.dart (88%) create mode 100644 np_platform_raw_image/lib/src/k.dart rename plugin/lib/src/image.dart => np_platform_raw_image/lib/src/rgba8_image.dart (100%) create mode 100644 np_platform_raw_image/pubspec.yaml delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Event.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEvent.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEventChannelHandler.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PermissionChannelHandler.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PermissionUtil.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/BlackPoint.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Brightness.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Contrast.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Cool.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Crop.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Image.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/LosslessRotator.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/NeurOp.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Orientation.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Saturation.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/TfLiteHelper.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Tint.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Warmth.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/WhitePoint.kt delete mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle index 031bc7bd..c263e524 100644 --- a/app/android/app/build.gradle +++ b/app/android/app/build.gradle @@ -121,6 +121,6 @@ dependencies { // fix crash on sdk33, need investigation implementation "androidx.window:window:1.0.0" implementation 'com.google.android.gms:play-services-maps:18.1.0' - implementation 'com.nkming.nc_photos.np_android_log:np_android_log' + implementation 'com.nkming.nc_photos.np_android_core:np_android_core' coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3" } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/App.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/App.kt index 41d3c26f..d60086a7 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/App.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/App.kt @@ -1,14 +1,14 @@ package com.nkming.nc_photos -import com.nkming.nc_photos.np_android_log.LogConfig +import com.nkming.nc_photos.np_android_core.LogConfig import io.flutter.BuildConfig import io.flutter.app.FlutterApplication class App : FlutterApplication() { - override fun onCreate() { - super.onCreate() - LogConfig.isShowInfo = BuildConfig.DEBUG - LogConfig.isShowDebug = BuildConfig.DEBUG - LogConfig.isShowVerbose = BuildConfig.DEBUG - } + override fun onCreate() { + super.onCreate() + LogConfig.isShowInfo = BuildConfig.DEBUG + LogConfig.isShowDebug = BuildConfig.DEBUG + LogConfig.isShowVerbose = BuildConfig.DEBUG + } } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomHostnameVerifier.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomHostnameVerifier.kt index 6b548c4f..c372def3 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomHostnameVerifier.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomHostnameVerifier.kt @@ -1,46 +1,44 @@ package com.nkming.nc_photos import android.content.Context -import com.nkming.nc_photos.np_android_log.logI +import com.nkming.nc_photos.np_android_core.logI import java.util.Locale import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLSession class CustomHostnameVerifier(context: Context) : HostnameVerifier { - /** - * Allow host names allowed by user, for other hosts, revert to the default - * behavior - */ - override fun verify(hostname: String, session: SSLSession): Boolean { - return if (allowedHosts.contains( - hostname.lowercase(Locale.getDefault()) - )) { - // good - logI( - "CustomHostnameVerifier::verify", - "Allowing registered host: $hostname" - ) - true - } else { - defaultHostnameVerifier.verify(hostname, session) - } - } + /** + * Allow host names allowed by user, for other hosts, revert to the default + * behavior + */ + override fun verify(hostname: String, session: SSLSession): Boolean { + return if (allowedHosts.contains(hostname.lowercase(Locale.getDefault()))) { + // good + logI( + "CustomHostnameVerifier::verify", + "Allowing registered host: $hostname" + ) + true + } else { + defaultHostnameVerifier.verify(hostname, session) + } + } - fun reload(context: Context) { - val certManager = SelfSignedCertManager() - val certs = certManager.readAllCerts(context) - allowedHosts.clear() - for (c in certs) { - allowedHosts.add(c.first.host.lowercase(Locale.getDefault())) - } - } + fun reload(context: Context) { + val certManager = SelfSignedCertManager() + val certs = certManager.readAllCerts(context) + allowedHosts.clear() + for (c in certs) { + allowedHosts.add(c.first.host.lowercase(Locale.getDefault())) + } + } - private val defaultHostnameVerifier: HostnameVerifier = - HttpsURLConnection.getDefaultHostnameVerifier() - private val allowedHosts: MutableList = ArrayList() + private val defaultHostnameVerifier: HostnameVerifier = + HttpsURLConnection.getDefaultHostnameVerifier() + private val allowedHosts: MutableList = ArrayList() - init { - reload(context) - } + init { + reload(context) + } } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomKeyStoresTrustManager.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomKeyStoresTrustManager.kt index 571936bf..b5264606 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomKeyStoresTrustManager.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomKeyStoresTrustManager.kt @@ -9,80 +9,84 @@ import javax.net.ssl.X509TrustManager // See: https://stackoverflow.com/a/6378872 class CustomKeyStoresTrustManager(keyStore: KeyStore) : X509TrustManager { - /* - * Delegate to the default trust manager. - */ - @Throws(CertificateException::class) - override fun checkClientTrusted(chain: Array, - authType: String) { - val defaultX509TrustManager = x509TrustManagers[0] - defaultX509TrustManager.checkClientTrusted(chain, authType) - } + /* + * Delegate to the default trust manager. + */ + @Throws(CertificateException::class) + override fun checkClientTrusted( + chain: Array, authType: String + ) { + val defaultX509TrustManager = x509TrustManagers[0] + defaultX509TrustManager.checkClientTrusted(chain, authType) + } - /* - * Loop over the trustmanagers until we find one that accepts our server - */ - @Throws(CertificateException::class) - override fun checkServerTrusted(chain: Array, - authType: String) { - var defaultException: Exception? = null - for (tm in x509TrustManagers) { - try { - tm.checkServerTrusted(chain, authType) - return - } catch (e: CertificateException) { - // ignore - if (defaultException == null) { - defaultException = e - } - } - } - if (defaultException != null) { - throw defaultException - } - } + /* + * Loop over the trustmanagers until we find one that accepts our server + */ + @Throws(CertificateException::class) + override fun checkServerTrusted( + chain: Array, authType: String + ) { + var defaultException: Exception? = null + for (tm in x509TrustManagers) { + try { + tm.checkServerTrusted(chain, authType) + return + } catch (e: CertificateException) { + // ignore + if (defaultException == null) { + defaultException = e + } + } + } + if (defaultException != null) { + throw defaultException + } + } - override fun getAcceptedIssuers(): Array { - val list = ArrayList() - for (tm in x509TrustManagers) { - list.addAll(tm.acceptedIssuers.toList()) - } - return list.toTypedArray() - } + override fun getAcceptedIssuers(): Array { + val list = ArrayList() + for (tm in x509TrustManagers) { + list.addAll(tm.acceptedIssuers.toList()) + } + return list.toTypedArray() + } - fun setCustomKeyStore(keyStore: KeyStore) { - val factories = ArrayList() - // The default Trustmanager with default keystore - val original = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()) - original.init(null as KeyStore?) - factories.add(original) + fun setCustomKeyStore(keyStore: KeyStore) { + val factories = ArrayList() + // The default Trustmanager with default keystore + val original = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm() + ) + original.init(null as KeyStore?) + factories.add(original) - // with custom keystore - val custom = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()) - custom.init(keyStore) - factories.add(custom) + // with custom keystore + val custom = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm() + ) + custom.init(keyStore) + factories.add(custom) - /* - * Iterate over the returned trustmanagers, and hold on - * to any that are X509TrustManagers - */ - for (tmf in factories) { - for (tm in tmf.trustManagers) { - if (tm is X509TrustManager) { - x509TrustManagers.add(tm) - } - } - } - if (x509TrustManagers.isEmpty()) { - throw RuntimeException("Couldn't find any X509TrustManagers") - } - } + /* + * Iterate over the returned trustmanagers, and hold on + * to any that are X509TrustManagers + */ + for (tmf in factories) { + for (tm in tmf.trustManagers) { + if (tm is X509TrustManager) { + x509TrustManagers.add(tm) + } + } + } + if (x509TrustManagers.isEmpty()) { + throw RuntimeException("Couldn't find any X509TrustManagers") + } + } - private val x509TrustManagers: MutableList = ArrayList() + private val x509TrustManagers: MutableList = ArrayList() - init { - setCustomKeyStore(keyStore) - } + init { + setCustomKeyStore(keyStore) + } } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomSSLSocketFactory.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomSSLSocketFactory.kt index d6b4cc94..ebfb6d78 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomSSLSocketFactory.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/CustomSSLSocketFactory.kt @@ -15,90 +15,100 @@ import javax.net.ssl.SSLSocketFactory import javax.net.ssl.TrustManager class CustomSSLSocketFactory(context: Context) : SSLSocketFactory() { - override fun getDefaultCipherSuites(): Array { - return sslSocketFactory.defaultCipherSuites - } + override fun getDefaultCipherSuites(): Array { + return sslSocketFactory.defaultCipherSuites + } - override fun getSupportedCipherSuites(): Array { - return sslSocketFactory.supportedCipherSuites - } + override fun getSupportedCipherSuites(): Array { + return sslSocketFactory.supportedCipherSuites + } - @Throws(IOException::class) - override fun createSocket(): Socket { - return enableProtocols(sslSocketFactory.createSocket()) - } + @Throws(IOException::class) + override fun createSocket(): Socket { + return enableProtocols(sslSocketFactory.createSocket()) + } - @Throws(IOException::class) - override fun createSocket(s: Socket, host: String, port: Int, - autoClose: Boolean): Socket { - return enableProtocols( - sslSocketFactory.createSocket(s, host, port, autoClose)) - } + @Throws(IOException::class) + override fun createSocket( + s: Socket, host: String, port: Int, autoClose: Boolean + ): Socket { + return enableProtocols( + sslSocketFactory.createSocket(s, host, port, autoClose) + ) + } - @Throws(IOException::class) - override fun createSocket(host: String, port: Int): Socket { - return enableProtocols(sslSocketFactory.createSocket(host, port)) - } + @Throws(IOException::class) + override fun createSocket(host: String, port: Int): Socket { + return enableProtocols(sslSocketFactory.createSocket(host, port)) + } - @Throws(IOException::class) - override fun createSocket(host: String, port: Int, localHost: InetAddress, - localPort: Int): Socket { - return enableProtocols( - sslSocketFactory.createSocket(host, port, localHost, localPort)) - } + @Throws(IOException::class) + override fun createSocket( + host: String, port: Int, localHost: InetAddress, localPort: Int + ): Socket { + return enableProtocols( + sslSocketFactory.createSocket(host, port, localHost, localPort) + ) + } - @Throws(IOException::class) - override fun createSocket(host: InetAddress, port: Int): Socket { - return enableProtocols(sslSocketFactory.createSocket(host, port)) - } + @Throws(IOException::class) + override fun createSocket(host: InetAddress, port: Int): Socket { + return enableProtocols(sslSocketFactory.createSocket(host, port)) + } - @Throws(IOException::class) - override fun createSocket(address: InetAddress, port: Int, - localAddress: InetAddress, localPort: Int): Socket { - return enableProtocols( - sslSocketFactory.createSocket(address, port, localAddress, - localPort)) - } + @Throws(IOException::class) + override fun createSocket( + address: InetAddress, + port: Int, + localAddress: InetAddress, + localPort: Int + ): Socket { + return enableProtocols( + sslSocketFactory.createSocket( + address, port, localAddress, localPort + ) + ) + } - fun reload(context: Context) { - val keyStore = makeCustomKeyStore(context) - trustManager.setCustomKeyStore(keyStore) - } + fun reload(context: Context) { + val keyStore = makeCustomKeyStore(context) + trustManager.setCustomKeyStore(keyStore) + } - private fun enableProtocols(socket: Socket): Socket { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - // enable TLSv1.1 and TLSv1.2 Protocols for API level 19 and below - if (socket is SSLSocket) { - socket.enabledProtocols = arrayOf("TLSv1.1", "TLSv1.2") - } - } - return socket - } + private fun enableProtocols(socket: Socket): Socket { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // enable TLSv1.1 and TLSv1.2 Protocols for API level 19 and below + if (socket is SSLSocket) { + socket.enabledProtocols = arrayOf("TLSv1.1", "TLSv1.2") + } + } + return socket + } - private fun makeCustomKeyStore(context: Context): KeyStore { - // build key store with ca certificate - val keyStoreType = KeyStore.getDefaultType() - val keyStore = KeyStore.getInstance(keyStoreType) - keyStore.load(null, null) + private fun makeCustomKeyStore(context: Context): KeyStore { + // build key store with ca certificate + val keyStoreType = KeyStore.getDefaultType() + val keyStore = KeyStore.getInstance(keyStoreType) + keyStore.load(null, null) - val certManager = SelfSignedCertManager() - val certs = certManager.readAllCerts(context) - for (c in certs) { - keyStore.setCertificateEntry(c.first.host, c.second) - } - return keyStore - } + val certManager = SelfSignedCertManager() + val certs = certManager.readAllCerts(context) + for (c in certs) { + keyStore.setCertificateEntry(c.first.host, c.second) + } + return keyStore + } - private val sslSocketFactory: SSLSocketFactory - private val trustManager: CustomKeyStoresTrustManager + private val sslSocketFactory: SSLSocketFactory + private val trustManager: CustomKeyStoresTrustManager - init { - val keyStore = makeCustomKeyStore(context) - trustManager = CustomKeyStoresTrustManager(keyStore) + init { + val keyStore = makeCustomKeyStore(context) + trustManager = CustomKeyStoresTrustManager(keyStore) - // Create an SSLContext that uses our TrustManager - val sslContext = SSLContext.getInstance("TLS") - sslContext.init(null, arrayOf(trustManager), null) - sslSocketFactory = sslContext.socketFactory - } + // Create an SSLContext that uses our TrustManager + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, arrayOf(trustManager), null) + sslSocketFactory = sslContext.socketFactory + } } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/DownloadEventChannelHandler.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/DownloadEventChannelHandler.kt index bbc6fc72..f49b29f0 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/DownloadEventChannelHandler.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/DownloadEventChannelHandler.kt @@ -8,40 +8,40 @@ import com.nkming.nc_photos.plugin.NcPhotosPlugin import io.flutter.plugin.common.EventChannel class DownloadEventCancelChannelHandler(context: Context) : BroadcastReceiver(), - EventChannel.StreamHandler { - companion object { - @JvmStatic - val CHANNEL = - "com.nkming.nc_photos/download_event/action_download_cancel" - } + EventChannel.StreamHandler { + companion object { + @JvmStatic + val CHANNEL = + "com.nkming.nc_photos/download_event/action_download_cancel" + } - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action != NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL || !intent.hasExtra( - NcPhotosPlugin.EXTRA_NOTIFICATION_ID - ) - ) { - return - } + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action != NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL || !intent.hasExtra( + NcPhotosPlugin.EXTRA_NOTIFICATION_ID + ) + ) { + return + } - val id = intent.getIntExtra(NcPhotosPlugin.EXTRA_NOTIFICATION_ID, 0) - _eventSink?.success( - mapOf( - "notificationId" to id - ) - ) - } + val id = intent.getIntExtra(NcPhotosPlugin.EXTRA_NOTIFICATION_ID, 0) + _eventSink?.success( + mapOf( + "notificationId" to id + ) + ) + } - override fun onListen(arguments: Any?, events: EventChannel.EventSink) { - _context.registerReceiver( - this, IntentFilter(NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL) - ) - _eventSink = events - } + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + _context.registerReceiver( + this, IntentFilter(NcPhotosPlugin.ACTION_DOWNLOAD_CANCEL) + ) + _eventSink = events + } - override fun onCancel(arguments: Any?) { - _context.unregisterReceiver(this) - } + override fun onCancel(arguments: Any?) { + _context.unregisterReceiver(this) + } - private val _context = context - private var _eventSink: EventChannel.EventSink? = null + private val _context = context + private var _eventSink: EventChannel.EventSink? = null } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/K.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/K.kt deleted file mode 100644 index 1444b45e..00000000 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/K.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.nkming.nc_photos - -interface K { - companion object { - } -} diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt index 883911cd..bd38ab5b 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt @@ -6,11 +6,11 @@ import android.os.Bundle import androidx.annotation.NonNull import com.google.android.gms.maps.MapsInitializer import com.google.android.gms.maps.OnMapsSdkInitializedCallback -import com.nkming.nc_photos.np_android_log.logD -import com.nkming.nc_photos.np_android_log.logE -import com.nkming.nc_photos.np_android_log.logI -import com.nkming.nc_photos.plugin.NcPhotosPlugin -import com.nkming.nc_photos.plugin.UriUtil +import com.nkming.nc_photos.np_android_core.UriUtil +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 com.nkming.nc_photos.np_platform_image_processor.NpPlatformImageProcessorPlugin import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.EventChannel @@ -19,117 +19,117 @@ import io.flutter.plugin.common.MethodChannel import java.net.URLEncoder class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, - OnMapsSdkInitializedCallback { - companion object { - private const val METHOD_CHANNEL = "com.nkming.nc_photos/activity" + OnMapsSdkInitializedCallback { + companion object { + private const val METHOD_CHANNEL = "com.nkming.nc_photos/activity" - private const val TAG = "MainActivity" - } + private const val TAG = "MainActivity" + } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (intent.action == NcPhotosPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT) { - val route = getRouteFromImageProcessorResult(intent) ?: return - logI(TAG, "Initial route: $route") - _initialRoute = route - } - MapsInitializer.initialize( - applicationContext, MapsInitializer.Renderer.LATEST, this - ) - } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (intent.action == NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT) { + val route = getRouteFromImageProcessorResult(intent) ?: return + logI(TAG, "Initial route: $route") + _initialRoute = route + } + MapsInitializer.initialize( + applicationContext, MapsInitializer.Renderer.LATEST, this + ) + } - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - MethodChannel( - flutterEngine.dartExecutor.binaryMessenger, - SelfSignedCertChannelHandler.CHANNEL - ).setMethodCallHandler( - SelfSignedCertChannelHandler(this) - ) - MethodChannel( - flutterEngine.dartExecutor.binaryMessenger, - ShareChannelHandler.CHANNEL - ).setMethodCallHandler( - ShareChannelHandler(this) - ) - MethodChannel( - flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL - ).setMethodCallHandler(this) + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + SelfSignedCertChannelHandler.CHANNEL + ).setMethodCallHandler( + SelfSignedCertChannelHandler(this) + ) + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + ShareChannelHandler.CHANNEL + ).setMethodCallHandler( + ShareChannelHandler(this) + ) + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL + ).setMethodCallHandler(this) - EventChannel( - flutterEngine.dartExecutor.binaryMessenger, - DownloadEventCancelChannelHandler.CHANNEL - ).setStreamHandler( - DownloadEventCancelChannelHandler(this) - ) - } + EventChannel( + flutterEngine.dartExecutor.binaryMessenger, + DownloadEventCancelChannelHandler.CHANNEL + ).setStreamHandler( + DownloadEventCancelChannelHandler(this) + ) + } - override fun onNewIntent(intent: Intent) { - when (intent.action) { - NcPhotosPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT -> { - val route = getRouteFromImageProcessorResult(intent) ?: return - logI(TAG, "Navigate to route: $route") - flutterEngine?.navigationChannel?.pushRoute(route) - } + override fun onNewIntent(intent: Intent) { + when (intent.action) { + NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT -> { + val route = getRouteFromImageProcessorResult(intent) ?: return + logI(TAG, "Navigate to route: $route") + flutterEngine?.navigationChannel?.pushRoute(route) + } - else -> { - super.onNewIntent(intent) - } - } - } + else -> { + super.onNewIntent(intent) + } + } + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "consumeInitialRoute" -> { - result.success(_initialRoute) - _initialRoute = null - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "consumeInitialRoute" -> { + result.success(_initialRoute) + _initialRoute = null + } - "isNewGMapsRenderer" -> { - result.success(_isNewGMapsRenderer) - } + "isNewGMapsRenderer" -> { + result.success(_isNewGMapsRenderer) + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - override fun onMapsSdkInitialized(renderer: MapsInitializer.Renderer) { - _isNewGMapsRenderer = when (renderer) { - MapsInitializer.Renderer.LATEST -> { - logD(TAG, "Using new map renderer") - true - } + override fun onMapsSdkInitialized(renderer: MapsInitializer.Renderer) { + _isNewGMapsRenderer = when (renderer) { + MapsInitializer.Renderer.LATEST -> { + logD(TAG, "Using new map renderer") + true + } - MapsInitializer.Renderer.LEGACY -> { - logD(TAG, "Using legacy map renderer") - false - } - } - } + MapsInitializer.Renderer.LEGACY -> { + logD(TAG, "Using legacy map renderer") + false + } + } + } - private fun getRouteFromImageProcessorResult(intent: Intent): String? { - val resultUri = intent.getParcelableExtra( - NcPhotosPlugin.EXTRA_IMAGE_RESULT_URI - ) - if (resultUri == null) { - logE(TAG, "Image result uri == null") - return null - } - return if (resultUri.scheme?.startsWith("http") == true) { - // remote uri - val encodedUrl = URLEncoder.encode(resultUri.toString(), "utf-8") - "/result-viewer?url=$encodedUrl" - } else { - val filename = UriUtil.resolveFilename(this, resultUri)?.let { - URLEncoder.encode(it, Charsets.UTF_8.toString()) - } - StringBuilder().apply { - append("/enhanced-photo-browser?") - if (filename != null) append("filename=$filename") - }.toString() - } - } + private fun getRouteFromImageProcessorResult(intent: Intent): String? { + val resultUri = intent.getParcelableExtra( + NpPlatformImageProcessorPlugin.EXTRA_IMAGE_RESULT_URI + ) + if (resultUri == null) { + logE(TAG, "Image result uri == null") + return null + } + return if (resultUri.scheme?.startsWith("http") == true) { + // remote uri + val encodedUrl = URLEncoder.encode(resultUri.toString(), "utf-8") + "/result-viewer?url=$encodedUrl" + } else { + val filename = UriUtil.resolveFilename(this, resultUri)?.let { + URLEncoder.encode(it, Charsets.UTF_8.toString()) + } + StringBuilder().apply { + append("/enhanced-photo-browser?") + if (filename != null) append("filename=$filename") + }.toString() + } + } - private var _initialRoute: String? = null - private var _isNewGMapsRenderer = false + private var _initialRoute: String? = null + private var _isNewGMapsRenderer = false } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertChannelHandler.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertChannelHandler.kt index 825dd7db..9ab72382 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertChannelHandler.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertChannelHandler.kt @@ -1,7 +1,7 @@ package com.nkming.nc_photos import android.app.Activity -import com.nkming.nc_photos.np_android_log.logE +import com.nkming.nc_photos.np_android_core.logE import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import javax.net.ssl.HttpsURLConnection diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertManager.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertManager.kt index cfd198d2..11e834bc 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertManager.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/SelfSignedCertManager.kt @@ -2,8 +2,8 @@ package com.nkming.nc_photos import android.content.Context import android.util.Pair -import com.nkming.nc_photos.np_android_log.logE -import com.nkming.nc_photos.np_android_log.logI +import com.nkming.nc_photos.np_android_core.logE +import com.nkming.nc_photos.np_android_core.logI import org.json.JSONObject import java.io.File import java.io.FileInputStream @@ -13,57 +13,63 @@ import java.time.OffsetDateTime // Modifications to this class must also reflect on dart side data class CertInfo( - val host: String, val sha1: String, val subject: String, - val issuer: String, val startValidity: OffsetDateTime, - val endValidity: OffsetDateTime + val host: String, + val sha1: String, + val subject: String, + val issuer: String, + val startValidity: OffsetDateTime, + val endValidity: OffsetDateTime ) { - companion object { - fun fromJson(json: JSONObject): CertInfo { - return CertInfo( - json.getString("host"), json.getString("sha1"), - json.getString("subject"), json.getString("issuer"), - OffsetDateTime.parse(json.getString("startValidity")), - OffsetDateTime.parse(json.getString("endValidity")) - ) - } - } + companion object { + fun fromJson(json: JSONObject): CertInfo { + return CertInfo( + json.getString("host"), + json.getString("sha1"), + json.getString("subject"), + json.getString("issuer"), + OffsetDateTime.parse(json.getString("startValidity")), + OffsetDateTime.parse(json.getString("endValidity")) + ) + } + } } class SelfSignedCertManager { - /** - * Read and return all persisted certificates - * - * @return List of certificates with the corresponding info - */ - fun readAllCerts(context: Context): List> { - val products = ArrayList>() - val certDir = openCertsDir(context) - val certFiles = certDir.listFiles()!! - val factory = CertificateFactory.getInstance("X.509") - for (f in certFiles) { - if (f.name.endsWith(".json")) { - // companion file - continue - } - try { - val c = factory.generateCertificate(FileInputStream(f)) - val jsonFile = File(certDir, f.name + ".json") - val jsonStr = jsonFile.bufferedReader().use { it.readText() } - val info = CertInfo.fromJson(JSONObject(jsonStr)) - logI( - "SelfSignedCertManager::readAllCerts", - "Found certificate: ${f.name} for host: ${info.host}" - ) - products.add(Pair(info, c)) - } catch (e: Exception) { - logE( - "SelfSignedCertManager::readAllCerts", - "Failed to read certificate file: ${f.name}", e - ) - } - } - return products - } + /** + * Read and return all persisted certificates + * + * @return List of certificates with the corresponding info + */ + fun readAllCerts(context: Context): List> { + val products = ArrayList>() + val certDir = openCertsDir(context) + val certFiles = certDir.listFiles()!! + val factory = CertificateFactory.getInstance("X.509") + for (f in certFiles) { + if (f.name.endsWith(".json")) { + // companion file + continue + } + try { + val c = factory.generateCertificate(FileInputStream(f)) + val jsonFile = File(certDir, f.name + ".json") + val jsonStr = jsonFile.bufferedReader().use { it.readText() } + val info = CertInfo.fromJson(JSONObject(jsonStr)) + logI( + "SelfSignedCertManager::readAllCerts", + "Found certificate: ${f.name} for host: ${info.host}" + ) + products.add(Pair(info, c)) + } catch (e: Exception) { + logE( + "SelfSignedCertManager::readAllCerts", + "Failed to read certificate file: ${f.name}", + e + ) + } + } + return products + } // Outdated, don't use // /** @@ -88,21 +94,21 @@ class SelfSignedCertManager { // } // } - private fun openCertsDir(context: Context): File { - val certDir = File(context.filesDir, "certs") - return if (!certDir.exists()) { - certDir.mkdir() - certDir - } else if (!certDir.isDirectory) { - logE( - "SelfSignedCertManager::openCertsDir", - "Removing certs file to make way for the directory" - ) - certDir.delete() - certDir.mkdir() - certDir - } else { - certDir - } - } + private fun openCertsDir(context: Context): File { + val certDir = File(context.filesDir, "certs") + return if (!certDir.exists()) { + certDir.mkdir() + certDir + } else if (!certDir.isDirectory) { + logE( + "SelfSignedCertManager::openCertsDir", + "Removing certs file to make way for the directory" + ) + certDir.delete() + certDir.mkdir() + certDir + } else { + certDir + } + } } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/ShareChannelHandler.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/ShareChannelHandler.kt index e35282bb..0f13f692 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/ShareChannelHandler.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/ShareChannelHandler.kt @@ -8,130 +8,134 @@ import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel class ShareChannelHandler(activity: Activity) : - MethodChannel.MethodCallHandler { - companion object { - const val CHANNEL = "com.nkming.nc_photos/share" - } + MethodChannel.MethodCallHandler { + companion object { + const val CHANNEL = "com.nkming.nc_photos/share" + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "shareItems" -> { - try { - shareItems( - call.argument("fileUris")!!, - call.argument("mimeTypes")!!, result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "shareItems" -> { + try { + shareItems( + call.argument("fileUris")!!, + call.argument("mimeTypes")!!, + result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "shareText" -> { - try { - shareText( - call.argument("text")!!, call.argument("mimeType"), - result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "shareText" -> { + try { + shareText( + call.argument("text")!!, + call.argument("mimeType"), + result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - "shareAsAttachData" -> { - try { - shareAsAttachData( - call.argument("fileUri")!!, call.argument("mimeType"), - result - ) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + "shareAsAttachData" -> { + try { + shareAsAttachData( + call.argument("fileUri")!!, + call.argument("mimeType"), + result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> { - result.notImplemented() - } - } - } + else -> { + result.notImplemented() + } + } + } - private fun shareItems( - fileUris: List, mimeTypes: List, - result: MethodChannel.Result - ) { - assert(fileUris.isNotEmpty()) - assert(fileUris.size == mimeTypes.size) - val uris = fileUris.map { Uri.parse(it) } + private fun shareItems( + fileUris: List, + mimeTypes: List, + result: MethodChannel.Result + ) { + assert(fileUris.isNotEmpty()) + assert(fileUris.size == mimeTypes.size) + val uris = fileUris.map { Uri.parse(it) } - val shareIntent = if (uris.size == 1) Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, uris[0]) - // setting clipdata is needed for FLAG_GRANT_READ_URI_PERMISSION to - // work, see: https://developer.android.com/reference/android/content/Intent#ACTION_SEND - clipData = - ClipData.newUri(_context.contentResolver, "Share", uris[0]) - type = mimeTypes[0] ?: "*/*" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } else Intent().apply { - action = Intent.ACTION_SEND_MULTIPLE - putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris)) - clipData = - ClipData.newUri(_context.contentResolver, "Share", uris[0]) - .apply { - for (uri in uris.subList(1, uris.size)) { - addItem(ClipData.Item(uri)) - } - } - type = if (mimeTypes.all { - it?.startsWith("image/") == true - }) "image/*" else "*/*" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - val shareChooser = Intent.createChooser( - shareIntent, _context.getString( - R.string.download_successful_notification_action_share_chooser - ) - ) - _context.startActivity(shareChooser) - result.success(null) - } + val shareIntent = if (uris.size == 1) Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, uris[0]) + // setting clipdata is needed for FLAG_GRANT_READ_URI_PERMISSION to + // work, see: https://developer.android.com/reference/android/content/Intent#ACTION_SEND + clipData = + ClipData.newUri(_context.contentResolver, "Share", uris[0]) + type = mimeTypes[0] ?: "*/*" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } else Intent().apply { + action = Intent.ACTION_SEND_MULTIPLE + putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris)) + clipData = + ClipData.newUri(_context.contentResolver, "Share", uris[0]) + .apply { + for (uri in uris.subList(1, uris.size)) { + addItem(ClipData.Item(uri)) + } + } + type = if (mimeTypes.all { + it?.startsWith("image/") == true + }) "image/*" else "*/*" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + val shareChooser = Intent.createChooser( + shareIntent, _context.getString( + R.string.download_successful_notification_action_share_chooser + ) + ) + _context.startActivity(shareChooser) + result.success(null) + } - private fun shareText( - text: String, mimeType: String?, result: MethodChannel.Result - ) { - val shareIntent = Intent().apply { - action = Intent.ACTION_SEND - type = mimeType ?: "text/plain" - putExtra(Intent.EXTRA_TEXT, text) - } - val shareChooser = Intent.createChooser( - shareIntent, _context.getString( - R.string.download_successful_notification_action_share_chooser - ) - ) - _context.startActivity(shareChooser) - result.success(null) - } + private fun shareText( + text: String, mimeType: String?, result: MethodChannel.Result + ) { + val shareIntent = Intent().apply { + action = Intent.ACTION_SEND + type = mimeType ?: "text/plain" + putExtra(Intent.EXTRA_TEXT, text) + } + val shareChooser = Intent.createChooser( + shareIntent, _context.getString( + R.string.download_successful_notification_action_share_chooser + ) + ) + _context.startActivity(shareChooser) + result.success(null) + } - private fun shareAsAttachData( - fileUri: String, mimeType: String?, result: MethodChannel.Result - ) { - val intent = Intent().apply { - action = Intent.ACTION_ATTACH_DATA - if (mimeType == null) { - data = Uri.parse(fileUri) - } else { - setDataAndType(Uri.parse(fileUri), mimeType) - } - addCategory(Intent.CATEGORY_DEFAULT) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - val chooser = Intent.createChooser( - intent, _context.getString(R.string.attach_data_chooser_title) - ) - _context.startActivity(chooser) - result.success(null) - } + private fun shareAsAttachData( + fileUri: String, mimeType: String?, result: MethodChannel.Result + ) { + val intent = Intent().apply { + action = Intent.ACTION_ATTACH_DATA + if (mimeType == null) { + data = Uri.parse(fileUri) + } else { + setDataAndType(Uri.parse(fileUri), mimeType) + } + addCategory(Intent.CATEGORY_DEFAULT) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + val chooser = Intent.createChooser( + intent, _context.getString(R.string.attach_data_chooser_title) + ) + _context.startActivity(chooser) + result.success(null) + } - private val _activity = activity - private val _context get() = _activity + private val _activity = activity + private val _context get() = _activity } diff --git a/app/android/settings.gradle b/app/android/settings.gradle index 1cb8da2f..706f7cdf 100644 --- a/app/android/settings.gradle +++ b/app/android/settings.gradle @@ -1,5 +1,5 @@ include ':app' -includeBuild '../../np_android_log' +includeBuild '../../np_android_core' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/app/lib/mobile/android/permission_util.dart b/app/lib/mobile/android/permission_util.dart index 690d8ff0..42a50ee2 100644 --- a/app/lib/mobile/android/permission_util.dart +++ b/app/lib/mobile/android/permission_util.dart @@ -1,6 +1,6 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/stream_extension.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; +import 'package:np_platform_permission/np_platform_permission.dart'; Future> requestPermissionsForResult( List permissions) async { diff --git a/app/lib/widget/enhanced_photo_browser.dart b/app/lib/widget/enhanced_photo_browser.dart index fa6a29e0..06255cbe 100644 --- a/app/lib/widget/enhanced_photo_browser.dart +++ b/app/lib/widget/enhanced_photo_browser.dart @@ -27,6 +27,7 @@ import 'package:nc_photos_plugin/nc_photos_plugin.dart'; import 'package:np_async/np_async.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:np_collection/np_collection.dart'; +import 'package:np_platform_permission/np_platform_permission.dart'; import 'package:np_platform_util/np_platform_util.dart'; part 'enhanced_photo_browser.g.dart'; diff --git a/app/lib/widget/handler/permission_handler.dart b/app/lib/widget/handler/permission_handler.dart index 552d8e55..152c0b64 100644 --- a/app/lib/widget/handler/permission_handler.dart +++ b/app/lib/widget/handler/permission_handler.dart @@ -4,7 +4,7 @@ import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/android/android_info.dart'; import 'package:nc_photos/mobile/android/permission_util.dart'; import 'package:nc_photos/snack_bar_manager.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; +import 'package:np_platform_permission/np_platform_permission.dart'; import 'package:np_platform_util/np_platform_util.dart'; /// Handle platform permissions diff --git a/app/lib/widget/image_editor.dart b/app/lib/widget/image_editor.dart index 93f5c0cf..fc65d800 100644 --- a/app/lib/widget/image_editor.dart +++ b/app/lib/widget/image_editor.dart @@ -23,7 +23,8 @@ import 'package:nc_photos/widget/image_editor/color_toolbar.dart'; import 'package:nc_photos/widget/image_editor/crop_controller.dart'; import 'package:nc_photos/widget/image_editor/transform_toolbar.dart'; import 'package:nc_photos/widget/image_editor_persist_option_dialog.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; +import 'package:np_platform_image_processor/np_platform_image_processor.dart'; +import 'package:np_platform_raw_image/np_platform_raw_image.dart'; import 'package:np_ui/np_ui.dart'; class ImageEditorArguments { diff --git a/app/lib/widget/image_editor/color_toolbar.dart b/app/lib/widget/image_editor/color_toolbar.dart index 3cdab079..e1725ff7 100644 --- a/app/lib/widget/image_editor/color_toolbar.dart +++ b/app/lib/widget/image_editor/color_toolbar.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/widget/image_editor/toolbar_button.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; import 'package:np_collection/np_collection.dart'; +import 'package:np_platform_image_processor/np_platform_image_processor.dart'; import 'package:np_string/np_string.dart'; import 'package:np_ui/np_ui.dart'; diff --git a/app/lib/widget/image_editor/crop_controller.dart b/app/lib/widget/image_editor/crop_controller.dart index f6cdbfe1..d82a98b9 100644 --- a/app/lib/widget/image_editor/crop_controller.dart +++ b/app/lib/widget/image_editor/crop_controller.dart @@ -4,8 +4,9 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/pixel_image_provider.dart'; import 'package:nc_photos/widget/image_editor/transform_toolbar.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_platform_image_processor/np_platform_image_processor.dart'; +import 'package:np_platform_raw_image/np_platform_raw_image.dart'; part 'crop_controller.g.dart'; diff --git a/app/lib/widget/image_editor/transform_toolbar.dart b/app/lib/widget/image_editor/transform_toolbar.dart index 0d1450c2..5f055c20 100644 --- a/app/lib/widget/image_editor/transform_toolbar.dart +++ b/app/lib/widget/image_editor/transform_toolbar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/widget/image_editor/toolbar_button.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; +import 'package:np_platform_image_processor/np_platform_image_processor.dart'; enum TransformToolType { crop, diff --git a/app/lib/widget/image_enhancer.dart b/app/lib/widget/image_enhancer.dart index 1e6574e1..87a3b59b 100644 --- a/app/lib/widget/image_enhancer.dart +++ b/app/lib/widget/image_enhancer.dart @@ -28,8 +28,8 @@ import 'package:nc_photos/widget/handler/permission_handler.dart'; import 'package:nc_photos/widget/image_editor_persist_option_dialog.dart'; import 'package:nc_photos/widget/selectable.dart'; import 'package:nc_photos/widget/settings/enhancement_settings.dart'; -import 'package:nc_photos_plugin/nc_photos_plugin.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_platform_image_processor/np_platform_image_processor.dart'; import 'package:np_platform_util/np_platform_util.dart'; import 'package:np_ui/np_ui.dart'; diff --git a/app/lib/widget/settings/developer_settings.dart b/app/lib/widget/settings/developer_settings.dart index 600be86f..7f38573f 100644 --- a/app/lib/widget/settings/developer_settings.dart +++ b/app/lib/widget/settings/developer_settings.dart @@ -17,7 +17,6 @@ import 'package:nc_photos/mobile/self_signed_cert_manager.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/widget/page_visibility_mixin.dart'; import 'package:np_codegen/np_codegen.dart'; -import 'package:np_platform_lock/np_platform_lock.dart'; import 'package:np_platform_util/np_platform_util.dart'; import 'package:to_string/to_string.dart'; diff --git a/app/pubspec.lock b/app/pubspec.lock index f5e5563b..5d0e9a45 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1011,6 +1011,13 @@ packages: relative: true source: path version: "1.0.0" + np_platform_image_processor: + dependency: "direct main" + description: + path: "../np_platform_image_processor" + relative: true + source: path + version: "0.0.1" np_platform_lock: dependency: "direct main" description: @@ -1018,6 +1025,20 @@ packages: relative: true source: path version: "0.0.1" + np_platform_permission: + dependency: "direct main" + description: + path: "../np_platform_permission" + relative: true + source: path + version: "0.0.1" + np_platform_raw_image: + dependency: "direct main" + description: + path: "../np_platform_raw_image" + relative: true + source: path + version: "0.0.1" np_platform_util: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 379405a4..dc05a1cf 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -109,8 +109,14 @@ dependencies: path: ../np_log np_math: path: ../np_math + np_platform_image_processor: + path: ../np_platform_image_processor np_platform_lock: path: ../np_platform_lock + np_platform_permission: + path: ../np_platform_permission + np_platform_raw_image: + path: ../np_platform_raw_image np_platform_util: path: ../np_platform_util np_string: diff --git a/np_android_log/.gitignore b/np_android_core/.gitignore similarity index 100% rename from np_android_log/.gitignore rename to np_android_core/.gitignore diff --git a/np_android_log/build.gradle b/np_android_core/build.gradle similarity index 77% rename from np_android_log/build.gradle rename to np_android_core/build.gradle index 27b5c56a..2ab929c5 100644 --- a/np_android_log/build.gradle +++ b/np_android_core/build.gradle @@ -1,4 +1,4 @@ -group 'com.nkming.nc_photos.np_android_log' +group 'com.nkming.nc_photos.np_android_core' version '1.0-SNAPSHOT' buildscript { @@ -26,7 +26,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - namespace 'com.nkming.nc_photos.np_android_log' + namespace 'com.nkming.nc_photos.np_android_core' compileSdk 31 defaultConfig { @@ -51,4 +51,7 @@ android { } dependencies { + implementation "androidx.annotation:annotation:1.6.0" + implementation "androidx.core:core-ktx:1.10.1" + implementation "androidx.exifinterface:exifinterface:1.3.6" } diff --git a/np_android_log/gradle.properties b/np_android_core/gradle.properties similarity index 100% rename from np_android_log/gradle.properties rename to np_android_core/gradle.properties diff --git a/np_android_log/gradle/wrapper/gradle-wrapper.properties b/np_android_core/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from np_android_log/gradle/wrapper/gradle-wrapper.properties rename to np_android_core/gradle/wrapper/gradle-wrapper.properties diff --git a/np_android_core/settings.gradle b/np_android_core/settings.gradle new file mode 100644 index 00000000..283e3d39 --- /dev/null +++ b/np_android_core/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "np_android_core" diff --git a/np_android_log/src/main/AndroidManifest.xml b/np_android_core/src/main/AndroidManifest.xml similarity index 100% rename from np_android_log/src/main/AndroidManifest.xml rename to np_android_core/src/main/AndroidManifest.xml 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 new file mode 100644 index 00000000..6b931b8f --- /dev/null +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/BitmapUtil.kt @@ -0,0 +1,244 @@ +package com.nkming.nc_photos.np_android_core + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Matrix +import android.net.Uri +import androidx.exifinterface.media.ExifInterface +import java.io.InputStream +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +fun Bitmap.aspectRatio() = width / height.toFloat() + +/** + * Recycle the bitmap after @c block returns. + * + * @param block + * @return + */ +@OptIn(ExperimentalContracts::class) +inline fun Bitmap.use(block: (Bitmap) -> T): T { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + try { + return block(this) + } finally { + recycle() + } +} + +enum class BitmapResizeMethod { + 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) + } + + /** + * 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 + } + } + + /** + * 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_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 + } + } + + 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 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 + ) + + BitmapResizeMethod.FILL -> minOf( + originalW / targetW, originalH / targetH + ) + } + } + + private const val TAG = "BitmapUtil" + } +} diff --git a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Exception.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Exception.kt new file mode 100644 index 00000000..b9a2f383 --- /dev/null +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Exception.kt @@ -0,0 +1,3 @@ +package com.nkming.nc_photos.np_android_core + +class PermissionException(message: String) : Exception(message) diff --git a/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/K.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/K.kt new file mode 100644 index 00000000..4f68678e --- /dev/null +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/K.kt @@ -0,0 +1,7 @@ +package com.nkming.nc_photos.np_android_core + +internal interface K { + companion object { + const val PERMISSION_REQUEST_CODE = 11011 + } +} diff --git a/np_android_log/src/main/java/com/nkming/nc_photos/np_android_log/Log.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Log.kt similarity index 96% rename from np_android_log/src/main/java/com/nkming/nc_photos/np_android_log/Log.kt rename to np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Log.kt index d47417fb..2fa69e5c 100644 --- a/np_android_log/src/main/java/com/nkming/nc_photos/np_android_log/Log.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Log.kt @@ -1,4 +1,4 @@ -package com.nkming.nc_photos.np_android_log +package com.nkming.nc_photos.np_android_core class LogConfig { companion object { diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreUtil.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/MediaStoreUtil.kt similarity index 98% rename from plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreUtil.kt rename to np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/MediaStoreUtil.kt index a5268e0a..af991205 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/MediaStoreUtil.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/MediaStoreUtil.kt @@ -1,4 +1,4 @@ -package com.nkming.nc_photos.plugin +package com.nkming.nc_photos.np_android_core import android.content.ContentValues import android.content.Context 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 new file mode 100644 index 00000000..d83c1bd6 --- /dev/null +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/PermissionUtil.kt @@ -0,0 +1,42 @@ +package com.nkming.nc_photos.np_android_core + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +interface PermissionUtil { + companion object { + const val REQUEST_CODE = K.PERMISSION_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 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 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 new file mode 100644 index 00000000..ffcc8930 --- /dev/null +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Rgba8Image.kt @@ -0,0 +1,44 @@ +package com.nkming.nc_photos.np_android_core + +import android.graphics.Bitmap +import java.nio.ByteBuffer + +/** + * Container of pixel data stored in RGBA format + */ +class Rgba8Image( + 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 + ) + + 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 toBitmap(): Bitmap { + return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + .apply { + copyPixelsFromBuffer(ByteBuffer.wrap(pixel)) + } + } + + init { + assert(pixel.size == width * height * 4) + } +} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/UriUtil.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/UriUtil.kt similarity index 92% rename from plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/UriUtil.kt rename to np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/UriUtil.kt index 6bf2e987..9f1faedb 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/UriUtil.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/UriUtil.kt @@ -1,9 +1,8 @@ -package com.nkming.nc_photos.plugin +package com.nkming.nc_photos.np_android_core import android.content.Context import android.net.Uri import android.provider.MediaStore -import com.nkming.nc_photos.np_android_log.logI interface UriUtil { companion object { diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Util.kt b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Util.kt similarity index 92% rename from plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Util.kt rename to np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Util.kt index 06cceac7..592cd02e 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Util.kt +++ b/np_android_core/src/main/java/com/nkming/nc_photos/np_android_core/Util.kt @@ -1,9 +1,8 @@ -package com.nkming.nc_photos.plugin +package com.nkming.nc_photos.np_android_core import android.app.PendingIntent import android.os.Build import android.os.Bundle -import com.nkming.nc_photos.np_android_log.logI import java.io.Serializable import java.net.HttpURLConnection diff --git a/np_android_log/consumer-rules.pro b/np_android_log/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/np_android_log/proguard-rules.pro b/np_android_log/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/np_android_log/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/np_android_log/settings.gradle b/np_android_log/settings.gradle deleted file mode 100644 index 1ab3fdc6..00000000 --- a/np_android_log/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "np_android_log" diff --git a/np_platform_image_processor/.gitignore b/np_platform_image_processor/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_platform_image_processor/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/np_platform_image_processor/.metadata b/np_platform_image_processor/.metadata new file mode 100644 index 00000000..9c0286ed --- /dev/null +++ b/np_platform_image_processor/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f72efea43c3013323d1b95cff571f3c1caa37583 + channel: stable + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + base_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + - platform: android + create_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + base_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + - platform: ios + create_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + base_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/np_platform_image_processor/analysis_options.yaml b/np_platform_image_processor/analysis_options.yaml new file mode 100644 index 00000000..f92d2567 --- /dev/null +++ b/np_platform_image_processor/analysis_options.yaml @@ -0,0 +1 @@ +include: package:np_lints/np.yaml diff --git a/np_platform_image_processor/android/.gitignore b/np_platform_image_processor/android/.gitignore new file mode 100644 index 00000000..161bdcda --- /dev/null +++ b/np_platform_image_processor/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/np_platform_image_processor/android/build.gradle b/np_platform_image_processor/android/build.gradle new file mode 100644 index 00000000..c3c66435 --- /dev/null +++ b/np_platform_image_processor/android/build.gradle @@ -0,0 +1,76 @@ +group 'com.nkming.nc_photos.np_platform_image_processor' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.8.20' + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace 'com.nkming.nc_photos.np_platform_image_processor' + compileSdk 31 + ndkVersion "23.2.8568313" + + defaultConfig { + minSdk 21 + ndk { + abiFilters "armeabi-v7a", "arm64-v8a", "x86_64" + } + + consumerProguardFiles "consumer-rules.pro" + } + externalNativeBuild { + cmake { + path file('src/main/cpp/CMakeLists.txt') + version '3.18.1' + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + lintOptions { + disable 'LongLogTag' + } +} + +dependencies { + coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3" + implementation "androidx.annotation:annotation:1.6.0" + implementation "androidx.core:core-ktx:1.10.1" + implementation "androidx.exifinterface:exifinterface:1.3.6" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'com.nkming.nc_photos.np_android_core:np_android_core' +} diff --git a/plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/headers/RenderScriptToolkit.h b/np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/headers/RenderScriptToolkit.h similarity index 100% rename from plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/headers/RenderScriptToolkit.h rename to np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/headers/RenderScriptToolkit.h diff --git a/plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/arm64-v8a/librenderscript-toolkit.so b/np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/arm64-v8a/librenderscript-toolkit.so similarity index 100% rename from plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/arm64-v8a/librenderscript-toolkit.so rename to np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/arm64-v8a/librenderscript-toolkit.so diff --git a/plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/armeabi-v7a/librenderscript-toolkit.so b/np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/armeabi-v7a/librenderscript-toolkit.so similarity index 100% rename from plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/armeabi-v7a/librenderscript-toolkit.so rename to np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/armeabi-v7a/librenderscript-toolkit.so diff --git a/plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/x86_64/librenderscript-toolkit.so b/np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/x86_64/librenderscript-toolkit.so similarity index 100% rename from plugin/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/x86_64/librenderscript-toolkit.so rename to np_platform_image_processor/android/dependency/renderscript-intrinsics-replacement-toolkit/jni/x86_64/librenderscript-toolkit.so diff --git a/plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/builtin_ops.h b/np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/builtin_ops.h similarity index 100% rename from plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/builtin_ops.h rename to np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/builtin_ops.h diff --git a/plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api.h b/np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api.h similarity index 100% rename from plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api.h rename to np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api.h diff --git a/plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_experimental.h b/np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_experimental.h similarity index 100% rename from plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_experimental.h rename to np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_experimental.h diff --git a/plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_types.h b/np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_types.h similarity index 100% rename from plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_types.h rename to np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/c_api_types.h diff --git a/plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/common.h b/np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/common.h similarity index 100% rename from plugin/android/dependency/tensorflowlite/headers/tensorflow/lite/c/common.h rename to np_platform_image_processor/android/dependency/tensorflowlite/headers/tensorflow/lite/c/common.h diff --git a/plugin/android/dependency/tensorflowlite/jni/arm64-v8a/libtensorflowlite_jni.so b/np_platform_image_processor/android/dependency/tensorflowlite/jni/arm64-v8a/libtensorflowlite_jni.so similarity index 100% rename from plugin/android/dependency/tensorflowlite/jni/arm64-v8a/libtensorflowlite_jni.so rename to np_platform_image_processor/android/dependency/tensorflowlite/jni/arm64-v8a/libtensorflowlite_jni.so diff --git a/plugin/android/dependency/tensorflowlite/jni/armeabi-v7a/libtensorflowlite_jni.so b/np_platform_image_processor/android/dependency/tensorflowlite/jni/armeabi-v7a/libtensorflowlite_jni.so similarity index 100% rename from plugin/android/dependency/tensorflowlite/jni/armeabi-v7a/libtensorflowlite_jni.so rename to np_platform_image_processor/android/dependency/tensorflowlite/jni/armeabi-v7a/libtensorflowlite_jni.so diff --git a/plugin/android/dependency/tensorflowlite/jni/x86_64/libtensorflowlite_jni.so b/np_platform_image_processor/android/dependency/tensorflowlite/jni/x86_64/libtensorflowlite_jni.so similarity index 100% rename from plugin/android/dependency/tensorflowlite/jni/x86_64/libtensorflowlite_jni.so rename to np_platform_image_processor/android/dependency/tensorflowlite/jni/x86_64/libtensorflowlite_jni.so diff --git a/np_platform_image_processor/android/gradle.properties b/np_platform_image_processor/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/np_platform_image_processor/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/np_platform_image_processor/android/gradle/wrapper/gradle-wrapper.properties b/np_platform_image_processor/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..4a71a623 --- /dev/null +++ b/np_platform_image_processor/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip diff --git a/np_platform_image_processor/android/settings.gradle b/np_platform_image_processor/android/settings.gradle new file mode 100644 index 00000000..f2077974 --- /dev/null +++ b/np_platform_image_processor/android/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'np_platform_image_processor' +includeBuild '../../np_android_core' diff --git a/np_platform_image_processor/android/src/main/AndroidManifest.xml b/np_platform_image_processor/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c6492c37 --- /dev/null +++ b/np_platform_image_processor/android/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_predict_1.tflite b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_predict_1.tflite similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_predict_1.tflite rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_predict_1.tflite diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_transfer_1.tflite b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_transfer_1.tflite similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_transfer_1.tflite rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer-inceptionv3_dr_transfer_1.tflite diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer/1.jpg b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/1.jpg similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer/1.jpg rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/1.jpg diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer/2.jpg b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/2.jpg similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer/2.jpg rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/2.jpg diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer/3.jpg b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/3.jpg similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer/3.jpg rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/3.jpg diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer/4.jpg b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/4.jpg similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer/4.jpg rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/4.jpg diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer/5.jpg b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/5.jpg similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer/5.jpg rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/5.jpg diff --git a/plugin/android/src/main/assets/tf/arbitrary-style-transfer/6.jpg b/np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/6.jpg similarity index 100% rename from plugin/android/src/main/assets/tf/arbitrary-style-transfer/6.jpg rename to np_platform_image_processor/android/src/main/assets/tf/arbitrary-style-transfer/6.jpg diff --git a/plugin/android/src/main/assets/tf/esrgan-tf2_1-dr.tflite b/np_platform_image_processor/android/src/main/assets/tf/esrgan-tf2_1-dr.tflite similarity index 100% rename from plugin/android/src/main/assets/tf/esrgan-tf2_1-dr.tflite rename to np_platform_image_processor/android/src/main/assets/tf/esrgan-tf2_1-dr.tflite diff --git a/plugin/android/src/main/assets/tf/lite-model_mobilenetv2-dm05-coco_dr_1.tflite b/np_platform_image_processor/android/src/main/assets/tf/lite-model_mobilenetv2-dm05-coco_dr_1.tflite similarity index 100% rename from plugin/android/src/main/assets/tf/lite-model_mobilenetv2-dm05-coco_dr_1.tflite rename to np_platform_image_processor/android/src/main/assets/tf/lite-model_mobilenetv2-dm05-coco_dr_1.tflite diff --git a/plugin/android/src/main/assets/tf/neurop_fivek_lite.tflite b/np_platform_image_processor/android/src/main/assets/tf/neurop_fivek_lite.tflite similarity index 100% rename from plugin/android/src/main/assets/tf/neurop_fivek_lite.tflite rename to np_platform_image_processor/android/src/main/assets/tf/neurop_fivek_lite.tflite diff --git a/plugin/android/src/main/assets/tf/zero_dce_lite_200x300_iter8_60.tflite b/np_platform_image_processor/android/src/main/assets/tf/zero_dce_lite_200x300_iter8_60.tflite similarity index 100% rename from plugin/android/src/main/assets/tf/zero_dce_lite_200x300_iter8_60.tflite rename to np_platform_image_processor/android/src/main/assets/tf/zero_dce_lite_200x300_iter8_60.tflite diff --git a/plugin/android/src/main/cpp/CMakeLists.txt b/np_platform_image_processor/android/src/main/cpp/CMakeLists.txt similarity index 92% rename from plugin/android/src/main/cpp/CMakeLists.txt rename to np_platform_image_processor/android/src/main/cpp/CMakeLists.txt index 76764557..5ac458ca 100644 --- a/plugin/android/src/main/cpp/CMakeLists.txt +++ b/np_platform_image_processor/android/src/main/cpp/CMakeLists.txt @@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.18.1) # Declares and names the project. -project("plugin") +project("np_platform_image_processor") # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. @@ -27,7 +27,7 @@ set_target_properties( renderscript-intrinsics-replacement-toolkit PROPERTIES IM ${dependency_DIR}/renderscript-intrinsics-replacement-toolkit/jni/${ANDROID_ABI}/librenderscript-toolkit.so ) add_library( # Sets the name of the library. - plugin + np_platform_image_processor # Sets the library as a shared library. SHARED @@ -62,7 +62,7 @@ add_library( # Sets the name of the library. util.cpp zero_dce.cpp ) -set_target_properties( plugin PROPERTIES COMPILE_OPTIONS -fopenmp ) +set_target_properties( np_platform_image_processor PROPERTIES COMPILE_OPTIONS -fopenmp ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by @@ -79,7 +79,7 @@ find_library( # Sets the name of the path variable. find_library( android-lib android ) -target_include_directories( plugin PRIVATE +target_include_directories( np_platform_image_processor PRIVATE ${dependency_DIR}/tensorflowlite/headers ${dependency_DIR}/renderscript-intrinsics-replacement-toolkit/headers ) @@ -88,7 +88,7 @@ target_include_directories( plugin PRIVATE # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. - plugin + np_platform_image_processor # Links the target library to the log library # included in the NDK. diff --git a/plugin/android/src/main/cpp/arbitrary_style_transfer.cpp b/np_platform_image_processor/android/src/main/cpp/arbitrary_style_transfer.cpp similarity index 98% rename from plugin/android/src/main/cpp/arbitrary_style_transfer.cpp rename to np_platform_image_processor/android/src/main/cpp/arbitrary_style_transfer.cpp index bc5ad5d7..6e0d9a34 100644 --- a/plugin/android/src/main/cpp/arbitrary_style_transfer.cpp +++ b/np_platform_image_processor/android/src/main/cpp/arbitrary_style_transfer.cpp @@ -15,7 +15,7 @@ #include "tflite_wrapper.h" #include "util.h" -using namespace plugin; +using namespace im_proc; using namespace std; using namespace tflite; @@ -60,7 +60,7 @@ private: } // namespace extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_ArbitraryStyleTransfer_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_ArbitraryStyleTransfer_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jbyteArray style, jfloat weight) { try { diff --git a/plugin/android/src/main/cpp/arbitrary_style_transfer.h b/np_platform_image_processor/android/src/main/cpp/arbitrary_style_transfer.h similarity index 71% rename from plugin/android/src/main/cpp/arbitrary_style_transfer.h rename to np_platform_image_processor/android/src/main/cpp/arbitrary_style_transfer.h index 0b1d1457..9e003944 100644 --- a/plugin/android/src/main/cpp/arbitrary_style_transfer.h +++ b/np_platform_image_processor/android/src/main/cpp/arbitrary_style_transfer.h @@ -7,7 +7,7 @@ extern "C" { #endif JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_ArbitraryStyleTransfer_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_ArbitraryStyleTransfer_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jbyteArray style, jfloat weight); diff --git a/plugin/android/src/main/cpp/core/filter/brightness.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/brightness.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/brightness.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/brightness.cpp diff --git a/plugin/android/src/main/cpp/core/filter/color_levels.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/color_levels.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/color_levels.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/color_levels.cpp diff --git a/plugin/android/src/main/cpp/core/filter/contrast.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/contrast.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/contrast.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/contrast.cpp diff --git a/plugin/android/src/main/cpp/core/filter/curve.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/curve.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/curve.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/curve.cpp diff --git a/plugin/android/src/main/cpp/core/filter/curve.h b/np_platform_image_processor/android/src/main/cpp/core/filter/curve.h similarity index 100% rename from plugin/android/src/main/cpp/core/filter/curve.h rename to np_platform_image_processor/android/src/main/cpp/core/filter/curve.h diff --git a/plugin/android/src/main/cpp/core/filter/filters.h b/np_platform_image_processor/android/src/main/cpp/core/filter/filters.h similarity index 100% rename from plugin/android/src/main/cpp/core/filter/filters.h rename to np_platform_image_processor/android/src/main/cpp/core/filter/filters.h diff --git a/plugin/android/src/main/cpp/core/filter/hslhsv.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/hslhsv.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/hslhsv.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/hslhsv.cpp diff --git a/plugin/android/src/main/cpp/core/filter/hslhsv.h b/np_platform_image_processor/android/src/main/cpp/core/filter/hslhsv.h similarity index 100% rename from plugin/android/src/main/cpp/core/filter/hslhsv.h rename to np_platform_image_processor/android/src/main/cpp/core/filter/hslhsv.h diff --git a/plugin/android/src/main/cpp/core/filter/saturation.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/saturation.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/saturation.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/saturation.cpp diff --git a/plugin/android/src/main/cpp/core/filter/saturation.h b/np_platform_image_processor/android/src/main/cpp/core/filter/saturation.h similarity index 100% rename from plugin/android/src/main/cpp/core/filter/saturation.h rename to np_platform_image_processor/android/src/main/cpp/core/filter/saturation.h diff --git a/plugin/android/src/main/cpp/core/filter/tint.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/tint.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/tint.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/tint.cpp diff --git a/plugin/android/src/main/cpp/core/filter/warmth.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/warmth.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/warmth.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/warmth.cpp diff --git a/plugin/android/src/main/cpp/core/filter/yuv.cpp b/np_platform_image_processor/android/src/main/cpp/core/filter/yuv.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/filter/yuv.cpp rename to np_platform_image_processor/android/src/main/cpp/core/filter/yuv.cpp diff --git a/plugin/android/src/main/cpp/core/filter/yuv.h b/np_platform_image_processor/android/src/main/cpp/core/filter/yuv.h similarity index 100% rename from plugin/android/src/main/cpp/core/filter/yuv.h rename to np_platform_image_processor/android/src/main/cpp/core/filter/yuv.h diff --git a/plugin/android/src/main/cpp/core/lib/base_resample.h b/np_platform_image_processor/android/src/main/cpp/core/lib/base_resample.h similarity index 100% rename from plugin/android/src/main/cpp/core/lib/base_resample.h rename to np_platform_image_processor/android/src/main/cpp/core/lib/base_resample.h diff --git a/plugin/android/src/main/cpp/core/lib/spline/LICENSE b/np_platform_image_processor/android/src/main/cpp/core/lib/spline/LICENSE similarity index 100% rename from plugin/android/src/main/cpp/core/lib/spline/LICENSE rename to np_platform_image_processor/android/src/main/cpp/core/lib/spline/LICENSE diff --git a/plugin/android/src/main/cpp/core/lib/spline/spline.cpp b/np_platform_image_processor/android/src/main/cpp/core/lib/spline/spline.cpp similarity index 100% rename from plugin/android/src/main/cpp/core/lib/spline/spline.cpp rename to np_platform_image_processor/android/src/main/cpp/core/lib/spline/spline.cpp diff --git a/plugin/android/src/main/cpp/core/lib/spline/spline.h b/np_platform_image_processor/android/src/main/cpp/core/lib/spline/spline.h similarity index 100% rename from plugin/android/src/main/cpp/core/lib/spline/spline.h rename to np_platform_image_processor/android/src/main/cpp/core/lib/spline/spline.h diff --git a/plugin/android/src/main/cpp/core/log.h b/np_platform_image_processor/android/src/main/cpp/core/log.h similarity index 100% rename from plugin/android/src/main/cpp/core/log.h rename to np_platform_image_processor/android/src/main/cpp/core/log.h diff --git a/plugin/android/src/main/cpp/core/math_util.h b/np_platform_image_processor/android/src/main/cpp/core/math_util.h similarity index 100% rename from plugin/android/src/main/cpp/core/math_util.h rename to np_platform_image_processor/android/src/main/cpp/core/math_util.h diff --git a/plugin/android/src/main/cpp/deep_lap_3.cpp b/np_platform_image_processor/android/src/main/cpp/deep_lap_3.cpp similarity index 98% rename from plugin/android/src/main/cpp/deep_lap_3.cpp rename to np_platform_image_processor/android/src/main/cpp/deep_lap_3.cpp index b62d47dc..bd7d1bda 100644 --- a/plugin/android/src/main/cpp/deep_lap_3.cpp +++ b/np_platform_image_processor/android/src/main/cpp/deep_lap_3.cpp @@ -18,7 +18,7 @@ #include "util.h" using namespace core; -using namespace plugin; +using namespace im_proc; using namespace renderscript; using namespace std; using namespace tflite; @@ -151,7 +151,7 @@ private: } // namespace extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3Portrait_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3Portrait_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jint radius) { try { @@ -176,7 +176,7 @@ Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3Portrait_inferNative( } extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3ColorPop_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3ColorPop_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jfloat weight) { try { diff --git a/plugin/android/src/main/cpp/deep_lap_3.h b/np_platform_image_processor/android/src/main/cpp/deep_lap_3.h similarity index 66% rename from plugin/android/src/main/cpp/deep_lap_3.h rename to np_platform_image_processor/android/src/main/cpp/deep_lap_3.h index 81aae761..2f54954a 100644 --- a/plugin/android/src/main/cpp/deep_lap_3.h +++ b/np_platform_image_processor/android/src/main/cpp/deep_lap_3.h @@ -7,12 +7,12 @@ extern "C" { #endif JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3Portrait_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3Portrait_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jint radius); JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_DeepLab3ColorPop_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_DeepLab3ColorPop_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jfloat weight); diff --git a/plugin/android/src/main/cpp/esrgan.cpp b/np_platform_image_processor/android/src/main/cpp/esrgan.cpp similarity index 97% rename from plugin/android/src/main/cpp/esrgan.cpp rename to np_platform_image_processor/android/src/main/cpp/esrgan.cpp index 94edec9c..4e0b0156 100644 --- a/plugin/android/src/main/cpp/esrgan.cpp +++ b/np_platform_image_processor/android/src/main/cpp/esrgan.cpp @@ -16,7 +16,7 @@ #include "tflite_wrapper.h" #include "util.h" -using namespace plugin; +using namespace im_proc; using namespace std; using namespace tflite; @@ -47,7 +47,7 @@ private: } // namespace extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Esrgan_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Esrgan_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height) { try { diff --git a/plugin/android/src/main/cpp/esrgan.h b/np_platform_image_processor/android/src/main/cpp/esrgan.h similarity index 72% rename from plugin/android/src/main/cpp/esrgan.h rename to np_platform_image_processor/android/src/main/cpp/esrgan.h index 759f43a4..e7d829bb 100644 --- a/plugin/android/src/main/cpp/esrgan.h +++ b/np_platform_image_processor/android/src/main/cpp/esrgan.h @@ -7,7 +7,7 @@ extern "C" { #endif JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Esrgan_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Esrgan_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height); diff --git a/plugin/android/src/main/cpp/exception.cpp b/np_platform_image_processor/android/src/main/cpp/exception.cpp similarity index 100% rename from plugin/android/src/main/cpp/exception.cpp rename to np_platform_image_processor/android/src/main/cpp/exception.cpp diff --git a/plugin/android/src/main/cpp/exception.h b/np_platform_image_processor/android/src/main/cpp/exception.h similarity index 100% rename from plugin/android/src/main/cpp/exception.h rename to np_platform_image_processor/android/src/main/cpp/exception.h diff --git a/plugin/android/src/main/cpp/filter/brightness.cpp b/np_platform_image_processor/android/src/main/cpp/filter/brightness.cpp similarity index 89% rename from plugin/android/src/main/cpp/filter/brightness.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/brightness.cpp index cc99355d..e0deee97 100644 --- a/plugin/android/src/main/cpp/filter/brightness.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/brightness.cpp @@ -7,11 +7,11 @@ #include "../util.h" using namespace core; -using namespace plugin; +using namespace im_proc; using namespace std; extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Brightness_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Brightness_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jfloat weight) { try { diff --git a/plugin/android/src/main/cpp/filter/color_levels.cpp b/np_platform_image_processor/android/src/main/cpp/filter/color_levels.cpp similarity index 89% rename from plugin/android/src/main/cpp/filter/color_levels.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/color_levels.cpp index 66826a6c..98d03722 100644 --- a/plugin/android/src/main/cpp/filter/color_levels.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/color_levels.cpp @@ -7,11 +7,11 @@ #include "../util.h" using namespace core; -using namespace plugin; +using namespace im_proc; using namespace std; extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_WhitePoint_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_WhitePoint_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jfloat weight) { try { @@ -34,7 +34,7 @@ Java_com_nkming_nc_1photos_plugin_image_1processor_WhitePoint_applyNative( } extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_BlackPoint_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_BlackPoint_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jfloat weight) { try { diff --git a/plugin/android/src/main/cpp/filter/contrast.cpp b/np_platform_image_processor/android/src/main/cpp/filter/contrast.cpp similarity index 89% rename from plugin/android/src/main/cpp/filter/contrast.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/contrast.cpp index c005f6bd..e8a4f90c 100644 --- a/plugin/android/src/main/cpp/filter/contrast.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/contrast.cpp @@ -7,11 +7,11 @@ #include "../util.h" using namespace core; -using namespace plugin; +using namespace im_proc; using namespace std; extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Contrast_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Contrast_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jfloat weight) { try { diff --git a/plugin/android/src/main/cpp/filter/crop.cpp b/np_platform_image_processor/android/src/main/cpp/filter/crop.cpp similarity index 94% rename from plugin/android/src/main/cpp/filter/crop.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/crop.cpp index 1d8e3815..c8a69750 100644 --- a/plugin/android/src/main/cpp/filter/crop.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/crop.cpp @@ -8,7 +8,7 @@ #include "../log.h" #include "../util.h" -using namespace plugin; +using namespace im_proc; using namespace std; namespace { @@ -26,7 +26,7 @@ private: } // namespace extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Crop_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Crop_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jint top, jint left, jint dstWidth, jint dstHeight) { try { diff --git a/plugin/android/src/main/cpp/filter/orientation.cpp b/np_platform_image_processor/android/src/main/cpp/filter/orientation.cpp similarity index 97% rename from plugin/android/src/main/cpp/filter/orientation.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/orientation.cpp index 3fdc7cc4..90d0fc2c 100644 --- a/plugin/android/src/main/cpp/filter/orientation.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/orientation.cpp @@ -7,7 +7,7 @@ #include "../log.h" #include "../util.h" -using namespace plugin; +using namespace im_proc; using namespace std; namespace { @@ -31,7 +31,7 @@ private: } // namespace extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Orientation_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Orientation_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jint degree) { try { diff --git a/plugin/android/src/main/cpp/filter/saturation.cpp b/np_platform_image_processor/android/src/main/cpp/filter/saturation.cpp similarity index 89% rename from plugin/android/src/main/cpp/filter/saturation.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/saturation.cpp index 1abfbe66..f77d968f 100644 --- a/plugin/android/src/main/cpp/filter/saturation.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/saturation.cpp @@ -7,11 +7,11 @@ #include "../util.h" using namespace core; -using namespace plugin; +using namespace im_proc; using namespace std; extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Saturation_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Saturation_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jfloat value) { try { diff --git a/plugin/android/src/main/cpp/filter/tint.cpp b/np_platform_image_processor/android/src/main/cpp/filter/tint.cpp similarity index 89% rename from plugin/android/src/main/cpp/filter/tint.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/tint.cpp index 2669f4b2..1474c4cb 100644 --- a/plugin/android/src/main/cpp/filter/tint.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/tint.cpp @@ -7,11 +7,11 @@ #include "../util.h" using namespace core; -using namespace plugin; +using namespace im_proc; using namespace std; extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Tint_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Tint_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jfloat weight) { try { diff --git a/plugin/android/src/main/cpp/filter/warmth.cpp b/np_platform_image_processor/android/src/main/cpp/filter/warmth.cpp similarity index 89% rename from plugin/android/src/main/cpp/filter/warmth.cpp rename to np_platform_image_processor/android/src/main/cpp/filter/warmth.cpp index e0793420..4256d9aa 100644 --- a/plugin/android/src/main/cpp/filter/warmth.cpp +++ b/np_platform_image_processor/android/src/main/cpp/filter/warmth.cpp @@ -7,11 +7,11 @@ #include "../util.h" using namespace core; -using namespace plugin; +using namespace im_proc; using namespace std; extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_Warmth_applyNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_Warmth_applyNative( JNIEnv *env, jobject *thiz, jbyteArray rgba8, jint width, jint height, jfloat weight) { try { diff --git a/plugin/android/src/main/cpp/image_splitter.cpp b/np_platform_image_processor/android/src/main/cpp/image_splitter.cpp similarity index 98% rename from plugin/android/src/main/cpp/image_splitter.cpp rename to np_platform_image_processor/android/src/main/cpp/image_splitter.cpp index 40205928..3252bdcd 100644 --- a/plugin/android/src/main/cpp/image_splitter.cpp +++ b/np_platform_image_processor/android/src/main/cpp/image_splitter.cpp @@ -10,7 +10,7 @@ using namespace std; -namespace plugin { +namespace im_proc { ImageTile::ImageTile() : _width(0), _height(0), _channel(3) {} @@ -73,4 +73,4 @@ ImageSplitter::operator()(const uint8_t *image, const size_t width, return product; } -} // namespace plugin +} // namespace im_proc diff --git a/plugin/android/src/main/cpp/image_splitter.h b/np_platform_image_processor/android/src/main/cpp/image_splitter.h similarity index 97% rename from plugin/android/src/main/cpp/image_splitter.h rename to np_platform_image_processor/android/src/main/cpp/image_splitter.h index 44e90b07..91564b45 100644 --- a/plugin/android/src/main/cpp/image_splitter.h +++ b/np_platform_image_processor/android/src/main/cpp/image_splitter.h @@ -2,7 +2,7 @@ #include #include -namespace plugin { +namespace im_proc { class ImageTile { public: @@ -51,4 +51,4 @@ private: static constexpr const char *TAG = "ImageSplitter"; }; -} // namespace plugin +} // namespace im_proc diff --git a/plugin/android/src/main/cpp/log.h b/np_platform_image_processor/android/src/main/cpp/log.h similarity index 100% rename from plugin/android/src/main/cpp/log.h rename to np_platform_image_processor/android/src/main/cpp/log.h diff --git a/plugin/android/src/main/cpp/math_util.h b/np_platform_image_processor/android/src/main/cpp/math_util.h similarity index 78% rename from plugin/android/src/main/cpp/math_util.h rename to np_platform_image_processor/android/src/main/cpp/math_util.h index effb960c..77cd834c 100644 --- a/plugin/android/src/main/cpp/math_util.h +++ b/np_platform_image_processor/android/src/main/cpp/math_util.h @@ -2,10 +2,10 @@ #include -namespace plugin { +namespace im_proc { template inline T clamp(const T &min, const T &x, const T &max) { return std::max(min, std::min(x, max)); } -} // namespace plugin +} // namespace im_proc diff --git a/plugin/android/src/main/cpp/neur_op.cpp b/np_platform_image_processor/android/src/main/cpp/neur_op.cpp similarity index 96% rename from plugin/android/src/main/cpp/neur_op.cpp rename to np_platform_image_processor/android/src/main/cpp/neur_op.cpp index 71f3e811..1f574d8a 100644 --- a/plugin/android/src/main/cpp/neur_op.cpp +++ b/np_platform_image_processor/android/src/main/cpp/neur_op.cpp @@ -11,7 +11,7 @@ #include "tflite_wrapper.h" #include "util.h" -using namespace plugin; +using namespace im_proc; using namespace std; using namespace tflite; @@ -35,7 +35,7 @@ private: } // namespace extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_NeurOp_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_NeurOp_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height) { try { diff --git a/plugin/android/src/main/cpp/neur_op.h b/np_platform_image_processor/android/src/main/cpp/neur_op.h similarity index 72% rename from plugin/android/src/main/cpp/neur_op.h rename to np_platform_image_processor/android/src/main/cpp/neur_op.h index 4c4c504e..e41c5c0c 100644 --- a/plugin/android/src/main/cpp/neur_op.h +++ b/np_platform_image_processor/android/src/main/cpp/neur_op.h @@ -7,7 +7,7 @@ extern "C" { #endif JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_NeurOp_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_NeurOp_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height); diff --git a/plugin/android/src/main/cpp/stopwatch.cpp b/np_platform_image_processor/android/src/main/cpp/stopwatch.cpp similarity index 100% rename from plugin/android/src/main/cpp/stopwatch.cpp rename to np_platform_image_processor/android/src/main/cpp/stopwatch.cpp diff --git a/plugin/android/src/main/cpp/stopwatch.h b/np_platform_image_processor/android/src/main/cpp/stopwatch.h similarity index 100% rename from plugin/android/src/main/cpp/stopwatch.h rename to np_platform_image_processor/android/src/main/cpp/stopwatch.h diff --git a/plugin/android/src/main/cpp/tflite_wrapper.cpp b/np_platform_image_processor/android/src/main/cpp/tflite_wrapper.cpp similarity index 99% rename from plugin/android/src/main/cpp/tflite_wrapper.cpp rename to np_platform_image_processor/android/src/main/cpp/tflite_wrapper.cpp index d1632d05..fc33be0d 100644 --- a/plugin/android/src/main/cpp/tflite_wrapper.cpp +++ b/np_platform_image_processor/android/src/main/cpp/tflite_wrapper.cpp @@ -4,7 +4,7 @@ #include "tflite_wrapper.h" #include "util.h" -using namespace plugin; +using namespace im_proc; using namespace std; namespace tflite { diff --git a/plugin/android/src/main/cpp/tflite_wrapper.h b/np_platform_image_processor/android/src/main/cpp/tflite_wrapper.h similarity index 95% rename from plugin/android/src/main/cpp/tflite_wrapper.h rename to np_platform_image_processor/android/src/main/cpp/tflite_wrapper.h index e31fa41e..fe185f88 100644 --- a/plugin/android/src/main/cpp/tflite_wrapper.h +++ b/np_platform_image_processor/android/src/main/cpp/tflite_wrapper.h @@ -9,7 +9,7 @@ namespace tflite { class Model { public: - explicit Model(plugin::Asset &&asset); + explicit Model(im_proc::Asset &&asset); Model(Model &&rhs); ~Model(); @@ -18,7 +18,7 @@ public: const size_t getSize() const; private: - plugin::Asset asset; + im_proc::Asset asset; TfLiteModel *model; }; diff --git a/plugin/android/src/main/cpp/util.cpp b/np_platform_image_processor/android/src/main/cpp/util.cpp similarity index 98% rename from plugin/android/src/main/cpp/util.cpp rename to np_platform_image_processor/android/src/main/cpp/util.cpp index 987cdc72..4382a6da 100644 --- a/plugin/android/src/main/cpp/util.cpp +++ b/np_platform_image_processor/android/src/main/cpp/util.cpp @@ -15,10 +15,10 @@ #include "log.h" #include "math_util.h" -using namespace plugin; +using namespace im_proc; using namespace std; -namespace plugin { +namespace im_proc { Asset::Asset(AAssetManager *const aam, const string &name, const int mode) { asset = AAssetManager_open(aam, name.c_str(), AASSET_MODE_BUFFER); @@ -193,4 +193,4 @@ vector argmax(const float *output, const size_t width, return product; } -} // namespace plugin +} // namespace im_proc diff --git a/plugin/android/src/main/cpp/util.h b/np_platform_image_processor/android/src/main/cpp/util.h similarity index 98% rename from plugin/android/src/main/cpp/util.h rename to np_platform_image_processor/android/src/main/cpp/util.h index 7e5c4f4d..496990d5 100644 --- a/plugin/android/src/main/cpp/util.h +++ b/np_platform_image_processor/android/src/main/cpp/util.h @@ -11,7 +11,7 @@ namespace renderscript { class RenderScriptToolkit; } -namespace plugin { +namespace im_proc { template class RaiiContainer { public: @@ -101,4 +101,4 @@ void alphaBlend(const uint8_t *src, uint8_t *dst, const size_t width, std::vector argmax(const float *output, const size_t width, const size_t height, const unsigned channel); -} // namespace plugin +} // namespace im_proc diff --git a/plugin/android/src/main/cpp/zero_dce.cpp b/np_platform_image_processor/android/src/main/cpp/zero_dce.cpp similarity index 97% rename from plugin/android/src/main/cpp/zero_dce.cpp rename to np_platform_image_processor/android/src/main/cpp/zero_dce.cpp index 528a7349..bfcdfa01 100644 --- a/plugin/android/src/main/cpp/zero_dce.cpp +++ b/np_platform_image_processor/android/src/main/cpp/zero_dce.cpp @@ -13,7 +13,7 @@ #include "tflite_wrapper.h" #include "util.h" -using namespace plugin; +using namespace im_proc; using namespace std; using namespace tflite; @@ -46,7 +46,7 @@ private: } // namespace extern "C" JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_ZeroDce_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_ZeroDce_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jint iteration) { try { diff --git a/plugin/android/src/main/cpp/zero_dce.h b/np_platform_image_processor/android/src/main/cpp/zero_dce.h similarity index 73% rename from plugin/android/src/main/cpp/zero_dce.h rename to np_platform_image_processor/android/src/main/cpp/zero_dce.h index b1425a1b..9d5dd931 100644 --- a/plugin/android/src/main/cpp/zero_dce.h +++ b/np_platform_image_processor/android/src/main/cpp/zero_dce.h @@ -7,7 +7,7 @@ extern "C" { #endif JNIEXPORT jbyteArray JNICALL -Java_com_nkming_nc_1photos_plugin_image_1processor_ZeroDce_inferNative( +Java_com_nkming_nc_1photos_np_1platform_1image_1processor_processor_ZeroDce_inferNative( JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, jint width, jint height, jint iteration); 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 new file mode 100644 index 00000000..4e9d3922 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Event.kt @@ -0,0 +1,13 @@ +package com.nkming.nc_photos.np_platform_image_processor + +import android.net.Uri + +internal interface MessageEvent + +internal data class ImageProcessorCompletedEvent( + val result: Uri, +) : MessageEvent + +internal data class ImageProcessorFailedEvent( + 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 new file mode 100644 index 00000000..b58a0d3b --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/Exception.kt @@ -0,0 +1,6 @@ +package com.nkming.nc_photos.np_platform_image_processor + +internal class HttpException(statusCode: Int, message: String) : + 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 new file mode 100644 index 00000000..43f9bd9b --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorChannelHandler.kt @@ -0,0 +1,406 @@ +package com.nkming.nc_photos.np_platform_image_processor + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.core.content.ContextCompat +import com.nkming.nc_photos.np_android_core.Rgba8Image +import com.nkming.nc_photos.np_android_core.logE +import com.nkming.nc_photos.np_platform_image_processor.processor.BlackPoint +import com.nkming.nc_photos.np_platform_image_processor.processor.Brightness +import com.nkming.nc_photos.np_platform_image_processor.processor.Contrast +import com.nkming.nc_photos.np_platform_image_processor.processor.Crop +import com.nkming.nc_photos.np_platform_image_processor.processor.Orientation +import com.nkming.nc_photos.np_platform_image_processor.processor.Saturation +import com.nkming.nc_photos.np_platform_image_processor.processor.Tint +import com.nkming.nc_photos.np_platform_image_processor.processor.Warmth +import com.nkming.nc_photos.np_platform_image_processor.processor.WhitePoint +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +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" + + 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) + } + } + + "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) + } + } + + "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) + } + } + + "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) + } + } + + "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() + } + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink) { + eventSink = events + } + + 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 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 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 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 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 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 + ) + + else -> throw IllegalArgumentException( + "Unknown type: ${json["type"]}" + ) + } + } + } + + 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 new file mode 100644 index 00000000..69bc4af8 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/ImageProcessorService.kt @@ -0,0 +1,983 @@ +package com.nkming.nc_photos.np_platform_image_processor + +import android.annotation.SuppressLint +import android.app.Notification +import android.app.PendingIntent +import android.app.Service +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.AsyncTask +import android.os.Bundle +import android.os.IBinder +import android.os.PowerManager +import android.webkit.MimeTypeMap +import androidx.core.app.NotificationChannelCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.exifinterface.media.ExifInterface +import com.nkming.nc_photos.np_android_core.* +import com.nkming.nc_photos.np_platform_image_processor.processor.* +import java.io.File +import java.io.Serializable +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" + + 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" + + const val TAG = "ImageProcessorService" + } + + override fun onBind(intent: Intent?): IBinder? = null + + @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 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()) + } + + 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 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!! + ) + + METHOD_ESRGAN -> onEsrgan(startId, intent.extras!!) + METHOD_ARBITRARY_STYLE_TRANSFER -> onArbitraryStyleTransfer( + 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 + ) + ) + ) + } + } + } + + 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 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 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 onFilter(startId: Int, extras: Bundle) { + val filters = extras.getSerializable(EXTRA_FILTERS)!! + .asType>() + .map { ImageFilter.fromJson(it.asType>()) } + + 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 + ) + ) + } + + /** + * 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 + ) + ) + ) + } + + 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 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 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 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( + @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) + } + } + + 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) + } + } + + 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 interface ImageProcessorCommand + +private abstract class ImageProcessorImageCommand( + 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, + ) + + 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 +} + +private class ImageProcessorDummyCommand( + params: Params, +) : ImageProcessorImageCommand(params) { + override fun apply(context: Context, fileUri: Uri): Bitmap { + throw UnsupportedOperationException() + } + + override fun isEnhanceCommand() = true +} + +private class ImageProcessorZeroDceCommand( + 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 isEnhanceCommand() = true +} + +private class ImageProcessorDeepLapPortraitCommand( + 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 isEnhanceCommand() = true +} + +private class ImageProcessorEsrganCommand( + params: Params, +) : ImageProcessorImageCommand(params) { + override fun apply(context: Context, fileUri: Uri): Bitmap { + return Esrgan(context, maxWidth, maxHeight).infer(fileUri) + } + + override fun isEnhanceCommand() = true +} + +private class ImageProcessorArbitraryStyleTransferCommand( + 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 isEnhanceCommand() = true +} + +private class ImageProcessorDeepLapColorPopCommand( + 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 isEnhanceCommand() = true +} + +private class ImageProcessorNeurOpCommand( + params: Params, +) : ImageProcessorImageCommand(params) { + override fun apply(context: Context, fileUri: Uri): Bitmap { + return NeurOp(context, maxWidth, maxHeight).infer(fileUri) + } + + override fun isEnhanceCommand() = true +} + +private class ImageProcessorFilterCommand( + 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 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 +// 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, + ) + + 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) + } + } + + 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 + ) + } + } + } + + 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 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() + } + } + + 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) + } + + // 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() + } + } + + 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 getPersister(isSaveToServer: Boolean): EnhancedFilePersister { + return if (isSaveToServer) { + EnhancedFileServerPersisterWithFallback(context) + } else { + EnhancedFileDevicePersister(context) + } + } + + @SuppressLint("StaticFieldLeak") + private val context = context +} + +private interface AsyncTaskCancellable { + 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 +} + +private interface EnhancedFilePersister { + 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 + } + + private fun getSubDir(cmd: ImageProcessorImageCommand): String { + return if (!cmd.isEnhanceCommand()) { + "Edited Photos" + } else { + "Enhanced Photos" + } + } + + val context = context +} + +private class EnhancedFileServerPersister : EnhancedFilePersister { + 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) + } + + 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" + } + + 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) +} 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 new file mode 100644 index 00000000..e1c3dabb --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/K.kt @@ -0,0 +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" + + 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 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 new file mode 100644 index 00000000..a9362352 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEvent.kt @@ -0,0 +1,15 @@ +package com.nkming.nc_photos.np_platform_image_processor + +// To be removed +internal interface NativeEvent { + fun getId(): String + fun getData(): String? = null +} + +internal class ImageProcessorUploadSuccessEvent : NativeEvent { + companion object { + const val id = "ImageProcessorUploadSuccessEvent" + } + + 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 new file mode 100644 index 00000000..65524a31 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NativeEventChannelHandler.kt @@ -0,0 +1,72 @@ +package com.nkming.nc_photos.np_platform_image_processor + +import io.flutter.plugin.common.EventChannel +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" + + /** + * 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 + } + + 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 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 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 new file mode 100644 index 00000000..2682e0d9 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/NpPlatformImageProcessorPlugin.kt @@ -0,0 +1,57 @@ +package com.nkming.nc_photos.np_platform_image_processor + +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodChannel + +class NpPlatformImageProcessorPlugin : FlutterPlugin { + 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 + } + + 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) + } + + 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 +} 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 new file mode 100644 index 00000000..9d9e4ac9 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ArbitraryStyleTransfer.kt @@ -0,0 +1,88 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import android.content.Context +import android.content.res.AssetManager +import android.graphics.Bitmap +import android.net.Uri +import com.nkming.nc_photos.np_android_core.BitmapResizeMethod +import com.nkming.nc_photos.np_android_core.BitmapUtil +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 +) { + 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 + + 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 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 new file mode 100644 index 00000000..923a390f --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/BlackPoint.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..f6a6d12d --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Brightness.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..b92574df --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Contrast.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..25c80169 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Cool.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..44c81f44 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Crop.kt @@ -0,0 +1,31 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import com.nkming.nc_photos.np_android_core.Rgba8Image +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 +) : 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) + } + + 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 new file mode 100644 index 00000000..3b0d6dc5 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/DeepLab3.kt @@ -0,0 +1,94 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import android.content.Context +import android.content.res.AssetManager +import android.graphics.Bitmap +import android.net.Uri +import com.nkming.nc_photos.np_android_core.BitmapResizeMethod +import com.nkming.nc_photos.np_android_core.BitmapUtil +import com.nkming.nc_photos.np_android_core.use + +/** + * DeepLab is a state-of-art deep learning model for semantic image + * segmentation, where the goal is to assign semantic labels (e.g., person, dog, + * cat and so on) to every pixel in the input image + * + * See: https://github.com/tensorflow/models/tree/master/research/deeplab + */ +internal class DeepLab3Portrait( + 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 + + 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 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 +) { + 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) + } + } + + 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 +} 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 new file mode 100644 index 00000000..0db537bd --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Esrgan.kt @@ -0,0 +1,43 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import android.content.Context +import android.content.res.AssetManager +import android.graphics.Bitmap +import android.net.Uri +import com.nkming.nc_photos.np_android_core.BitmapResizeMethod +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 + + 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 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 new file mode 100644 index 00000000..4789cb20 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ImageFilterProcessor.kt @@ -0,0 +1,43 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import android.content.Context +import android.graphics.Bitmap +import android.net.Uri +import com.nkming.nc_photos.np_android_core.BitmapResizeMethod +import com.nkming.nc_photos.np_android_core.BitmapUtil +import com.nkming.nc_photos.np_android_core.Rgba8Image +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 +) { + 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) + } + + 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 +} 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 new file mode 100644 index 00000000..295a0cc4 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/LosslessRotator.kt @@ -0,0 +1,82 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import androidx.exifinterface.media.ExifInterface +import com.nkming.nc_photos.np_android_core.logI + +/** + * Lossless rotation is done by modifying the EXIF orientation tag in such a way + * that the viewer will rotate the image when displaying the image + */ +internal class 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() + ) + } + + /** + * 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" + ) + } + } +} 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 new file mode 100644 index 00000000..f2b63482 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/NeurOp.kt @@ -0,0 +1,43 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import android.content.Context +import android.content.res.AssetManager +import android.graphics.Bitmap +import android.net.Uri +import com.nkming.nc_photos.np_android_core.BitmapResizeMethod +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 + + 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 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 new file mode 100644 index 00000000..37bc1c2a --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Orientation.kt @@ -0,0 +1,20 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import com.nkming.nc_photos.np_android_core.Rgba8Image +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 + ) + } + + 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 new file mode 100644 index 00000000..704eeebf --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Saturation.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..beacf6c9 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/TfLiteHelper.kt @@ -0,0 +1,84 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import android.graphics.Bitmap +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 + } + + /** + * 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 + } + + 1 -> { + // G + pixel = pixel or (value shl 8) + } + + 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 + } + } +} 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 new file mode 100644 index 00000000..d504a826 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Tint.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..0c746666 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/Warmth.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..7487e46d --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/WhitePoint.kt @@ -0,0 +1,16 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +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 + ) + + 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 new file mode 100644 index 00000000..43f41c91 --- /dev/null +++ b/np_platform_image_processor/android/src/main/kotlin/com/nkming/nc_photos/np_platform_image_processor/processor/ZeroDce.kt @@ -0,0 +1,53 @@ +package com.nkming.nc_photos.np_platform_image_processor.processor + +import android.content.Context +import android.content.res.AssetManager +import android.graphics.Bitmap +import android.net.Uri +import com.nkming.nc_photos.np_android_core.BitmapResizeMethod +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 +) { + 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) + } + } + + 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 +} diff --git a/plugin/android/src/main/res/drawable-hdpi/outline_auto_fix_high_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-hdpi/outline_auto_fix_high_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-hdpi/outline_auto_fix_high_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-hdpi/outline_auto_fix_high_white_24.png diff --git a/plugin/android/src/main/res/drawable-hdpi/outline_error_outline_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-hdpi/outline_error_outline_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-hdpi/outline_error_outline_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-hdpi/outline_error_outline_white_24.png diff --git a/plugin/android/src/main/res/drawable-hdpi/outline_image_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-hdpi/outline_image_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-hdpi/outline_image_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-hdpi/outline_image_white_24.png diff --git a/plugin/android/src/main/res/drawable-mdpi/outline_auto_fix_high_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-mdpi/outline_auto_fix_high_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-mdpi/outline_auto_fix_high_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-mdpi/outline_auto_fix_high_white_24.png diff --git a/plugin/android/src/main/res/drawable-mdpi/outline_error_outline_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-mdpi/outline_error_outline_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-mdpi/outline_error_outline_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-mdpi/outline_error_outline_white_24.png diff --git a/plugin/android/src/main/res/drawable-mdpi/outline_image_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-mdpi/outline_image_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-mdpi/outline_image_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-mdpi/outline_image_white_24.png diff --git a/plugin/android/src/main/res/drawable-xhdpi/outline_auto_fix_high_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xhdpi/outline_auto_fix_high_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xhdpi/outline_auto_fix_high_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xhdpi/outline_auto_fix_high_white_24.png diff --git a/plugin/android/src/main/res/drawable-xhdpi/outline_error_outline_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xhdpi/outline_error_outline_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xhdpi/outline_error_outline_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xhdpi/outline_error_outline_white_24.png diff --git a/plugin/android/src/main/res/drawable-xhdpi/outline_image_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xhdpi/outline_image_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xhdpi/outline_image_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xhdpi/outline_image_white_24.png diff --git a/plugin/android/src/main/res/drawable-xxhdpi/outline_auto_fix_high_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xxhdpi/outline_auto_fix_high_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xxhdpi/outline_auto_fix_high_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xxhdpi/outline_auto_fix_high_white_24.png diff --git a/plugin/android/src/main/res/drawable-xxhdpi/outline_error_outline_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xxhdpi/outline_error_outline_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xxhdpi/outline_error_outline_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xxhdpi/outline_error_outline_white_24.png diff --git a/plugin/android/src/main/res/drawable-xxhdpi/outline_image_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xxhdpi/outline_image_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xxhdpi/outline_image_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xxhdpi/outline_image_white_24.png diff --git a/plugin/android/src/main/res/drawable-xxxhdpi/outline_auto_fix_high_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xxxhdpi/outline_auto_fix_high_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xxxhdpi/outline_auto_fix_high_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xxxhdpi/outline_auto_fix_high_white_24.png diff --git a/plugin/android/src/main/res/drawable-xxxhdpi/outline_error_outline_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xxxhdpi/outline_error_outline_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xxxhdpi/outline_error_outline_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xxxhdpi/outline_error_outline_white_24.png diff --git a/plugin/android/src/main/res/drawable-xxxhdpi/outline_image_white_24.png b/np_platform_image_processor/android/src/main/res/drawable-xxxhdpi/outline_image_white_24.png similarity index 100% rename from plugin/android/src/main/res/drawable-xxxhdpi/outline_image_white_24.png rename to np_platform_image_processor/android/src/main/res/drawable-xxxhdpi/outline_image_white_24.png diff --git a/np_platform_image_processor/lib/np_platform_image_processor.dart b/np_platform_image_processor/lib/np_platform_image_processor.dart new file mode 100644 index 00000000..6ad41cbb --- /dev/null +++ b/np_platform_image_processor/lib/np_platform_image_processor.dart @@ -0,0 +1,3 @@ +library np_platform_image_processor; + +export 'src/image_processor.dart'; diff --git a/plugin/lib/src/image_processor.dart b/np_platform_image_processor/lib/src/image_processor.dart similarity index 96% rename from plugin/lib/src/image_processor.dart rename to np_platform_image_processor/lib/src/image_processor.dart index 23722481..29f06000 100644 --- a/plugin/lib/src/image_processor.dart +++ b/np_platform_image_processor/lib/src/image_processor.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:flutter/services.dart'; -import 'package:nc_photos_plugin/src/image.dart'; -import 'package:nc_photos_plugin/src/k.dart' as k; +import 'package:np_platform_image_processor/src/k.dart' as k; +import 'package:np_platform_raw_image/np_platform_raw_image.dart'; abstract class ImageFilter { Map toJson(); @@ -48,7 +48,7 @@ class TransformCropFilter implements ImageFilter { const TransformCropFilter(this.top, this.left, this.bottom, this.right); @override - toJson() => { + Map toJson() => { "type": "crop", "top": top, "left": left, @@ -66,7 +66,7 @@ class TransformOrientationFilter implements ImageFilter { const TransformOrientationFilter(this.degree); @override - toJson() => { + Map toJson() => { "type": "orientation", "degree": degree, }; @@ -226,7 +226,7 @@ class _SingleWeightFilter implements ImageFilter { const _SingleWeightFilter(this.type, this.weight); @override - toJson() => { + Map toJson() => { "type": type, "weight": weight, }; diff --git a/np_platform_image_processor/lib/src/k.dart b/np_platform_image_processor/lib/src/k.dart new file mode 100644 index 00000000..454d9fc1 --- /dev/null +++ b/np_platform_image_processor/lib/src/k.dart @@ -0,0 +1 @@ +const libId = "com.nkming.nc_photos.np_platform_image_processor"; diff --git a/np_platform_image_processor/pubspec.yaml b/np_platform_image_processor/pubspec.yaml new file mode 100644 index 00000000..3db76bdc --- /dev/null +++ b/np_platform_image_processor/pubspec.yaml @@ -0,0 +1,27 @@ +name: np_platform_image_processor +description: A new Flutter plugin project. +version: 0.0.1 +homepage: +publish_to: none + +environment: + sdk: '>=2.19.6 <3.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + logging: ^1.1.1 + np_platform_raw_image: + path: ../np_platform_raw_image + +dev_dependencies: + np_lints: + path: ../np_lints + +flutter: + plugin: + platforms: + android: + package: com.nkming.nc_photos.np_platform_image_processor + pluginClass: NpPlatformImageProcessorPlugin diff --git a/np_platform_lock/android/build.gradle b/np_platform_lock/android/build.gradle index b4dd912a..811115f3 100644 --- a/np_platform_lock/android/build.gradle +++ b/np_platform_lock/android/build.gradle @@ -52,5 +52,5 @@ android { dependencies { implementation "androidx.annotation:annotation:1.6.0" - implementation 'com.nkming.nc_photos.np_android_log:np_android_log' + implementation 'com.nkming.nc_photos.np_android_core:np_android_core' } diff --git a/np_platform_lock/android/settings.gradle b/np_platform_lock/android/settings.gradle index b49a5c88..be07085d 100644 --- a/np_platform_lock/android/settings.gradle +++ b/np_platform_lock/android/settings.gradle @@ -1,2 +1,2 @@ rootProject.name = 'np_platform_lock' -includeBuild '../../np_android_log' +includeBuild '../../np_android_core' 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 0a33daab..5bcd6d02 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 @@ -1,6 +1,6 @@ package com.nkming.nc_photos.np_platform_lock -import com.nkming.nc_photos.np_android_log.logW +import com.nkming.nc_photos.np_android_core.logW import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -18,75 +18,77 @@ 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) - } - } - "unlock" -> { - try { - unlock(call.argument("lockId")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } - else -> { - result.notImplemented() - } - } - } + 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) + } + } - 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) - } - } + "unlock" -> { + try { + unlock(call.argument("lockId")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), 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 - ) - } - } + else -> { + result.notImplemented() + } + } + } - private val _lockedIds = mutableListOf() + 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 val _lockedIds = mutableListOf() } diff --git a/np_platform_permission/.gitignore b/np_platform_permission/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_platform_permission/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/np_platform_permission/.metadata b/np_platform_permission/.metadata new file mode 100644 index 00000000..20225b17 --- /dev/null +++ b/np_platform_permission/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f72efea43c3013323d1b95cff571f3c1caa37583 + channel: stable + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + base_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + - platform: android + create_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + base_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/np_platform_permission/analysis_options.yaml b/np_platform_permission/analysis_options.yaml new file mode 100644 index 00000000..f92d2567 --- /dev/null +++ b/np_platform_permission/analysis_options.yaml @@ -0,0 +1 @@ +include: package:np_lints/np.yaml diff --git a/np_platform_permission/android/.gitignore b/np_platform_permission/android/.gitignore new file mode 100644 index 00000000..161bdcda --- /dev/null +++ b/np_platform_permission/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/np_platform_permission/android/build.gradle b/np_platform_permission/android/build.gradle new file mode 100644 index 00000000..530f4e05 --- /dev/null +++ b/np_platform_permission/android/build.gradle @@ -0,0 +1,54 @@ +group 'com.nkming.nc_photos.np_platform_permission' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.8.20' + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace 'com.nkming.nc_photos.np_platform_permission' + compileSdk 31 + + defaultConfig { + minSdk 21 + } + + buildTypes { + release { + minifyEnabled false + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation "androidx.annotation:annotation:1.6.0" + implementation "androidx.core:core-ktx:1.10.1" + implementation 'com.nkming.nc_photos.np_android_core:np_android_core' +} diff --git a/np_platform_permission/android/gradle.properties b/np_platform_permission/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/np_platform_permission/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/np_platform_permission/android/gradle/wrapper/gradle-wrapper.properties b/np_platform_permission/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..98debb84 --- /dev/null +++ b/np_platform_permission/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/np_platform_permission/android/gradlew b/np_platform_permission/android/gradlew new file mode 100755 index 00000000..a69d9cb6 --- /dev/null +++ b/np_platform_permission/android/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/np_platform_permission/android/gradlew.bat b/np_platform_permission/android/gradlew.bat new file mode 100644 index 00000000..f127cfd4 --- /dev/null +++ b/np_platform_permission/android/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/np_platform_permission/android/settings.gradle b/np_platform_permission/android/settings.gradle new file mode 100644 index 00000000..a7722ba4 --- /dev/null +++ b/np_platform_permission/android/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'np_platform_permission' +includeBuild '../../np_android_core' diff --git a/np_platform_permission/android/src/main/AndroidManifest.xml b/np_platform_permission/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2fa6d4ef --- /dev/null +++ b/np_platform_permission/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + 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 new file mode 100644 index 00000000..495e747a --- /dev/null +++ b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/K.kt @@ -0,0 +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" + } +} 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 new file mode 100644 index 00000000..11ad0ac4 --- /dev/null +++ b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/NpPlatformPermissionPlugin.kt @@ -0,0 +1,93 @@ +package com.nkming.nc_photos.np_platform_permission + +import androidx.annotation.NonNull +import com.nkming.nc_photos.np_android_core.PermissionUtil +import com.nkming.nc_photos.np_android_core.logE +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.EventChannel +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" + } + + 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 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 onDetachedFromActivity() { + permissionChannelHandler.onDetachedFromActivity() + 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 + ) + } + + else -> false + } + } catch (e: Throwable) { + logE( + TAG, "Failed while onActivityResult, requestCode=$requestCode" + ) + false + } + } + + private var pluginBinding: ActivityPluginBinding? = null + + 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 new file mode 100644 index 00000000..5a916e68 --- /dev/null +++ b/np_platform_permission/android/src/main/kotlin/com/nkming/nc_photos/np_platform_permission/PermissionChannelHandler.kt @@ -0,0 +1,114 @@ +package com.nkming.nc_photos.np_platform_permission + +import android.app.Activity +import android.content.Context +import com.nkming.nc_photos.np_android_core.PermissionUtil +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +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" + + private const val TAG = "PermissionChannelHandler" + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + } + + override fun onReattachedToActivityForConfigChanges( + binding: ActivityPluginBinding + ) { + activity = binding.activity + } + + override fun onDetachedFromActivity() { + 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 onListen(arguments: Any?, events: EventChannel.EventSink) { + eventSink = events + } + + 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) + } + } + + "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) + } + } + + 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 val context = context + private var activity: Activity? = null + private var eventSink: EventChannel.EventSink? = null +} diff --git a/np_platform_permission/lib/np_platform_permission.dart b/np_platform_permission/lib/np_platform_permission.dart new file mode 100644 index 00000000..dfecb9ef --- /dev/null +++ b/np_platform_permission/lib/np_platform_permission.dart @@ -0,0 +1,3 @@ +library np_platform_permission; + +export 'src/permission.dart'; diff --git a/np_platform_permission/lib/src/k.dart b/np_platform_permission/lib/src/k.dart new file mode 100644 index 00000000..c30edfbb --- /dev/null +++ b/np_platform_permission/lib/src/k.dart @@ -0,0 +1 @@ +const libId = "com.nkming.nc_photos.np_platform_permission"; diff --git a/plugin/lib/src/permission.dart b/np_platform_permission/lib/src/permission.dart similarity index 96% rename from plugin/lib/src/permission.dart rename to np_platform_permission/lib/src/permission.dart index 756a60b1..1fede94a 100644 --- a/plugin/lib/src/permission.dart +++ b/np_platform_permission/lib/src/permission.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:logging/logging.dart'; -import 'package:nc_photos_plugin/src/k.dart' as k; +import 'package:np_platform_permission/src/k.dart' as k; class Permission { static const READ_EXTERNAL_STORAGE = diff --git a/np_platform_permission/pubspec.yaml b/np_platform_permission/pubspec.yaml new file mode 100644 index 00000000..3c2aaf8f --- /dev/null +++ b/np_platform_permission/pubspec.yaml @@ -0,0 +1,25 @@ +name: np_platform_permission +description: A new Flutter plugin project. +version: 0.0.1 +homepage: +publish_to: none + +environment: + sdk: '>=2.19.6 <3.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + logging: ^1.1.1 + +dev_dependencies: + np_lints: + path: ../np_lints + +flutter: + plugin: + platforms: + android: + package: com.nkming.nc_photos.np_platform_permission + pluginClass: NpPlatformPermissionPlugin diff --git a/np_platform_raw_image/.gitignore b/np_platform_raw_image/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_platform_raw_image/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/np_platform_raw_image/.metadata b/np_platform_raw_image/.metadata new file mode 100644 index 00000000..20225b17 --- /dev/null +++ b/np_platform_raw_image/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f72efea43c3013323d1b95cff571f3c1caa37583 + channel: stable + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + base_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + - platform: android + create_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + base_revision: f72efea43c3013323d1b95cff571f3c1caa37583 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/np_platform_raw_image/analysis_options.yaml b/np_platform_raw_image/analysis_options.yaml new file mode 100644 index 00000000..f92d2567 --- /dev/null +++ b/np_platform_raw_image/analysis_options.yaml @@ -0,0 +1 @@ +include: package:np_lints/np.yaml diff --git a/np_platform_raw_image/android/.gitignore b/np_platform_raw_image/android/.gitignore new file mode 100644 index 00000000..161bdcda --- /dev/null +++ b/np_platform_raw_image/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/np_platform_raw_image/android/build.gradle b/np_platform_raw_image/android/build.gradle new file mode 100644 index 00000000..8429c525 --- /dev/null +++ b/np_platform_raw_image/android/build.gradle @@ -0,0 +1,53 @@ +group 'com.nkming.nc_photos.np_platform_raw_image' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.8.20' + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace 'com.nkming.nc_photos.np_platform_raw_image' + compileSdk 31 + + defaultConfig { + minSdk 21 + } + + buildTypes { + release { + minifyEnabled false + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation "androidx.annotation:annotation:1.6.0" + implementation 'com.nkming.nc_photos.np_android_core:np_android_core' +} diff --git a/np_platform_raw_image/android/gradle/wrapper/gradle-wrapper.properties b/np_platform_raw_image/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..98debb84 --- /dev/null +++ b/np_platform_raw_image/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/np_platform_raw_image/android/gradlew b/np_platform_raw_image/android/gradlew new file mode 100755 index 00000000..a69d9cb6 --- /dev/null +++ b/np_platform_raw_image/android/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/np_platform_raw_image/android/gradlew.bat b/np_platform_raw_image/android/gradlew.bat new file mode 100644 index 00000000..f127cfd4 --- /dev/null +++ b/np_platform_raw_image/android/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/np_platform_raw_image/android/settings.gradle b/np_platform_raw_image/android/settings.gradle new file mode 100644 index 00000000..48695485 --- /dev/null +++ b/np_platform_raw_image/android/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'np_platform_raw_image' +includeBuild '../../np_android_core' diff --git a/np_platform_raw_image/android/src/main/AndroidManifest.xml b/np_platform_raw_image/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f6fc3ef2 --- /dev/null +++ b/np_platform_raw_image/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + 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 new file mode 100644 index 00000000..ad5e59e5 --- /dev/null +++ b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/ImageLoaderChannelHandler.kt @@ -0,0 +1,79 @@ +package com.nkming.nc_photos.np_platform_raw_image + +import android.content.Context +import android.net.Uri +import com.nkming.nc_photos.np_android_core.BitmapResizeMethod +import com.nkming.nc_photos.np_android_core.BitmapUtil +import com.nkming.nc_photos.np_android_core.Rgba8Image +import com.nkming.nc_photos.np_android_core.use +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" + } + + 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() + } + } + + /** + * 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 +} 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 new file mode 100644 index 00000000..cb64b73f --- /dev/null +++ b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/K.kt @@ -0,0 +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" + } +} 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 new file mode 100644 index 00000000..330f1f3d --- /dev/null +++ b/np_platform_raw_image/android/src/main/kotlin/com/nkming/nc_photos/np_platform_raw_image/NpPlatformRawImagePlugin.kt @@ -0,0 +1,23 @@ +package com.nkming.nc_photos.np_platform_raw_image + +import androidx.annotation.NonNull +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 onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + imageLoaderMethodChannel.setMethodCallHandler(null) + } + + private lateinit var imageLoaderMethodChannel: MethodChannel +} diff --git a/np_platform_raw_image/lib/np_platform_raw_image.dart b/np_platform_raw_image/lib/np_platform_raw_image.dart new file mode 100644 index 00000000..1058b4d3 --- /dev/null +++ b/np_platform_raw_image/lib/np_platform_raw_image.dart @@ -0,0 +1,4 @@ +library np_platform_raw_image; + +export 'src/image_loader.dart'; +export 'src/rgba8_image.dart'; diff --git a/plugin/lib/src/image_loader.dart b/np_platform_raw_image/lib/src/image_loader.dart similarity index 88% rename from plugin/lib/src/image_loader.dart rename to np_platform_raw_image/lib/src/image_loader.dart index 7fb3de3e..5072d739 100644 --- a/plugin/lib/src/image_loader.dart +++ b/np_platform_raw_image/lib/src/image_loader.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:flutter/services.dart'; -import 'package:nc_photos_plugin/src/image.dart'; -import 'package:nc_photos_plugin/src/k.dart' as k; +import 'package:np_platform_raw_image/src/k.dart' as k; +import 'package:np_platform_raw_image/src/rgba8_image.dart'; enum ImageLoaderResizeMethod { fit, diff --git a/np_platform_raw_image/lib/src/k.dart b/np_platform_raw_image/lib/src/k.dart new file mode 100644 index 00000000..af373580 --- /dev/null +++ b/np_platform_raw_image/lib/src/k.dart @@ -0,0 +1 @@ +const libId = "com.nkming.nc_photos.np_platform_raw_image"; diff --git a/plugin/lib/src/image.dart b/np_platform_raw_image/lib/src/rgba8_image.dart similarity index 100% rename from plugin/lib/src/image.dart rename to np_platform_raw_image/lib/src/rgba8_image.dart diff --git a/np_platform_raw_image/pubspec.yaml b/np_platform_raw_image/pubspec.yaml new file mode 100644 index 00000000..ae3f07fe --- /dev/null +++ b/np_platform_raw_image/pubspec.yaml @@ -0,0 +1,24 @@ +name: np_platform_raw_image +description: A new Flutter plugin project. +version: 0.0.1 +homepage: +publish_to: none + +environment: + sdk: '>=2.19.6 <3.0.0' + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + np_lints: + path: ../np_lints + +flutter: + plugin: + platforms: + android: + package: com.nkming.nc_photos.np_platform_raw_image + pluginClass: NpPlatformRawImagePlugin diff --git a/plugin/android/build.gradle b/plugin/android/build.gradle index b9662af6..38a6fdc6 100644 --- a/plugin/android/build.gradle +++ b/plugin/android/build.gradle @@ -29,7 +29,6 @@ apply plugin: 'kotlin-android' android { namespace 'com.nkming.nc_photos.plugin' compileSdk 31 - ndkVersion "23.2.8568313" compileOptions { coreLibraryDesugaringEnabled true @@ -47,23 +46,12 @@ android { defaultConfig { minSdk 21 - ndk { - abiFilters "armeabi-v7a", "arm64-v8a", "x86_64" - } - } - externalNativeBuild { - cmake { - path file('src/main/cpp/CMakeLists.txt') - version '3.18.1' - } } } dependencies { coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3" - implementation "androidx.annotation:annotation:1.6.0" implementation "androidx.core:core-ktx:1.10.1" - implementation "androidx.exifinterface:exifinterface:1.3.6" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.nkming.nc_photos.np_android_log:np_android_log" + implementation "com.nkming.nc_photos.np_android_core:np_android_core" } diff --git a/plugin/android/settings.gradle b/plugin/android/settings.gradle index 11fbd04a..c8c63414 100644 --- a/plugin/android/settings.gradle +++ b/plugin/android/settings.gradle @@ -1,2 +1,2 @@ rootProject.name = 'plugin' -includeBuild '../../np_android_log' +includeBuild '../../np_android_core' diff --git a/plugin/android/src/main/AndroidManifest.xml b/plugin/android/src/main/AndroidManifest.xml index d2bb947b..575db223 100644 --- a/plugin/android/src/main/AndroidManifest.xml +++ b/plugin/android/src/main/AndroidManifest.xml @@ -1,11 +1,3 @@ - - - - - - diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt deleted file mode 100644 index e79455e7..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt +++ /dev/null @@ -1,255 +0,0 @@ -package com.nkming.nc_photos.plugin - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Matrix -import android.net.Uri -import androidx.exifinterface.media.ExifInterface -import com.nkming.nc_photos.np_android_log.logD -import com.nkming.nc_photos.np_android_log.logE -import com.nkming.nc_photos.np_android_log.logI -import java.io.InputStream -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -fun Bitmap.aspectRatio() = width / height.toFloat() - -/** - * Recycle the bitmap after @c block returns. - * - * @param block - * @return - */ -@OptIn(ExperimentalContracts::class) -inline fun Bitmap.use(block: (Bitmap) -> T): T { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - try { - return block(this) - } finally { - recycle() - } -} - -enum class BitmapResizeMethod { - 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) - } - - /** - * 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 - } - } - - /** - * 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_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 - } - } - - 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 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 - ) - BitmapResizeMethod.FILL -> minOf( - originalW / targetW, - originalH / targetH - ) - } - } - - private const val TAG = "BitmapUtil" - } -} 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 ad87da17..bbc5303b 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 @@ -3,72 +3,73 @@ package com.nkming.nc_photos.plugin import android.content.Context import android.net.Uri import androidx.core.content.FileProvider -import com.nkming.nc_photos.np_android_log.logE +import com.nkming.nc_photos.np_android_core.UriUtil +import com.nkming.nc_photos.np_android_core.logE import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import java.io.File import java.io.FileNotFoundException -class ContentUriChannelHandler(context: Context) : - MethodChannel.MethodCallHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/content_uri_method" +internal class ContentUriChannelHandler(context: Context) : + 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/Event.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Event.kt deleted file mode 100644 index 53826f9b..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Event.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.nkming.nc_photos.plugin - -import android.net.Uri - -interface MessageEvent - -data class ImageProcessorCompletedEvent( - val result: Uri, -) : MessageEvent - -data class ImageProcessorFailedEvent( - val exception: Throwable, -) : MessageEvent diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt deleted file mode 100644 index 4cdd1e89..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/Exception.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.nkming.nc_photos.plugin - -class PermissionException(message: String) : Exception(message) - -class HttpException(statusCode: Int, message: String): Exception(message) - -class NativeException(message: String) : Exception(message) diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt deleted file mode 100644 index 0f157430..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.nkming.nc_photos.plugin - -import android.content.Context -import android.net.Uri -import com.nkming.nc_photos.plugin.image_processor.Rgba8Image -import com.nkming.nc_photos.plugin.image_processor.TfLiteHelper -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel - -class ImageLoaderChannelHandler(context: Context) : - 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) - } - } - - 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(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height) - } - result.success(image.toJson()) - } - - private val context = context -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt deleted file mode 100644 index 2158046f..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt +++ /dev/null @@ -1,319 +0,0 @@ -package com.nkming.nc_photos.plugin - -import android.content.Context -import android.content.Intent -import android.net.Uri -import androidx.core.content.ContextCompat -import com.nkming.nc_photos.np_android_log.logE -import com.nkming.nc_photos.plugin.image_processor.* -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import java.io.Serializable - -class ImageProcessorChannelHandler(context: Context) : - MethodChannel.MethodCallHandler, EventChannel.StreamHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/image_processor_method" - - 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) - } - } - - "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) - } - } - - "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) - } - } - - "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) - } - } - - "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() - } - } - - override fun onListen(arguments: Any?, events: EventChannel.EventSink) { - eventSink = events - } - - 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 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 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 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 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 val context = context - private var eventSink: EventChannel.EventSink? = null -} - -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 - ) - else -> throw IllegalArgumentException( - "Unknown type: ${json["type"]}" - ) - } - } - } - - fun apply(rgba8: Rgba8Image): Rgba8Image -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt deleted file mode 100644 index c5694548..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt +++ /dev/null @@ -1,986 +0,0 @@ -package com.nkming.nc_photos.plugin - -import android.annotation.SuppressLint -import android.app.Notification -import android.app.PendingIntent -import android.app.Service -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.AsyncTask -import android.os.Bundle -import android.os.IBinder -import android.os.PowerManager -import android.webkit.MimeTypeMap -import androidx.core.app.NotificationChannelCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.exifinterface.media.ExifInterface -import com.nkming.nc_photos.np_android_log.logD -import com.nkming.nc_photos.np_android_log.logE -import com.nkming.nc_photos.np_android_log.logI -import com.nkming.nc_photos.np_android_log.logW -import com.nkming.nc_photos.plugin.image_processor.* -import java.io.File -import java.io.Serializable -import java.net.HttpURLConnection -import java.net.URL - -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" - - 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" - - const val TAG = "ImageProcessorService" - } - - override fun onBind(intent: Intent?): IBinder? = null - - @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 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()) - } - - 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 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!! - ) - - METHOD_ESRGAN -> onEsrgan(startId, intent.extras!!) - METHOD_ARBITRARY_STYLE_TRANSFER -> onArbitraryStyleTransfer( - 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 - ) - ) - ) - } - } - } - - 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 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 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 onFilter(startId: Int, extras: Bundle) { - val filters = extras.getSerializable(EXTRA_FILTERS)!! - .asType>() - .map { ImageFilter.fromJson(it.asType>()) } - - 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 - ) - ) - } - - /** - * 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 - ) - ) - ) - } - - 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 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 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 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( - @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) - } - } - - 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) - } - } - - 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 interface ImageProcessorCommand - -private abstract class ImageProcessorImageCommand( - 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, - ) - - 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 -} - -private class ImageProcessorDummyCommand( - params: Params, -) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - throw UnsupportedOperationException() - } - - override fun isEnhanceCommand() = true -} - -private class ImageProcessorZeroDceCommand( - 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 isEnhanceCommand() = true -} - -private class ImageProcessorDeepLapPortraitCommand( - 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 isEnhanceCommand() = true -} - -private class ImageProcessorEsrganCommand( - params: Params, -) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return Esrgan(context, maxWidth, maxHeight).infer(fileUri) - } - - override fun isEnhanceCommand() = true -} - -private class ImageProcessorArbitraryStyleTransferCommand( - 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 isEnhanceCommand() = true -} - -private class ImageProcessorDeepLapColorPopCommand( - 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 isEnhanceCommand() = true -} - -private class ImageProcessorNeurOpCommand( - params: Params, -) : ImageProcessorImageCommand(params) { - override fun apply(context: Context, fileUri: Uri): Bitmap { - return NeurOp(context, maxWidth, maxHeight).infer(fileUri) - } - - override fun isEnhanceCommand() = true -} - -private class ImageProcessorFilterCommand( - 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 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 -// 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, - ) - - 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) - } - } - - 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 - ) - } - } - } - - 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 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() - } - } - - 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) - } - - // 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() - } - } - - 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 getPersister(isSaveToServer: Boolean): EnhancedFilePersister { - return if (isSaveToServer) { - EnhancedFileServerPersisterWithFallback(context) - } else { - EnhancedFileDevicePersister(context) - } - } - - @SuppressLint("StaticFieldLeak") - private val context = context -} - -private interface AsyncTaskCancellable { - 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 -} - -private interface EnhancedFilePersister { - 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 - } - - private fun getSubDir(cmd: ImageProcessorImageCommand): String { - return if (!cmd.isEnhanceCommand()) { - "Edited Photos" - } else { - "Enhanced Photos" - } - } - - val context = context -} - -private class EnhancedFileServerPersister : EnhancedFilePersister { - 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) - } - - 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" - } - - 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) -} 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 a5ad2c49..a0801db8 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,23 +1,16 @@ package com.nkming.nc_photos.plugin -interface K { - companion object { - const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000 - const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000 - 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 +internal interface K { + companion object { + const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000 + const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000 - const val PERMISSION_REQUEST_CODE = 11011 - 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_SHOW_IMAGE_PROCESSOR_RESULT = - "${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT" + const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL" - const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID" - const val EXTRA_IMAGE_RESULT_URI = "${LIB_ID}.EXTRA_IMAGE_RESULT_URI" - } + const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID" + } } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/LogcatChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/LogcatChannelHandler.kt index e63b7e5a..f59788bc 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/LogcatChannelHandler.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/LogcatChannelHandler.kt @@ -3,32 +3,32 @@ package com.nkming.nc_photos.plugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -class LogcatChannelHandler : MethodChannel.MethodCallHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/logcat_method" - } +internal class LogcatChannelHandler : MethodChannel.MethodCallHandler { + companion object { + const val METHOD_CHANNEL = "${K.LIB_ID}/logcat_method" + } - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "dump" -> { - try { - dump(result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "dump" -> { + try { + dump(result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - else -> result.notImplemented() - } - } + else -> result.notImplemented() + } + } - private fun dump(result: MethodChannel.Result) { - val logs = StringBuilder() - val process = Runtime.getRuntime().exec("logcat -d") - process.inputStream.bufferedReader().use { - while (it.readLine()?.also(logs::appendLine) != null) { - } - } - result.success(logs.toString()) - } + private fun dump(result: MethodChannel.Result) { + val logs = StringBuilder() + val process = Runtime.getRuntime().exec("logcat -d") + process.inputStream.bufferedReader().use { + while (it.readLine()?.also(logs::appendLine) != null) { + } + } + result.success(logs.toString()) + } } 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 f9eb30ab..e8b3c7f7 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 @@ -7,8 +7,12 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.provider.MediaStore -import com.nkming.nc_photos.np_android_log.logE -import com.nkming.nc_photos.np_android_log.logI +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 import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import io.flutter.plugin.common.EventChannel @@ -28,253 +32,253 @@ import java.io.File * Return files under @c relativePath and its sub dirs * fun queryFiles(relativePath: String): List */ -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" +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" - 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/NativeEvent.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEvent.kt deleted file mode 100644 index 4151628c..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEvent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin - -interface NativeEvent { - fun getId(): String - fun getData(): String? = null -} - -class ImageProcessorUploadSuccessEvent : NativeEvent { - companion object { - const val id = "ImageProcessorUploadSuccessEvent" - } - - override fun getId() = id -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEventChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEventChannelHandler.kt deleted file mode 100644 index dbd790dc..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/NativeEventChannelHandler.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.nkming.nc_photos.plugin - -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel - -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" - - /** - * 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 - } - - 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 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 val id = nextId++ -} 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 4ee20631..2599f016 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 @@ -2,7 +2,7 @@ package com.nkming.nc_photos.plugin import android.content.Intent import androidx.annotation.NonNull -import com.nkming.nc_photos.np_android_log.logE +import com.nkming.nc_photos.np_android_core.logE import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding @@ -11,221 +11,126 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.PluginRegistry class NcPhotosPlugin : FlutterPlugin, ActivityAware, - PluginRegistry.ActivityResultListener, - PluginRegistry.RequestPermissionsResultListener { - companion object { - init { - System.loadLibrary("plugin") - } + PluginRegistry.ActivityResultListener { + companion object { + const val ACTION_DOWNLOAD_CANCEL = K.ACTION_DOWNLOAD_CANCEL + const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID - const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT = - K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT - const val EXTRA_IMAGE_RESULT_URI = K.EXTRA_IMAGE_RESULT_URI + private const val TAG = "NcPhotosPlugin" + } - const val ACTION_DOWNLOAD_CANCEL = - K.ACTION_DOWNLOAD_CANCEL - const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + notificationChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + NotificationChannelHandler.CHANNEL + ) + notificationChannel.setMethodCallHandler( + NotificationChannelHandler( + flutterPluginBinding.applicationContext + ) + ) - private const val TAG = "NcPhotosPlugin" - } + mediaStoreChannelHandler = + MediaStoreChannelHandler(flutterPluginBinding.applicationContext) + mediaStoreChannel = EventChannel( + flutterPluginBinding.binaryMessenger, + MediaStoreChannelHandler.EVENT_CHANNEL + ) + mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler) + mediaStoreMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + MediaStoreChannelHandler.METHOD_CHANNEL + ) + mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler) - override fun onAttachedToEngine( - @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding - ) { - notificationChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - NotificationChannelHandler.CHANNEL - ) - notificationChannel.setMethodCallHandler( - NotificationChannelHandler( - flutterPluginBinding.applicationContext - ) - ) + contentUriMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + ContentUriChannelHandler.METHOD_CHANNEL + ) + contentUriMethodChannel.setMethodCallHandler( + ContentUriChannelHandler(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 logcatChannelHandler = LogcatChannelHandler() + logcatMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + LogcatChannelHandler.METHOD_CHANNEL + ) + logcatMethodChannel.setMethodCallHandler(logcatChannelHandler) - mediaStoreChannelHandler = - MediaStoreChannelHandler(flutterPluginBinding.applicationContext) - mediaStoreChannel = EventChannel( - flutterPluginBinding.binaryMessenger, - MediaStoreChannelHandler.EVENT_CHANNEL - ) - mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler) - mediaStoreMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - MediaStoreChannelHandler.METHOD_CHANNEL - ) - mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler) + val preferenceChannelHandler = + PreferenceChannelHandler(flutterPluginBinding.applicationContext) + preferenceMethodChannel = MethodChannel( + flutterPluginBinding.binaryMessenger, + PreferenceChannelHandler.METHOD_CHANNEL + ) + preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler) + } - imageProcessorMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - ImageProcessorChannelHandler.METHOD_CHANNEL - ) - imageProcessorMethodChannel.setMethodCallHandler( - ImageProcessorChannelHandler( - flutterPluginBinding.applicationContext - ) - ) + override fun onDetachedFromEngine( + @NonNull binding: FlutterPlugin.FlutterPluginBinding + ) { + notificationChannel.setMethodCallHandler(null) + mediaStoreChannel.setStreamHandler(null) + mediaStoreMethodChannel.setMethodCallHandler(null) + contentUriMethodChannel.setMethodCallHandler(null) + logcatMethodChannel.setMethodCallHandler(null) + preferenceMethodChannel.setMethodCallHandler(null) + } - contentUriMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - ContentUriChannelHandler.METHOD_CHANNEL - ) - contentUriMethodChannel.setMethodCallHandler( - ContentUriChannelHandler(flutterPluginBinding.applicationContext) - ) + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + mediaStoreChannelHandler.onAttachedToActivity(binding) + pluginBinding = binding + binding.addActivityResultListener(this) + } - 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 onReattachedToActivityForConfigChanges( + binding: ActivityPluginBinding + ) { + mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding) + pluginBinding = binding + binding.addActivityResultListener(this) + } - val logcatChannelHandler = LogcatChannelHandler() - logcatMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - LogcatChannelHandler.METHOD_CHANNEL - ) - logcatMethodChannel.setMethodCallHandler(logcatChannelHandler) + override fun onDetachedFromActivity() { + mediaStoreChannelHandler.onDetachedFromActivity() + pluginBinding?.removeActivityResultListener(this) + } - val preferenceChannelHandler = - PreferenceChannelHandler(flutterPluginBinding.applicationContext) - preferenceMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - PreferenceChannelHandler.METHOD_CHANNEL - ) - preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler) + override fun onDetachedFromActivityForConfigChanges() { + mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges() + pluginBinding?.removeActivityResultListener(this) + } - val imageLoaderChannelHandler = - ImageLoaderChannelHandler(flutterPluginBinding.applicationContext) - imageLoaderMethodChannel = MethodChannel( - flutterPluginBinding.binaryMessenger, - ImageLoaderChannelHandler.METHOD_CHANNEL - ) - imageLoaderMethodChannel.setMethodCallHandler(imageLoaderChannelHandler) - } + 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 onDetachedFromEngine( - @NonNull binding: FlutterPlugin.FlutterPluginBinding - ) { - notificationChannel.setMethodCallHandler(null) - nativeEventChannel.setStreamHandler(null) - nativeEventMethodChannel.setMethodCallHandler(null) - mediaStoreChannel.setStreamHandler(null) - mediaStoreMethodChannel.setMethodCallHandler(null) - imageProcessorMethodChannel.setMethodCallHandler(null) - contentUriMethodChannel.setMethodCallHandler(null) - permissionChannel.setStreamHandler(null) - permissionMethodChannel.setMethodCallHandler(null) - logcatMethodChannel.setMethodCallHandler(null) - preferenceMethodChannel.setMethodCallHandler(null) - imageLoaderMethodChannel.setMethodCallHandler(null) - } + else -> false + } + } catch (e: Throwable) { + logE(TAG, "Failed while onActivityResult, requestCode=$requestCode") + false + } + } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - mediaStoreChannelHandler.onAttachedToActivity(binding) - permissionChannelHandler.onAttachedToActivity(binding) - pluginBinding = binding - binding.addActivityResultListener(this) - binding.addRequestPermissionsResultListener(this) - } + private var pluginBinding: ActivityPluginBinding? = null - override fun onReattachedToActivityForConfigChanges( - binding: ActivityPluginBinding - ) { - mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding) - permissionChannelHandler.onReattachedToActivityForConfigChanges(binding) - pluginBinding = binding - binding.addActivityResultListener(this) - binding.addRequestPermissionsResultListener(this) - } + private lateinit var notificationChannel: MethodChannel + private lateinit var mediaStoreChannel: EventChannel + private lateinit var mediaStoreMethodChannel: MethodChannel + private lateinit var contentUriMethodChannel: MethodChannel + private lateinit var logcatMethodChannel: MethodChannel + private lateinit var preferenceMethodChannel: MethodChannel - override fun onDetachedFromActivity() { - mediaStoreChannelHandler.onDetachedFromActivity() - permissionChannelHandler.onDetachedFromActivity() - pluginBinding?.removeActivityResultListener(this) - pluginBinding?.removeRequestPermissionsResultListener(this) - } - - override fun onDetachedFromActivityForConfigChanges() { - mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges() - permissionChannelHandler.onDetachedFromActivityForConfigChanges() - pluginBinding?.removeActivityResultListener(this) - pluginBinding?.removeRequestPermissionsResultListener(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 - ) - } - - else -> false - } - } catch (e: Throwable) { - logE(TAG, "Failed while onActivityResult, requestCode=$requestCode") - false - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray - ): Boolean { - return try { - when (requestCode) { - K.PERMISSION_REQUEST_CODE -> { - permissionChannelHandler.onRequestPermissionsResult( - requestCode, permissions, grantResults - ) - } - - else -> false - } - } catch (e: Throwable) { - logE( - TAG, "Failed while onActivityResult, requestCode=$requestCode" - ) - false - } - } - - private var pluginBinding: ActivityPluginBinding? = null - - private lateinit var notificationChannel: MethodChannel - private lateinit var nativeEventChannel: EventChannel - private lateinit var nativeEventMethodChannel: MethodChannel - private lateinit var mediaStoreChannel: EventChannel - private lateinit var mediaStoreMethodChannel: MethodChannel - private lateinit var imageProcessorMethodChannel: MethodChannel - private lateinit var contentUriMethodChannel: MethodChannel - private lateinit var permissionChannel: EventChannel - private lateinit var permissionMethodChannel: MethodChannel - private lateinit var logcatMethodChannel: MethodChannel - private lateinit var preferenceMethodChannel: MethodChannel - private lateinit var imageLoaderMethodChannel: MethodChannel - - private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler - private lateinit var permissionChannelHandler: PermissionChannelHandler + 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 9fe55a96..7d0ca71f 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 @@ -12,7 +12,8 @@ import android.net.Uri import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import com.nkming.nc_photos.np_android_log.logE +import com.nkming.nc_photos.np_android_core.getPendingIntentFlagImmutable +import com.nkming.nc_photos.np_android_core.logE import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import kotlin.math.max @@ -24,331 +25,335 @@ import kotlin.math.max * fun notifyItemsDownloadSuccessful(fileUris: List, * mimeTypes: List): Unit */ -class NotificationChannelHandler(context: Context) : - MethodChannel.MethodCallHandler { - companion object { - const val CHANNEL = "${K.LIB_ID}/notification" +internal class NotificationChannelHandler(context: Context) : + 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) - } - } - "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) - } - } - "dismiss" -> { - try { - dismiss(call.argument("notificationId")!!, result) - } catch (e: Throwable) { - result.error("systemException", e.toString(), null) - } - } - else -> { - result.notImplemented() - } - } - } + 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) + } + } - 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) + "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) + } + } - if (uris.size == 1) { - builder.setContentTitle( - _context.getString( - R.string.download_successful_notification_title - ) - ).setContentText( - _context.getString( - R.string.download_successful_notification_text - ) - ) + "notifyLogSaveSuccessful" -> { + try { + notifyLogSaveSuccessful( + call.argument("fileUri")!!, result + ) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - 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) + "dismiss" -> { + try { + dismiss(call.argument("notificationId")!!, result) + } catch (e: Throwable) { + result.error("systemException", e.toString(), null) + } + } - // 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 - ) - ) - } + else -> { + result.notImplemented() + } + } + } - 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 - ) + 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) - val id = notificationId ?: getNextNotificationId() - with(NotificationManagerCompat.from(_context)) { - notify(id, builder.build()) - } - result.success(id) - } + if (uris.size == 1) { + builder.setContentTitle( + _context.getString( + R.string.download_successful_notification_title + ) + ).setContentText( + _context.getString( + R.string.download_successful_notification_text + ) + ) - 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 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 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 - ) + // 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 + ) + ) + } - with(NotificationManagerCompat.from(_context)) { - notify(id, builder.build()) - } - result.success(id) - } + 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 + ) - 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 id = notificationId ?: getNextNotificationId() + with(NotificationManagerCompat.from(_context)) { + notify(id, builder.build()) + } + result.success(id) + } - 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) + 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 + ) + ) + } - // 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 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 id = getNextNotificationId() - with(NotificationManagerCompat.from(_context)) { - notify(id, builder.build()) - } - result.success(id) - } + 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 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 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 - } - } + 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) - 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 - } + // 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 manager = - context.getSystemService( - Context.NOTIFICATION_SERVICE - ) as NotificationManager - manager.createNotificationChannel(channel) - } - } + val id = getNextNotificationId() + with(NotificationManagerCompat.from(_context)) { + notify(id, builder.build()) + } + result.success(id) + } - private val _context = context + 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 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) + } + } + + private val _context = context } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PermissionChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PermissionChannelHandler.kt deleted file mode 100644 index f3dc820e..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PermissionChannelHandler.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.nkming.nc_photos.plugin - -import android.app.Activity -import android.content.Context -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.PluginRegistry - -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" - - private const val TAG = "PermissionChannelHandler" - } - - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activity = binding.activity - } - - override fun onReattachedToActivityForConfigChanges( - binding: ActivityPluginBinding - ) { - activity = binding.activity - } - - override fun onDetachedFromActivity() { - activity = null - } - - override fun onDetachedFromActivityForConfigChanges() { - activity = null - } - - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray - ): Boolean { - return if (requestCode == K.PERMISSION_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 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) - } - } - - "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) - } - } - - 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 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/PermissionUtil.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PermissionUtil.kt deleted file mode 100644 index de9caed2..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/PermissionUtil.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.nkming.nc_photos.plugin - -import android.Manifest -import android.app.Activity -import android.content.Context -import android.content.pm.PackageManager -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat - -interface PermissionUtil { - companion object { - fun request(activity: Activity, vararg permissions: String) { - ActivityCompat.requestPermissions( - activity, permissions, K.PERMISSION_REQUEST_CODE - ) - } - - 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 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) - } -} 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 95c39ed6..39fd708a 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 @@ -5,73 +5,77 @@ import android.content.SharedPreferences import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -class PreferenceChannelHandler(context: Context) : - MethodChannel.MethodCallHandler { - companion object { - const val METHOD_CHANNEL = "${K.LIB_ID}/preference_method" - } +internal class PreferenceChannelHandler(context: Context) : + 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 } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt deleted file mode 100644 index d70b8f19..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.content.Context -import android.content.res.AssetManager -import android.graphics.Bitmap -import android.net.Uri -import com.nkming.nc_photos.np_android_log.logI -import com.nkming.nc_photos.plugin.BitmapResizeMethod -import com.nkming.nc_photos.plugin.BitmapUtil -import com.nkming.nc_photos.plugin.use - -class ArbitraryStyleTransfer( - context: Context, maxWidth: Int, maxHeight: Int, styleUri: Uri, - weight: Float -) { - 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 - - 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 val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight - private val styleUri = styleUri - private val weight = weight -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/BlackPoint.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/BlackPoint.kt deleted file mode 100644 index 3e75fd9b..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/BlackPoint.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class BlackPoint(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Brightness.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Brightness.kt deleted file mode 100644 index 53c2a382..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Brightness.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class Brightness(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Contrast.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Contrast.kt deleted file mode 100644 index 7b465f3f..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Contrast.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class Contrast(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Cool.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Cool.kt deleted file mode 100644 index d188524b..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Cool.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class Cool(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Crop.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Crop.kt deleted file mode 100644 index be04fb61..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Crop.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter -import java.lang.Integer.max - -class Crop( - 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) - } - - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, top: Int, left: Int, - dstWidth: Int, dstHeight: Int - ): ByteArray -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt deleted file mode 100644 index 70747d40..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.content.Context -import android.content.res.AssetManager -import android.graphics.Bitmap -import android.net.Uri -import com.nkming.nc_photos.plugin.BitmapResizeMethod -import com.nkming.nc_photos.plugin.BitmapUtil -import com.nkming.nc_photos.plugin.use - -/** - * DeepLab is a state-of-art deep learning model for semantic image - * segmentation, where the goal is to assign semantic labels (e.g., person, dog, - * cat and so on) to every pixel in the input image - * - * See: https://github.com/tensorflow/models/tree/master/research/deeplab - */ -class DeepLab3Portrait( - 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 - - 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 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 -) { - 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) - } - } - - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt deleted file mode 100644 index b02e5ee4..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.content.Context -import android.content.res.AssetManager -import android.graphics.Bitmap -import android.net.Uri -import com.nkming.nc_photos.plugin.BitmapResizeMethod -import com.nkming.nc_photos.plugin.BitmapUtil -import com.nkming.nc_photos.plugin.use - -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 - - 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 val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Image.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Image.kt deleted file mode 100644 index 2f0579f4..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Image.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.graphics.Bitmap -import java.nio.ByteBuffer - -/** - * Container of pixel data stored in RGBA format - */ -class Rgba8Image(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 - ) - } - - 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)) - } - } - - init { - assert(pixel.size == width * height * 4) - } -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt deleted file mode 100644 index 6fb04620..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.content.Context -import android.graphics.Bitmap -import android.net.Uri -import com.nkming.nc_photos.plugin.BitmapResizeMethod -import com.nkming.nc_photos.plugin.BitmapUtil -import com.nkming.nc_photos.plugin.ImageFilter -import com.nkming.nc_photos.plugin.use - -class ImageFilterProcessor( - context: Context, maxWidth: Int, maxHeight: Int, filters: List -) { - 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) - } - - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/LosslessRotator.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/LosslessRotator.kt deleted file mode 100644 index ccdbeeb4..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/LosslessRotator.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import androidx.exifinterface.media.ExifInterface -import com.nkming.nc_photos.np_android_log.logI - -/** - * Lossless rotation is done by modifying the EXIF orientation tag in such a way - * that the viewer will rotate the image when displaying the image - */ -class 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() - ) - } - - /** - * 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" - ) - } - } -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/NeurOp.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/NeurOp.kt deleted file mode 100644 index cab579c6..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/NeurOp.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.content.Context -import android.content.res.AssetManager -import android.graphics.Bitmap -import android.net.Uri -import com.nkming.nc_photos.plugin.BitmapResizeMethod -import com.nkming.nc_photos.plugin.BitmapUtil -import com.nkming.nc_photos.plugin.use - -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 - - 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 val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Orientation.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Orientation.kt deleted file mode 100644 index 86d87e79..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Orientation.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter -import kotlin.math.abs - -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 - ) - } - - private external fun applyNative( - rgba8: ByteArray, width: Int, height: Int, degree: Int - ): ByteArray -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Saturation.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Saturation.kt deleted file mode 100644 index 08706686..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Saturation.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class Saturation(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/TfLiteHelper.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/TfLiteHelper.kt deleted file mode 100644 index 480530ed..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/TfLiteHelper.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.graphics.Bitmap -import java.nio.ByteBuffer -import java.nio.IntBuffer - -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 - } - - /** - * Convert an ARGB_8888 Android bitmap to a RGBA byte array - * - * @param bitmap - * @return - */ - fun bitmapToRgba8Array(bitmap: Bitmap): ByteArray { - val buffer = ByteBuffer.allocate(bitmap.width * bitmap.height * 4) - bitmap.copyPixelsToBuffer(buffer) - return buffer.array() - } - - /** - * 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) - } - 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 - } - } -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Tint.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Tint.kt deleted file mode 100644 index b07ac00e..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Tint.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class Tint(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Warmth.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Warmth.kt deleted file mode 100644 index 66ff5da8..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Warmth.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class Warmth(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/WhitePoint.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/WhitePoint.kt deleted file mode 100644 index 64d45b69..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/WhitePoint.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import com.nkming.nc_photos.plugin.ImageFilter - -class WhitePoint(val weight: Float) : ImageFilter { - 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 -} diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt deleted file mode 100644 index b9b20483..00000000 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.nkming.nc_photos.plugin.image_processor - -import android.content.Context -import android.content.res.AssetManager -import android.graphics.Bitmap -import android.net.Uri -import com.nkming.nc_photos.plugin.BitmapResizeMethod -import com.nkming.nc_photos.plugin.BitmapUtil -import com.nkming.nc_photos.plugin.use - -class ZeroDce(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 - - 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 val context = context - private val maxWidth = maxWidth - private val maxHeight = maxHeight - private val iteration = iteration -} diff --git a/plugin/lib/nc_photos_plugin.dart b/plugin/lib/nc_photos_plugin.dart index 3cf7c2e7..dd6e02eb 100644 --- a/plugin/lib/nc_photos_plugin.dart +++ b/plugin/lib/nc_photos_plugin.dart @@ -2,12 +2,8 @@ library nc_photos_plugin; export 'src/content_uri.dart'; export 'src/exception.dart'; -export 'src/image.dart'; -export 'src/image_loader.dart'; -export 'src/image_processor.dart'; export 'src/logcat.dart'; export 'src/media_store.dart'; export 'src/native_event.dart'; export 'src/notification.dart'; -export 'src/permission.dart'; export 'src/preference.dart';