From a43aa4cbbd813be9589547b699086b088afcd0c5 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Thu, 15 Sep 2022 12:59:52 +0800 Subject: [PATCH] Add auto retouch enhancement --- app/lib/help_utils.dart | 1 + app/lib/l10n/app_en.arb | 4 + app/lib/l10n/untranslated-messages.txt | 11 ++ app/lib/widget/handler/enhance_handler.dart | 23 +++++ .../main/assets/tf/neurop_fivek_lite.tflite | Bin 0 -> 58016 bytes plugin/android/src/main/cpp/CMakeLists.txt | 1 + plugin/android/src/main/cpp/neur_op.cpp | 95 ++++++++++++++++++ plugin/android/src/main/cpp/neur_op.h | 16 +++ .../plugin/ImageProcessorChannelHandler.kt | 26 +++++ .../nc_photos/plugin/ImageProcessorService.kt | 18 ++++ .../plugin/image_processor/NeurOp.kt | 38 +++++++ plugin/lib/src/image_processor.dart | 17 ++++ 12 files changed, 250 insertions(+) create mode 100644 plugin/android/src/main/assets/tf/neurop_fivek_lite.tflite create mode 100644 plugin/android/src/main/cpp/neur_op.cpp create mode 100644 plugin/android/src/main/cpp/neur_op.h create mode 100644 plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/NeurOp.kt diff --git a/app/lib/help_utils.dart b/app/lib/help_utils.dart index c48ee3a4..d921d460 100644 --- a/app/lib/help_utils.dart +++ b/app/lib/help_utils.dart @@ -9,4 +9,5 @@ const enhanceDeepLabPortraitBlurUrl = "https://bit.ly/3wIuXy6"; const enhanceEsrganUrl = "https://bit.ly/3wO0NJP"; const enhanceStyleTransferUrl = "https://bit.ly/3agpTcF"; const enhanceDeepLabColorPopUrl = "https://bit.ly/3Rx0YCD"; +const enhanceRetouchUrl = "https://bit.ly/3Ds2cea"; const editPhotosUrl = "https://bit.ly/3v82oKA"; diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index c145056c..1d7658cd 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -1274,6 +1274,10 @@ "@enhanceGenericParamWeightLabel": { "description": "This generic parameter sets the weight of the applied effect. The effect will be more obvious when the weight is high." }, + "enhanceRetouchTitle": "Auto retouch", + "@enhanceRetouchTitle": { + "description": "Automatically improve your photo" + }, "doubleTapExitNotification": "Tap again to exit", "@doubleTapExitNotification": { "description": "If double tap to exit is enabled in settings, shown when users tap the back button" diff --git a/app/lib/l10n/untranslated-messages.txt b/app/lib/l10n/untranslated-messages.txt index 7e924eab..d250685a 100644 --- a/app/lib/l10n/untranslated-messages.txt +++ b/app/lib/l10n/untranslated-messages.txt @@ -120,6 +120,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -301,6 +302,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -373,6 +375,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -439,6 +442,7 @@ "collectionEditedPhotosLabel", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "imageEditToolbarColorLabel", "imageEditToolbarTransformLabel", "imageEditTransformOrientation", @@ -473,6 +477,7 @@ "collectionEditedPhotosLabel", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "imageEditToolbarColorLabel", "imageEditToolbarTransformLabel", "imageEditTransformOrientation", @@ -533,6 +538,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -641,6 +647,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -728,6 +735,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -815,6 +823,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -902,6 +911,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", @@ -989,6 +999,7 @@ "enhanceStyleTransferStyleDialogTitle", "enhanceColorPopTitle", "enhanceGenericParamWeightLabel", + "enhanceRetouchTitle", "doubleTapExitNotification", "imageEditDiscardDialogTitle", "imageEditDiscardDialogContent", diff --git a/app/lib/widget/handler/enhance_handler.dart b/app/lib/widget/handler/enhance_handler.dart index 5e7edf48..6f40e17c 100644 --- a/app/lib/widget/handler/enhance_handler.dart +++ b/app/lib/widget/handler/enhance_handler.dart @@ -133,6 +133,19 @@ class EnhanceHandler { isSaveToServer: isSaveToServer, ); break; + + case _Algorithm.neurOp: + await ImageProcessor.neurOp( + "${account.url}/${file.path}", + file.filename, + Pref().getEnhanceMaxWidthOr(), + Pref().getEnhanceMaxHeightOr(), + headers: { + "Authorization": Api.getAuthorizationHeaderValue(account), + }, + isSaveToServer: isSaveToServer, + ); + break; } } @@ -207,6 +220,12 @@ class EnhanceHandler { ); List<_Option> _getOptions() => [ + if (platform_k.isAndroid) + _Option( + title: L10n.global().enhanceRetouchTitle, + link: enhanceRetouchUrl, + algorithm: _Algorithm.neurOp, + ), if (platform_k.isAndroid) _Option( title: L10n.global().enhanceColorPopTitle, @@ -260,6 +279,9 @@ class EnhanceHandler { case _Algorithm.deepLab3ColorPop: return _getDeepLab3ColorPopArgs(context); + + case _Algorithm.neurOp: + return {}; } } @@ -458,6 +480,7 @@ enum _Algorithm { esrgan, arbitraryStyleTransfer, deepLab3ColorPop, + neurOp, } class _Option { diff --git a/plugin/android/src/main/assets/tf/neurop_fivek_lite.tflite b/plugin/android/src/main/assets/tf/neurop_fivek_lite.tflite new file mode 100644 index 0000000000000000000000000000000000000000..09d2b0acb8b4adbb5484ff30d5d4c75ca0e7de44 GIT binary patch literal 58016 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

WRr6n$^{e}Xo;P;@M*R^8`nM{{#h8>odwJAO z$*@gIBai%0!H~~IidJk^$M}RCBejp+;X}9!AH6}}s*;Zedn7y%80qVMN$L}|zHX4` zDyF&6XB<;$k7VG#V<%(K_E`HP13rgLecCqi(XXtskMtyM6jNHi$0H_Yl-luFIi?_~ z6jS0aFwhr*dK#;WeM;US`qR)y`CWQ;)bEU(JjysB9{SEb3db{{^i(VD9sHrcwo{(G zGm_>p&8M+CUimpw+;_xlc~v_jlj+c9?fj)@Okbc7JkDo9?at_-j)&GD;yD5y=e|R| zE5i3{ZB4 zjK0XCoiG_c#rL#A&ONmXm6-$<}hm+m7TZ8tnXYIeU#v|Th zPqA;Ep)rY;WcoKHG$AzxQkQ*Wz`|d+0GXDC#Qvef)Vg%es~wooZ*{ zb$?+|WvAKwi3eHP9Cz--=u52klHb^v&4p}7pOx&B3uoA`wT`iterwtHKi_4;?*7eE zm-pdJai6jN`EB`krOQwY<2+>u#}r z(f{JkH1^`^rnd8Ul7e@ZC=VJI?FcVvWxs~rWz#-y#aTmM=DxFC zVtdb*vEMyAbJlAm?C$V}T<-7+R`W&>SG=eZhyQfVxGN9Y9|8N=jfpk6_fA#g-ur1S zyHNWe>$z+ccd%|XuJP>p+_dfo*v8m>?D>b?x$j1xPC9x z<$`9rbF)?+V)qYy&!!A`hkf%=C$8m%z3iiF{@k7aG~nL(q=aR@U6rd+;>B&uwXqg2 zZDpg!*5Hx0&j5D*n@lThw?nby;-dO=Y`@hc3vr4GprBy)SXe>=)7-O!H4y^=C=>A zGt&ohX~}V1@ytUkYF9h%#wUHa8ux>_#^&GH*CThZw|uH_1AN+Y8|QW44*%YYJ3p%{ zyZQH4R$92K{-TO-S_MdS5kh-=){$ zXm6=K^N17A%A|Bke@!p#5zHv*41z%S8}WRC!p)4AZRk@v!^KtO2=2YaZ{5-NFg?UO zale3ivqCgXXc+ zxYyXq4Xc^|!$Q{Z{1W!bmL)8E!fN(lWCk;}TE-UqozFPye3sR-7yENk37grmfbE#E znzbEqo(UyI?97!Iw(HIUHl$WItM2y|D>zon4quDe@@q(3#mC$fL6l^)t2ZyH=GpvlwEc zjWcyJ$u5#P5Vq4fj9R86uems{C7EFW{RRUHH^ol+P8-QAf=s=gir+3oniQGzn+R%| zbOuE-Z^<&%&pD6ld$vwyV_9Zj*iXKzWj?O&M|CpkZ{^6&Rgg)(t7Sf}@2&9M1y)Bq z=`56F-jHRg*Ph4qeTq&doo|!OaM(}2t7Sf}?}v0U8_F^ZA(MPp%Y0nlTj2Kw!0Pb5 zfh_ZyEYm0-KWZ$Q!Jnp+SzneJ2K&j*$}(LZr;Fp0WbW6=q_IzPeK}<6=ep8I>bXwy zo5s2_-sv|H=&dq`==_f4mC5pq#*tcH7%|Hr*@-%NbgoD8LSQ%9rsW{E+088pwMb`-bb})q*VgjG+zern6Ysr~Rzh#h_7QxK1ap zCS>>k8IVV^4DA(X}==7A{y9c85h5fBjYIP2>W=S8S(zlGGlTz><#)Ncn z+>jpn4F(jBF-Yqgoz;$qEXtLJ-yaT;zO`~v$6F2b6#ruIQv8t%o+$o)ay*s!P*s+9 zOO}W6`$Y1l=;YD4Gx-({yUDjo_DO#S;P|{X1~j{LGH9NWjQO&RN;VnDpuVKvIHhlv zs()`o=}X05>JO4#Cd;lgeqG3>ae^I%C>=L=&eV<*l1ZR(ZUH>xd$=ZEr_2}PqjhRDD#Cy^fHy$D4|)srWk4xV0&E6~ zfP5eaNCjem!9W-g2v9DdD3N`>u!q(bTn+tkA1mF*dZ3lsEvTbsEnL2RaWBuk8j-lC zIO1}T!TkW~en2wriPZkC5&o@^Rlvx^G4GG)BlrT#Yy2{ZQ$C=nmXx zNcR~fxTp0BR|EWMe+7M>kE`c7+|xRPt35snsgOUP>v{|KQBr*r?F%TckjIn@2Z{Hl UL0;%dS__o@0@*-w0aum(1KqytEC2ui literal 0 HcmV?d00001 diff --git a/plugin/android/src/main/cpp/CMakeLists.txt b/plugin/android/src/main/cpp/CMakeLists.txt index c1c6c6f6..834e8ded 100644 --- a/plugin/android/src/main/cpp/CMakeLists.txt +++ b/plugin/android/src/main/cpp/CMakeLists.txt @@ -50,6 +50,7 @@ add_library( # Sets the name of the library. esrgan.cpp exception.cpp image_splitter.cpp + neur_op.cpp stopwatch.cpp tflite_wrapper.cpp util.cpp diff --git a/plugin/android/src/main/cpp/neur_op.cpp b/plugin/android/src/main/cpp/neur_op.cpp new file mode 100644 index 00000000..52543c71 --- /dev/null +++ b/plugin/android/src/main/cpp/neur_op.cpp @@ -0,0 +1,95 @@ +#include "exception.h" +#include "log.h" +#include "stopwatch.h" +#include "tflite_wrapper.h" +#include "util.h" +#include +#include +#include +#include +#include +#include + +using namespace plugin; +using namespace std; +using namespace tflite; + +namespace { + +constexpr const char *MODEL = "tf/neurop_fivek_lite.tflite"; + +class NeurOp { +public: + explicit NeurOp(AAssetManager *const aam); + + std::vector infer(const uint8_t *image, const size_t width, + const size_t height); + +private: + Model model; + + static constexpr const char *TAG = "NeurOp"; +}; + +} // namespace + +extern "C" JNIEXPORT jbyteArray JNICALL +Java_com_nkming_nc_1photos_plugin_image_1processor_NeurOp_inferNative( + JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, + jint width, jint height) { + try { + initOpenMp(); + auto aam = AAssetManager_fromJava(env, assetManager); + NeurOp model(aam); + RaiiContainer cImage( + [&]() { return env->GetByteArrayElements(image, nullptr); }, + [&](jbyte *obj) { + env->ReleaseByteArrayElements(image, obj, JNI_ABORT); + }); + const auto result = + model.infer(reinterpret_cast(cImage.get()), width, height); + auto resultAry = env->NewByteArray(result.size()); + env->SetByteArrayRegion(resultAry, 0, result.size(), + reinterpret_cast(result.data())); + return resultAry; + } catch (const exception &e) { + throwJavaException(env, e.what()); + return nullptr; + } +} + +namespace { + +NeurOp::NeurOp(AAssetManager *const aam) : model(Asset(aam, MODEL)) {} + +vector NeurOp::infer(const uint8_t *image, const size_t width, + const size_t height) { + InterpreterOptions options; + options.setNumThreads(getNumberOfProcessors()); + Interpreter interpreter(model, options); + const int dims[] = {1, static_cast(height), static_cast(width), 3}; + interpreter.resizeInputTensor(0, dims, 4); + interpreter.allocateTensors(); + + LOGI(TAG, "[infer] Convert bitmap to input"); + auto input = rgb8ToRgbFloat(image, width * height * 3, true); + auto inputTensor = interpreter.getInputTensor(0); + assert(TfLiteTensorByteSize(inputTensor) == input.size() * sizeof(float)); + TfLiteTensorCopyFromBuffer(inputTensor, input.data(), + input.size() * sizeof(float)); + input.clear(); + + LOGI(TAG, "[infer] Inferring"); + Stopwatch stopwatch; + interpreter.invoke(); + LOGI(TAG, "[infer] Elapsed: %.3fs", stopwatch.getMs() / 1000.0f); + + auto outputTensor = interpreter.getOutputTensor(0); + vector output(width * height * 3); + assert(TfLiteTensorByteSize(outputTensor) == output.size() * sizeof(float)); + TfLiteTensorCopyToBuffer(outputTensor, output.data(), + output.size() * sizeof(float)); + return rgbFloatToRgb8(output.data(), output.size(), true); +} + +} // namespace diff --git a/plugin/android/src/main/cpp/neur_op.h b/plugin/android/src/main/cpp/neur_op.h new file mode 100644 index 00000000..4c4c504e --- /dev/null +++ b/plugin/android/src/main/cpp/neur_op.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jbyteArray JNICALL +Java_com_nkming_nc_1photos_plugin_image_1processor_NeurOp_inferNative( + JNIEnv *env, jobject *thiz, jobject assetManager, jbyteArray image, + jint width, jint height); + +#ifdef __cplusplus +} +#endif 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 index 35fa34f5..93493434 100644 --- 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 @@ -110,6 +110,23 @@ class ImageProcessorChannelHandler(context: Context) : } } + "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( @@ -210,6 +227,15 @@ class ImageProcessorChannelHandler(context: Context) : } ) + 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, 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 index 739a8429..a836f875 100644 --- 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 @@ -32,6 +32,7 @@ class ImageProcessorService : Service() { 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" @@ -122,6 +123,7 @@ class ImageProcessorService : Service() { 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") @@ -186,6 +188,12 @@ class ImageProcessorService : Service() { ) } + 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>() @@ -543,6 +551,16 @@ private class ImageProcessorDeepLapColorPopCommand( 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, 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 new file mode 100644 index 00000000..cab579c6 --- /dev/null +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/NeurOp.kt @@ -0,0 +1,38 @@ +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/lib/src/image_processor.dart b/plugin/lib/src/image_processor.dart index 1106b3c4..23722481 100644 --- a/plugin/lib/src/image_processor.dart +++ b/plugin/lib/src/image_processor.dart @@ -170,6 +170,23 @@ class ImageProcessor { "isSaveToServer": isSaveToServer, }); + static Future neurOp( + String fileUrl, + String filename, + int maxWidth, + int maxHeight, { + Map? headers, + required bool isSaveToServer, + }) => + _methodChannel.invokeMethod("neurOp", { + "fileUrl": fileUrl, + "headers": headers, + "filename": filename, + "maxWidth": maxWidth, + "maxHeight": maxHeight, + "isSaveToServer": isSaveToServer, + }); + static Future filter( String fileUrl, String filename,