diff --git a/README.md b/README.md
index 2a58433..40e1e7b 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,17 @@ The power of AI in the palm of my hands!
This is AI utility app for Android device, it basically uses AI model directly on-device to help on performing some tasks.
+## Features
+### Face Detection & Recognition
+
+ - Detect and add blur effect on people face and export the result to gallery
+ - Detect and select which face to be blurred and export the result to gallery
+
+
+### Neural Style Transfer
Current features:
- - For image files: Detect and add blur effect on people face and export the image to gallery
+ - Select neural style transfer theme to apply to the image(s) and export the result to gallery
diff --git a/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/NavigatorProvider.java b/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/NavigatorProvider.java
index 3a35763..f8437ad 100644
--- a/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/NavigatorProvider.java
+++ b/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/NavigatorProvider.java
@@ -18,6 +18,7 @@
import m.co.rh.id.a_jarwis.app.ui.page.HomePage;
import m.co.rh.id.a_jarwis.app.ui.page.SplashPage;
import m.co.rh.id.a_jarwis.app.ui.page.common.SelectFaceImagePage;
+import m.co.rh.id.a_jarwis.app.ui.page.common.SelectNSTThemePage;
import m.co.rh.id.a_jarwis.app.ui.page.common.ShowMessagePage;
import m.co.rh.id.a_jarwis.base.constants.Routes;
import m.co.rh.id.a_jarwis.settings.ui.page.SettingsPage;
@@ -51,12 +52,13 @@ public INavigator getNavigator(Activity activity) {
@SuppressLint("InflateParams")
@SuppressWarnings("unchecked")
- private Navigator setupMainActivityNavigator() {
+ private void setupMainActivityNavigator() {
Map navMap = new ArrayMap<>();
navMap.put(Routes.SPLASH_PAGE, (args, activity) -> new SplashPage(Routes.HOME_PAGE));
navMap.put(Routes.HOME_PAGE, (args, activity) -> new HomePage());
navMap.put(Routes.SHOW_MESSAGE_PAGE, (args, activity) -> new ShowMessagePage());
navMap.put(Routes.SELECT_FACE_IMAGE_PAGE, (args, activity) -> new SelectFaceImagePage());
+ navMap.put(Routes.SELECT_NST_THEME_PAGE, (args, activity) -> new SelectNSTThemePage());
navMap.put(Routes.SETTINGS_PAGE, (args, activity) -> new SettingsPage());
navMap.put(Routes.DONATIONS_PAGE, (args, activity) -> new DonationsPage());
navMap.putAll(mNavExtDialogConfig.getNavMap());
@@ -71,7 +73,6 @@ private Navigator setupMainActivityNavigator() {
mActivityNavigatorMap.put(MainActivity.class, navigator);
mApplication.registerActivityLifecycleCallbacks(navigator);
mApplication.registerComponentCallbacks(navigator);
- return navigator;
}
@Override
diff --git a/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/command/CommandProviderModule.java b/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/command/CommandProviderModule.java
index 091e75e..9a3c559 100644
--- a/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/command/CommandProviderModule.java
+++ b/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/command/CommandProviderModule.java
@@ -8,5 +8,6 @@ public class CommandProviderModule implements ProviderModule {
@Override
public void provides(ProviderRegistry providerRegistry, Provider provider) {
providerRegistry.registerLazy(BlurFaceCommand.class, () -> new BlurFaceCommand(provider));
+ providerRegistry.registerLazy(NSTApplyCommand.class, () -> new NSTApplyCommand(provider));
}
}
diff --git a/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/command/NSTApplyCommand.java b/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/command/NSTApplyCommand.java
new file mode 100644
index 0000000..66439c4
--- /dev/null
+++ b/app/src/main/java/m/co/rh/id/a_jarwis/app/provider/command/NSTApplyCommand.java
@@ -0,0 +1,34 @@
+package m.co.rh.id.a_jarwis.app.provider.command;
+
+import android.net.Uri;
+
+import java.io.File;
+import java.util.concurrent.ExecutorService;
+
+import io.reactivex.rxjava3.core.Single;
+import m.co.rh.id.a_jarwis.base.provider.component.helper.FileHelper;
+import m.co.rh.id.a_jarwis.ml_engine.provider.component.NSTEngine;
+import m.co.rh.id.aprovider.Provider;
+
+public class NSTApplyCommand {
+ private final ExecutorService mExecutorService;
+ private final FileHelper mFileHelper;
+ private final NSTEngine mNSTEngine;
+
+ public NSTApplyCommand(Provider provider) {
+ mExecutorService = provider.get(ExecutorService.class);
+ mFileHelper = provider.get(FileHelper.class);
+ mNSTEngine = provider.get(NSTEngine.class);
+ }
+
+ public Single execute(Uri uriFile, int theme) {
+ return Single.fromFuture(mExecutorService.submit(() -> {
+ String fullPath = uriFile.getPath();
+ int cut = fullPath.lastIndexOf("/");
+ File result = mFileHelper
+ .createImageTempFile(fullPath.substring(cut + 1), uriFile);
+ mNSTEngine.enqueueNsT(result, theme);
+ return result;
+ }));
+ }
+}
diff --git a/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/HomePage.java b/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/HomePage.java
index 4fc8323..b9f052d 100644
--- a/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/HomePage.java
+++ b/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/HomePage.java
@@ -26,9 +26,11 @@
import io.reactivex.rxjava3.schedulers.Schedulers;
import m.co.rh.id.a_jarwis.R;
import m.co.rh.id.a_jarwis.app.provider.command.BlurFaceCommand;
+import m.co.rh.id.a_jarwis.app.provider.command.NSTApplyCommand;
import m.co.rh.id.a_jarwis.app.ui.page.nav.param.FileList;
import m.co.rh.id.a_jarwis.app.ui.page.nav.param.MessageText;
import m.co.rh.id.a_jarwis.app.ui.page.nav.param.SelectedChoice;
+import m.co.rh.id.a_jarwis.app.ui.page.nav.param.SelectedTheme;
import m.co.rh.id.a_jarwis.base.constants.Routes;
import m.co.rh.id.a_jarwis.base.provider.IStatefulViewProvider;
import m.co.rh.id.a_jarwis.base.rx.RxDisposer;
@@ -50,6 +52,7 @@ public class HomePage extends StatefulView implements RequireComponent
private static final int REQUEST_CODE_IMAGE_AUTO_BLUR_FACE = 1;
private static final int REQUEST_CODE_IMAGE_EXCLUDE_BLUR_FACE = 2;
private static final int REQUEST_CODE_IMAGE_SELECTIVE_BLUR_FACE = 3;
+ private static final int REQUEST_CODE_IMAGE_NST_APPLY_PICTURE = 4;
@NavInject
private transient INavigator mNavigator;
@@ -64,12 +67,14 @@ public class HomePage extends StatefulView implements RequireComponent
private transient ExecutorService mExecutorService;
private transient RxDisposer mRxDisposer;
private transient BlurFaceCommand mBlurFaceCommand;
+ private transient NSTApplyCommand mNSTApplyCommand;
// View related
private transient DrawerLayout mDrawerLayout;
private transient View.OnClickListener mOnNavigationClicked;
private ArrayList mfacesList;
+ private SelectedTheme mSelectedNSTTheme;
public HomePage() {
mAppBarSV = new AppBarSV();
@@ -82,6 +87,7 @@ public void provideComponent(Provider provider) {
mExecutorService = mSvProvider.get(ExecutorService.class);
mRxDisposer = mSvProvider.get(RxDisposer.class);
mBlurFaceCommand = mSvProvider.get(BlurFaceCommand.class);
+ mNSTApplyCommand = mSvProvider.get(NSTApplyCommand.class);
mOnNavigationClicked = view -> {
if (!mDrawerLayout.isOpen()) {
mDrawerLayout.open();
@@ -109,6 +115,8 @@ protected View createView(Activity activity, ViewGroup container) {
excludeBlurButton.setOnClickListener(this);
Button selectiveBlurButton = rootLayout.findViewById(R.id.button_selective_blur_face);
selectiveBlurButton.setOnClickListener(this);
+ Button nstApplyPictureButton = rootLayout.findViewById(R.id.button_nst_apply_picture);
+ nstApplyPictureButton.setOnClickListener(this);
ViewGroup containerAppBar = rootLayout.findViewById(R.id.container_app_bar);
containerAppBar.addView(mAppBarSV.buildView(activity, container));
return rootLayout;
@@ -191,6 +199,40 @@ public void onClick(View view) {
} else {
startSelectiveAutoBlur();
}
+ } else if (id == R.id.button_nst_apply_picture) {
+ Activity activity = mNavigator.getActivity();
+ if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_IMAGE_NST_APPLY_PICTURE);
+ } else {
+ startNstApplyPicture();
+ }
+ }
+ }
+
+ private void startNstApplyPicture() {
+ int title = m.co.rh.id.a_jarwis.base.R.string.title_what_to_do;
+ int body = m.co.rh.id.a_jarwis.base.R.string.pick_image_for_nst_apply_picture;
+ mNavigator.push(Routes.SHOW_MESSAGE_PAGE, new MessageText(title, body, true)
+ , (navigator, navRoute, activity1, currentView) ->
+ startNstApplyPicture_processFirstRespond(navRoute));
+ }
+
+ private void startNstApplyPicture_processFirstRespond(NavRoute navRoute) {
+ Serializable serializable = navRoute.getRouteResult();
+ if (serializable instanceof SelectedChoice) {
+ int selectedChoice = ((SelectedChoice) serializable).getSelectedChoice();
+ if (SelectedChoice.POSITIVE == selectedChoice) {
+ mNavigator.push(Routes.SELECT_NST_THEME_PAGE, (navigator, navRoute1, activity1, currentView) ->
+ startNstApplyPicture_processSecondRespond(activity1, navRoute1));
+ }
+ }
+ }
+
+ private void startNstApplyPicture_processSecondRespond(Activity activity, NavRoute navRoute) {
+ Serializable serializable = navRoute.getRouteResult();
+ if (serializable instanceof SelectedTheme) {
+ mSelectedNSTTheme = (SelectedTheme) serializable;
+ pickImage(activity, REQUEST_CODE_IMAGE_NST_APPLY_PICTURE);
}
}
@@ -363,6 +405,40 @@ public void onActivityResult(View currentView, Activity activity, INavigator INa
.subscribeOn(Schedulers.from(mExecutorService))
.subscribe(consumeFile));
}
+ } else if (requestCode == REQUEST_CODE_IMAGE_NST_APPLY_PICTURE && resultCode == Activity.RESULT_OK) {
+ ClipData listPhoto = data.getClipData();
+ Uri fullPhotoUri = data.getData();
+ BiConsumer consumeFile = (file, throwable) -> {
+ if (throwable != null) {
+ mLogger.e(TAG, throwable.getMessage(), throwable);
+ } else {
+ mLogger.i(TAG, mSvProvider.getContext()
+ .getString(m.co.rh.id.a_jarwis.base.R.string.processing_,
+ file.getName()));
+ }
+ };
+ if (listPhoto != null) {
+ int count = listPhoto.getItemCount();
+ List uriList = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ uriList.add(listPhoto.getItemAt(i).getUri());
+ }
+ if (!uriList.isEmpty()) {
+ mRxDisposer.add("onActivityResult_nstApplyPicture_multiple",
+ Flowable.fromIterable(uriList)
+ .map(uri -> mNSTApplyCommand.execute(uri, mSelectedNSTTheme.getSelectedTheme())
+ .blockingGet())
+ .subscribeOn(Schedulers.from(mExecutorService))
+ .doOnError(throwable -> consumeFile.accept(null, throwable))
+ .subscribe(file -> consumeFile.accept(file, null))
+ );
+ }
+ } else {
+ mRxDisposer.add("onActivityResult_nstApplyPicture",
+ mNSTApplyCommand.execute(fullPhotoUri, mSelectedNSTTheme.getSelectedTheme())
+ .subscribeOn(Schedulers.from(mExecutorService))
+ .subscribe(consumeFile));
+ }
}
}
@@ -378,6 +454,16 @@ public void onRequestPermissionsResult(View currentView, Activity activity, INav
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
startExcludeAutoBlur();
+ } else if (requestCode == REQUEST_CODE_IMAGE_SELECTIVE_BLUR_FACE
+ && grantResults.length > 0 &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED
+ ) {
+ startSelectiveAutoBlur();
+ } else if (requestCode == REQUEST_CODE_IMAGE_NST_APPLY_PICTURE
+ && grantResults.length > 0 &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED
+ ) {
+ startNstApplyPicture();
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/common/SelectNSTThemePage.java b/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/common/SelectNSTThemePage.java
new file mode 100644
index 0000000..caeb22b
--- /dev/null
+++ b/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/common/SelectNSTThemePage.java
@@ -0,0 +1,54 @@
+package m.co.rh.id.a_jarwis.app.ui.page.common;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.ViewGroup;
+
+import m.co.rh.id.a_jarwis.R;
+import m.co.rh.id.a_jarwis.app.ui.page.nav.param.SelectedTheme;
+import m.co.rh.id.a_jarwis.ml_engine.provider.component.NSTEngine;
+import m.co.rh.id.anavigator.StatefulView;
+import m.co.rh.id.anavigator.annotation.NavInject;
+import m.co.rh.id.anavigator.component.INavigator;
+
+public class SelectNSTThemePage extends StatefulView implements View.OnClickListener {
+
+ @NavInject
+ private transient INavigator mNavigator;
+
+ @Override
+ protected View createView(Activity activity, ViewGroup container) {
+ View rootView = activity.getLayoutInflater().inflate(R.layout.page_select_nst_theme, container, false);
+ View mosaic = rootView.findViewById(R.id.container_mosaic_theme);
+ mosaic.setOnClickListener(this);
+ View candy = rootView.findViewById(R.id.container_candy_theme);
+ candy.setOnClickListener(this);
+ View rainPrincess = rootView.findViewById(R.id.container_rain_princess_theme);
+ rainPrincess.setOnClickListener(this);
+ View udnie = rootView.findViewById(R.id.container_udnie_theme);
+ udnie.setOnClickListener(this);
+ View pointilism = rootView.findViewById(R.id.container_pointilism_theme);
+ pointilism.setOnClickListener(this);
+ View backButton = rootView.findViewById(R.id.button_back);
+ backButton.setOnClickListener(this);
+ return rootView;
+ }
+
+ @Override
+ public void onClick(View view) {
+ int id = view.getId();
+ if (id == R.id.container_mosaic_theme) {
+ mNavigator.pop(new SelectedTheme(NSTEngine.THEME_MOSAIC));
+ } else if (id == R.id.container_candy_theme) {
+ mNavigator.pop(new SelectedTheme(NSTEngine.THEME_CANDY));
+ } else if (id == R.id.container_rain_princess_theme) {
+ mNavigator.pop(new SelectedTheme(NSTEngine.THEME_RAIN_PRINCESS));
+ } else if (id == R.id.container_udnie_theme) {
+ mNavigator.pop(new SelectedTheme(NSTEngine.THEME_UDNIE));
+ } else if (id == R.id.container_pointilism_theme) {
+ mNavigator.pop(new SelectedTheme(NSTEngine.THEME_POINTILISM));
+ } else if (id == R.id.button_back) {
+ mNavigator.pop();
+ }
+ }
+}
diff --git a/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/nav/param/SelectedTheme.java b/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/nav/param/SelectedTheme.java
new file mode 100644
index 0000000..b70505b
--- /dev/null
+++ b/app/src/main/java/m/co/rh/id/a_jarwis/app/ui/page/nav/param/SelectedTheme.java
@@ -0,0 +1,15 @@
+package m.co.rh.id.a_jarwis.app.ui.page.nav.param;
+
+import java.io.Serializable;
+
+public class SelectedTheme implements Serializable {
+ private int selectedTheme;
+
+ public SelectedTheme(int selectedTheme) {
+ this.selectedTheme = selectedTheme;
+ }
+
+ public int getSelectedTheme() {
+ return selectedTheme;
+ }
+}
diff --git a/app/src/main/res/drawable/nst_theme_candy.jpg b/app/src/main/res/drawable/nst_theme_candy.jpg
new file mode 100644
index 0000000..f40e5a3
Binary files /dev/null and b/app/src/main/res/drawable/nst_theme_candy.jpg differ
diff --git a/app/src/main/res/drawable/nst_theme_mosaic.jpg b/app/src/main/res/drawable/nst_theme_mosaic.jpg
new file mode 100644
index 0000000..63aa06f
Binary files /dev/null and b/app/src/main/res/drawable/nst_theme_mosaic.jpg differ
diff --git a/app/src/main/res/drawable/nst_theme_pointilism.jpg b/app/src/main/res/drawable/nst_theme_pointilism.jpg
new file mode 100644
index 0000000..ac64b37
Binary files /dev/null and b/app/src/main/res/drawable/nst_theme_pointilism.jpg differ
diff --git a/app/src/main/res/drawable/nst_theme_rain_princess.jpg b/app/src/main/res/drawable/nst_theme_rain_princess.jpg
new file mode 100644
index 0000000..520f6a2
Binary files /dev/null and b/app/src/main/res/drawable/nst_theme_rain_princess.jpg differ
diff --git a/app/src/main/res/drawable/nst_theme_udnie.jpg b/app/src/main/res/drawable/nst_theme_udnie.jpg
new file mode 100644
index 0000000..3dbb29c
Binary files /dev/null and b/app/src/main/res/drawable/nst_theme_udnie.jpg differ
diff --git a/app/src/main/res/layout/page_home.xml b/app/src/main/res/layout/page_home.xml
index 09f2e4f..07ec227 100644
--- a/app/src/main/res/layout/page_home.xml
+++ b/app/src/main/res/layout/page_home.xml
@@ -52,6 +52,17 @@
android:gravity="center"
android:text="@string/title_selective_blur_face"
app:icon="@drawable/ic_face_black" />
+
+
diff --git a/app/src/main/res/layout/page_select_nst_theme.xml b/app/src/main/res/layout/page_select_nst_theme.xml
new file mode 100644
index 0000000..1df9fda
--- /dev/null
+++ b/app/src/main/res/layout/page_select_nst_theme.xml
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/base/src/main/java/m/co/rh/id/a_jarwis/base/constants/Routes.java b/base/src/main/java/m/co/rh/id/a_jarwis/base/constants/Routes.java
index 3e01ccc..e115abd 100644
--- a/base/src/main/java/m/co/rh/id/a_jarwis/base/constants/Routes.java
+++ b/base/src/main/java/m/co/rh/id/a_jarwis/base/constants/Routes.java
@@ -6,6 +6,7 @@ public class Routes {
public static final String SHOW_MESSAGE_PAGE = "/common/showMessage";
public static final String SELECT_FACE_IMAGE_PAGE = "/common/selectFaceImage";
+ public static final String SELECT_NST_THEME_PAGE = "/common/selectNstTheme";
public static final String SETTINGS_PAGE = "/settings";
public static final String DONATIONS_PAGE = "/donations";
diff --git a/base/src/main/java/m/co/rh/id/a_jarwis/base/provider/component/helper/FileHelper.java b/base/src/main/java/m/co/rh/id/a_jarwis/base/provider/component/helper/FileHelper.java
index 168a78f..f266e91 100644
--- a/base/src/main/java/m/co/rh/id/a_jarwis/base/provider/component/helper/FileHelper.java
+++ b/base/src/main/java/m/co/rh/id/a_jarwis/base/provider/component/helper/FileHelper.java
@@ -30,10 +30,10 @@
public class FileHelper {
private static final String TAG = FileHelper.class.getName();
- private Context mAppContext;
- private ProviderValue mLogger;
- private File mLogFile;
- private File mTempFileRoot;
+ private final Context mAppContext;
+ private final ProviderValue mLogger;
+ private final File mLogFile;
+ private final File mTempFileRoot;
public FileHelper(Provider provider) {
mAppContext = provider.getContext().getApplicationContext();
@@ -122,15 +122,23 @@ public File getLogFile() {
}
public File createImageTempFile() throws IOException {
+ return createImageTempFile(UUID.randomUUID().toString());
+ }
+
+ public File createImageTempFile(String fileName) throws IOException {
File parent = new File(mTempFileRoot, UUID.randomUUID().toString());
parent.mkdirs();
- File tmpFile = new File(parent, UUID.randomUUID().toString() + ".jpg");
+ File tmpFile = new File(parent, fileName + ".jpg");
tmpFile.createNewFile();
return tmpFile;
}
public File createImageTempFile(Uri content) throws IOException {
- File outFile = createImageTempFile();
+ return createImageTempFile(UUID.randomUUID().toString(), content);
+ }
+
+ public File createImageTempFile(String fileName, Uri content) throws IOException {
+ File outFile = createImageTempFile(fileName);
try {
copyImage(content, outFile);
return outFile;
diff --git a/base/src/main/res/drawable-anydpi/ic_palette_black.xml b/base/src/main/res/drawable-anydpi/ic_palette_black.xml
new file mode 100644
index 0000000..577ea23
--- /dev/null
+++ b/base/src/main/res/drawable-anydpi/ic_palette_black.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/base/src/main/res/drawable-hdpi/ic_palette_black.png b/base/src/main/res/drawable-hdpi/ic_palette_black.png
new file mode 100644
index 0000000..7b2ba90
Binary files /dev/null and b/base/src/main/res/drawable-hdpi/ic_palette_black.png differ
diff --git a/base/src/main/res/drawable-mdpi/ic_palette_black.png b/base/src/main/res/drawable-mdpi/ic_palette_black.png
new file mode 100644
index 0000000..2d5ed44
Binary files /dev/null and b/base/src/main/res/drawable-mdpi/ic_palette_black.png differ
diff --git a/base/src/main/res/drawable-xhdpi/ic_palette_black.png b/base/src/main/res/drawable-xhdpi/ic_palette_black.png
new file mode 100644
index 0000000..9dc51f2
Binary files /dev/null and b/base/src/main/res/drawable-xhdpi/ic_palette_black.png differ
diff --git a/base/src/main/res/drawable-xxhdpi/ic_palette_black.png b/base/src/main/res/drawable-xxhdpi/ic_palette_black.png
new file mode 100644
index 0000000..f7b5b85
Binary files /dev/null and b/base/src/main/res/drawable-xxhdpi/ic_palette_black.png differ
diff --git a/base/src/main/res/values/colors.xml b/base/src/main/res/values/colors.xml
index 9e1921c..190d1e4 100644
--- a/base/src/main/res/values/colors.xml
+++ b/base/src/main/res/values/colors.xml
@@ -7,6 +7,7 @@
#00E0E0
#E00070
#E07000
+ #99616161
@color/white
@color/gray_700
@color/black
diff --git a/base/src/main/res/values/dimens.xml b/base/src/main/res/values/dimens.xml
index d695c2b..fd90579 100644
--- a/base/src/main/res/values/dimens.xml
+++ b/base/src/main/res/values/dimens.xml
@@ -9,4 +9,5 @@
18sp
6sp
50sp
+ 25sp
\ No newline at end of file
diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml
index c231300..c270437 100644
--- a/base/src/main/res/values/strings.xml
+++ b/base/src/main/res/values/strings.xml
@@ -9,17 +9,24 @@
Auto Blur Face
Exclude Blur Face
Selective Blur Face
+ Apply Neural Style Transfer to Picture
Press BACK again to exit
Buy developer a coffee
Processing %s
Done processing: %s
- No image detected: %s
+ No face detected: %s
Back
Next
Tap to select image
What to do?
Pick image with face(s) to be excluded from blur and then select image(s) from gallery that you want to blur the face(s)
Pick image with face(s) to select the face(s) to be blurred and then select image(s) from gallery on which the face(s) to be blurred
+ Pick neural style transfer theme to be applied to image and then select image(s) from gallery on which image(s) to be applied on
No face selected
No Face
+ Mosaic
+ Candy
+ Rain Princess
+ Udnie
+ Pointilism
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index a0f12cd..d7e76f0 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/FaceEngine.java b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/FaceEngine.java
index a91d62c..b06a66a 100644
--- a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/FaceEngine.java
+++ b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/FaceEngine.java
@@ -26,9 +26,9 @@
import m.co.rh.id.a_jarwis.base.provider.component.helper.FileHelper;
import m.co.rh.id.a_jarwis.base.util.BitmapUtils;
import m.co.rh.id.a_jarwis.base.util.SerializeUtils;
-import m.co.rh.id.a_jarwis.ml_engine.workmanager.BlurFaceSerialFile;
import m.co.rh.id.a_jarwis.ml_engine.workmanager.BlurFaceWorkRequest;
import m.co.rh.id.a_jarwis.ml_engine.workmanager.Params;
+import m.co.rh.id.a_jarwis.ml_engine.workmanager.model.BlurFaceSerialFile;
import m.co.rh.id.alogger.ILogger;
import m.co.rh.id.aprovider.Provider;
import m.co.rh.id.aprovider.ProviderValue;
diff --git a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/NSTEngine.java b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/NSTEngine.java
index dddf168..8595543 100644
--- a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/NSTEngine.java
+++ b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/provider/component/NSTEngine.java
@@ -2,34 +2,107 @@
import android.graphics.Bitmap;
+import androidx.work.Data;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+import m.co.rh.id.a_jarwis.base.provider.component.helper.FileHelper;
+import m.co.rh.id.a_jarwis.base.util.SerializeUtils;
+import m.co.rh.id.a_jarwis.ml_engine.workmanager.NSTApplyWorkRequest;
+import m.co.rh.id.a_jarwis.ml_engine.workmanager.Params;
+import m.co.rh.id.a_jarwis.ml_engine.workmanager.model.NSTApplySerialFile;
+import m.co.rh.id.alogger.ILogger;
import m.co.rh.id.aprovider.Provider;
import m.co.rh.id.aprovider.ProviderValue;
public class NSTEngine {
+ public static final int THEME_MOSAIC = 1;
+ public static final int THEME_CANDY = 2;
+ public static final int THEME_RAIN_PRINCESS = 3;
+ public static final int THEME_UDNIE = 4;
+ public static final int THEME_POINTILISM = 5;
- private final ProviderValue mlEngine;
+ private static final String TAG = "NSTEngine";
+
+ private final WorkManager mWorkManager;
+ private final ILogger mLogger;
+ private final ProviderValue mMLEngine;
+ private final FileHelper mFileHelper;
public NSTEngine(Provider provider) {
- mlEngine = provider.lazyGet(MLEngineInstance.class);
+ mWorkManager = provider.get(WorkManager.class);
+ mLogger = provider.get(ILogger.class);
+ mMLEngine = provider.lazyGet(MLEngineInstance.class);
+ mFileHelper = provider.get(FileHelper.class);
}
public Bitmap applyMosaic(Bitmap bitmap) {
- return mlEngine.get().getNSTMosaic().process(bitmap);
+ return mMLEngine.get().getNSTMosaic().process(bitmap);
}
public Bitmap applyCandy(Bitmap bitmap) {
- return mlEngine.get().getNSTCandy().process(bitmap);
+ return mMLEngine.get().getNSTCandy().process(bitmap);
}
public Bitmap applyRainPrincess(Bitmap bitmap) {
- return mlEngine.get().getNSTRainPrincess().process(bitmap);
+ return mMLEngine.get().getNSTRainPrincess().process(bitmap);
}
public Bitmap applyUdnie(Bitmap bitmap) {
- return mlEngine.get().getNSTUdnie().process(bitmap);
+ return mMLEngine.get().getNSTUdnie().process(bitmap);
}
public Bitmap applyPointilism(Bitmap bitmap) {
- return mlEngine.get().getNSTPointilism().process(bitmap);
+ return mMLEngine.get().getNSTPointilism().process(bitmap);
+ }
+
+ public Bitmap apply(Bitmap bitmap, int theme) {
+ switch (theme) {
+ case THEME_MOSAIC:
+ return applyMosaic(bitmap);
+ case THEME_CANDY:
+ return applyCandy(bitmap);
+ case THEME_RAIN_PRINCESS:
+ return applyRainPrincess(bitmap);
+ case THEME_UDNIE:
+ return applyUdnie(bitmap);
+ case THEME_POINTILISM:
+ return applyPointilism(bitmap);
+ default:
+ return bitmap;
+ }
+ }
+
+ public void enqueueNsT(File imageFile, int theme) {
+ ObjectOutputStream oos = null;
+ try {
+ File serialFile = mFileHelper.createTempFile();
+ oos = new ObjectOutputStream(new FileOutputStream(serialFile));
+ NSTApplySerialFile nstApplySerialFile = new NSTApplySerialFile(imageFile, theme);
+ oos.writeObject(nstApplySerialFile);
+ oos.close();
+ Data.Builder inputBuilder = new Data.Builder();
+ inputBuilder.putByteArray(Params.SERIAL_FILE, SerializeUtils.serialize(serialFile));
+ OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(NSTApplyWorkRequest.class)
+ .setInputData(inputBuilder.build())
+ .build();
+ mWorkManager.enqueue(oneTimeWorkRequest);
+ } catch (Exception e) {
+ mLogger.e(TAG, e.getMessage(), e);
+ throw new RuntimeException(e);
+ } finally {
+ if (oos != null) {
+ try {
+ oos.close();
+ } catch (IOException e) {
+ mLogger.e(TAG, e.getMessage(), e);
+ }
+ }
+ }
}
}
diff --git a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/BlurFaceWorkRequest.java b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/BlurFaceWorkRequest.java
index 7ee91c5..de0d259 100644
--- a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/BlurFaceWorkRequest.java
+++ b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/BlurFaceWorkRequest.java
@@ -19,6 +19,7 @@
import m.co.rh.id.a_jarwis.base.provider.component.helper.MediaHelper;
import m.co.rh.id.a_jarwis.base.util.SerializeUtils;
import m.co.rh.id.a_jarwis.ml_engine.provider.component.FaceEngine;
+import m.co.rh.id.a_jarwis.ml_engine.workmanager.model.BlurFaceSerialFile;
import m.co.rh.id.alogger.ILogger;
import m.co.rh.id.aprovider.Provider;
@@ -65,7 +66,7 @@ public Result doWork() {
.getString(m.co.rh.id.a_jarwis.base.R.string.done_processing_, fileName));
} else {
logger.i(TAG, getApplicationContext()
- .getString(m.co.rh.id.a_jarwis.base.R.string.no_image_detected_, fileName));
+ .getString(m.co.rh.id.a_jarwis.base.R.string.no_face_detected_, fileName));
}
} catch (Exception e) {
logger.e(TAG, e.getMessage(), e);
diff --git a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/NSTApplyWorkRequest.java b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/NSTApplyWorkRequest.java
new file mode 100644
index 0000000..e5d60b7
--- /dev/null
+++ b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/NSTApplyWorkRequest.java
@@ -0,0 +1,78 @@
+package m.co.rh.id.a_jarwis.ml_engine.workmanager;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import androidx.annotation.NonNull;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+import m.co.rh.id.a_jarwis.base.BaseApplication;
+import m.co.rh.id.a_jarwis.base.provider.component.helper.MediaHelper;
+import m.co.rh.id.a_jarwis.base.util.SerializeUtils;
+import m.co.rh.id.a_jarwis.ml_engine.provider.component.NSTEngine;
+import m.co.rh.id.a_jarwis.ml_engine.workmanager.model.NSTApplySerialFile;
+import m.co.rh.id.alogger.ILogger;
+import m.co.rh.id.aprovider.Provider;
+
+public class NSTApplyWorkRequest extends Worker {
+ private static final String TAG = "NSTApplyWorkRequest";
+
+ public NSTApplyWorkRequest(@NonNull Context context, @NonNull WorkerParameters workerParams) {
+ super(context, workerParams);
+ }
+
+ @SuppressLint("MissingPermission")
+ @NonNull
+ @Override
+ public Result doWork() {
+ Provider provider = BaseApplication.of(getApplicationContext()).getProvider();
+ ILogger logger = provider.get(ILogger.class);
+ MediaHelper mediaHelper = provider.get(MediaHelper.class);
+ NSTEngine nstEngine = provider.get(NSTEngine.class);
+
+ File inputFile = null;
+ ObjectInputStream ois = null;
+ try {
+ byte[] serialFileB = getInputData().getByteArray(Params.SERIAL_FILE);
+ File serialFile = SerializeUtils.deserialize(serialFileB);
+ ois = new ObjectInputStream(new FileInputStream(serialFile));
+ NSTApplySerialFile nstApplySerialFile = (NSTApplySerialFile) ois.readObject();
+ inputFile = nstApplySerialFile.getInputFile();
+ int theme = nstApplySerialFile.getTheme();
+
+ Bitmap input = BitmapFactory.decodeFile(inputFile.getAbsolutePath());
+ Bitmap applied = nstEngine.apply(input, theme);
+ String fileName = inputFile.getName();
+ if (applied != null) {
+ mediaHelper.insertImage(applied, fileName, fileName);
+ applied.recycle();
+ input.recycle();
+ logger.i(TAG, getApplicationContext()
+ .getString(m.co.rh.id.a_jarwis.base.R.string.done_processing_, fileName));
+ }
+ } catch (Exception e) {
+ logger.e(TAG, e.getMessage(), e);
+ return Result.failure();
+ } finally {
+ if (inputFile != null) {
+ inputFile.delete();
+ }
+ if (ois != null) {
+ try {
+ ois.close();
+ } catch (IOException e) {
+ // Leave blank
+ }
+ }
+ }
+ return Result.success();
+ }
+}
diff --git a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/BlurFaceSerialFile.java b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/model/BlurFaceSerialFile.java
similarity index 92%
rename from ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/BlurFaceSerialFile.java
rename to ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/model/BlurFaceSerialFile.java
index d620af1..3ebfc27 100644
--- a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/BlurFaceSerialFile.java
+++ b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/model/BlurFaceSerialFile.java
@@ -1,4 +1,4 @@
-package m.co.rh.id.a_jarwis.ml_engine.workmanager;
+package m.co.rh.id.a_jarwis.ml_engine.workmanager.model;
import java.io.File;
import java.io.Serializable;
diff --git a/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/model/NSTApplySerialFile.java b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/model/NSTApplySerialFile.java
new file mode 100644
index 0000000..7204d4a
--- /dev/null
+++ b/ml-engine/src/main/java/m/co/rh/id/a_jarwis/ml_engine/workmanager/model/NSTApplySerialFile.java
@@ -0,0 +1,23 @@
+package m.co.rh.id.a_jarwis.ml_engine.workmanager.model;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class NSTApplySerialFile implements Serializable {
+ private File inputFile;
+ private int theme;
+
+ public NSTApplySerialFile(File inputFile, int theme) {
+ this.inputFile = inputFile;
+ this.theme = theme;
+ }
+
+ public File getInputFile() {
+ return inputFile;
+ }
+
+ public int getTheme() {
+ return theme;
+ }
+
+}