diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..564a3ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+.gradle
+.DS_Store
+.idea
+build/
+local.properties
+localhost/
+obj/
+*.iml
+Gemfile.lock
+_site/
\ No newline at end of file
diff --git a/.project b/.project
new file mode 100644
index 0000000..d278faf
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ Android
+ Project Android created by Buildship.
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
index 0000000..e889521
--- /dev/null
+++ b/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=
+eclipse.preferences.version=1
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..66d37dc
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,28 @@
+language: android
+jdk: oraclejdk8
+sudo: false
+
+android:
+
+ components:
+ - tools
+ - platform-tools
+ - build-tools-28.0.3
+ - android-28
+ - android-22
+ - extra-google-google_play_services
+ - extra-google-m2repository
+ - extra-android-m2repository
+ - sys-img-armeabi-v7a-android-22
+
+before_script:
+ - echo yes | android update sdk --all --filter build-tools-28.0.3 --no-ui --force
+ - touch local.properties
+ - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a -c 100M
+ - emulator -avd test -no-audio -no-window &
+ - android-wait-for-emulator
+ - adb shell input keyevent 82 &
+ - chmod +x gradlew
+
+script:
+ - ./gradlew clean build connectedCheck
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..e2fc278
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2018 Gabriel Basilio Brito
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to use,
+copy, modify, merge, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+1 - You cannot use this software to make an Android Backgroung Removal App and publish it to the Google Play Store, but you can use it to integrate the functionality in an existing app.
+
+2 - The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..22311b2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,130 @@
+
+
+
+ ✂️
+ Android-CutOut
+
+ Android image background cutting library
+
+
+[ ![Version](https://api.bintray.com/packages/gabrielbb/Android-CutOut/Android-CutOut/images/download.svg) ](https://bintray.com/gabrielbb/Android-CutOut/Android-CutOut/_latestVersion)
+[ ![Build](https://api.travis-ci.org/GabrielBB/Android-CutOut.svg?branch=master) ](https://api.travis-ci.org/GabrielBB/Android-CutOut.svg?branch=master)
+
+## Usage
+
+Add Gradle dependency:
+```groovy
+implementation 'com.github.gabrielbb:cutout:0.1.2'
+```
+
+Start the CutOut screen with this single line:
+
+```java
+CutOut.activity().start(this);
+```
+
+
+
+### Getting the result
+
+```java
+@Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ if (requestCode == CutOut.CUTOUT_ACTIVITY_REQUEST_CODE) {
+
+ switch (resultCode) {
+ case Activity.RESULT_OK:
+ Uri imageUri = CutOut.getUri(data);
+ // Save the image using the returned Uri here
+ break;
+ case CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE:
+ Exception ex = CutOut.getError(data);
+ break;
+ default:
+ System.out.print("User cancelled the CutOut screen");
+ }
+ }
+ }
+```
+
+## Features
+
+
+
+
+## Options
+
+You can use one or more options from these:
+
+```java
+ CutOut.activity()
+ .src(uri)
+ .bordered()
+ .noCrop()
+ .intro()
+ .start(this);
+```
+
+ - #### src
+
+By default the user can select images from camera or gallery but you can also pass an `android.net.Uri` of an image that is already saved:
+
+ ```java
+Uri uri = Uri.parse("/images/cat.jpg");
+
+CutOut.activity().src(uri).start(this);
+```
+
+
+ - #### bordered
+
+ ```java
+CutOut.activity().bordered().start(this);
+```
+
+This option makes the final PNG have a border around it. The default border color is White. You can also pass the `android.graphics.Color` of your choice.
+
+
+ - #### noCrop
+
+ ```java
+CutOut.activity().noCrop().start(this);
+```
+
+By default and thanks to this library: [Android-Image-Cropper](https://github.com/ArthurHub/Android-Image-Cropper), the user can crop or rotate the image. This option disables that cropping screen.
+
+
+
+ - #### intro
+
+ ```java
+CutOut.activity().intro().start(this);
+```
+
+Display an intro explaining every button usage. The user can skip the intro and it is only shown once. The images displayed in the intro are the same you saw in the "Features" section of this document.
+
+## Change log
+*0.1.2*
+- Removed Admob Ads automatic integration. I will probably add it later. For now, it was causing problems.
+- Images are now saved as temporary files in the cache directory. This guarantees that these images will be deleted when users uninstall your app or when the disk memory is low. If you want the images to live forever on the Gallery, you should take the returned Uri and save the image there by yourself.
+
+## License
+Copyright (c) 2018 Gabriel Basilio Brito
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to use,
+copy, modify, merge, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+1 - You cannot use this software to make an Android app that its main goal is to remove background from images and publish it to the Google Play Store, but you can use it to integrate the functionality in an existing app or a new app that does more than just this.
+
+2 - The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d856e4a
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,41 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.2.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ maven {
+ url 'https://jitpack.io'
+ }
+ maven { url 'https://dl.bintray.com/gabrielbb/Android-CutOut' }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+ext {
+ compileSdkVersion = 28
+ minSdkVersion = 16
+ buildToolsVersion = '28.0.3'
+ versionCode = 2
+ javaVersion = JavaVersion.VERSION_1_8
+ PUBLISH_GROUP_ID = 'com.github.gabrielbb'
+ PUBLISH_ARTIFACT_ID = 'cutout'
+ PUBLISH_VERSION = '0.1.2'
+}
\ No newline at end of file
diff --git a/cutout/.gitignore b/cutout/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/cutout/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/cutout/build.gradle b/cutout/build.gradle
new file mode 100644
index 0000000..fa0c312
--- /dev/null
+++ b/cutout/build.gradle
@@ -0,0 +1,54 @@
+apply plugin: 'com.android.library'
+apply from: 'publish.gradle'
+
+android {
+ compileSdkVersion rootProject.compileSdkVersion
+ defaultConfig {
+ minSdkVersion rootProject.minSdkVersion
+ targetSdkVersion rootProject.compileSdkVersion
+ versionCode rootProject.versionCode
+ versionName rootProject.PUBLISH_VERSION
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility rootProject.javaVersion
+ targetCompatibility rootProject.javaVersion
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.github.duanhong169:checkerboarddrawable:1.0.2'
+ implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0'
+ implementation 'com.alexvasilkov:gesture-views:2.5.2'
+ implementation 'com.github.apl-devs:appintro:v4.2.3'
+ implementation 'com.github.jkwiecien:EasyImage:1.3.1'
+
+ implementation 'com.intuit.sdp:sdp-android:1.0.6'
+}
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
+ classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
+ }
+}
diff --git a/cutout/proguard-rules.pro b/cutout/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/cutout/proguard-rules.pro
@@ -0,0 +1,21 @@
+# 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
diff --git a/cutout/publish.gradle b/cutout/publish.gradle
new file mode 100644
index 0000000..eaa821e
--- /dev/null
+++ b/cutout/publish.gradle
@@ -0,0 +1,106 @@
+apply plugin: 'com.github.dcendents.android-maven'
+apply plugin: 'com.jfrog.bintray'
+
+android {
+ compileSdkVersion rootProject.compileSdkVersion
+}
+
+
+ext {
+ libraryName = 'Android-CutOut'
+ bintrayRepo = libraryName
+ bintrayName = bintrayRepo
+ libraryDescription = 'Android image background removing library'
+
+ gitUrl = 'https://github.com/GabrielBB/Android-CutOut.git'
+ siteUrl = gitUrl
+
+ developerId = 'gabrielbb'
+ developerName = 'Gabriel Basilio Brito'
+ developerEmail = 'gabrielbb0306@gmail.com'
+
+ licenseName = 'The Apache Software License, Version 2.0'
+ licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ allLicenses = ["Apache-2.0"]
+}
+
+group = rootProject.PUBLISH_GROUP_ID
+version = rootProject.PUBLISH_VERSION
+
+install {
+ repositories.mavenInstaller {
+ pom.project {
+ packaging 'aar'
+ groupId rootProject.PUBLISH_GROUP_ID
+ artifactId rootProject.PUBLISH_ARTIFACT_ID
+
+ name libraryName
+ description libraryDescription
+ url siteUrl
+
+ licenses {
+ license {
+ name licenseName
+ url licenseUrl
+ }
+ }
+ developers {
+ developer {
+ id developerId
+ name developerName
+ email developerEmail
+ }
+ }
+ scm {
+ connection gitUrl
+ developerConnection gitUrl
+ url siteUrl
+ }
+ }
+ }
+}
+
+task sourcesJar(type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+}
+
+task javadoc(type: Javadoc) {
+ source = android.sourceSets.main.java.srcDirs
+ classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+ classifier = 'javadoc'
+ from javadoc.destinationDir
+}
+
+artifacts {
+ archives javadocJar
+ archives sourcesJar
+}
+
+Properties properties = new Properties()
+properties.load(project.rootProject.file('local.properties').newDataInputStream())
+
+bintray {
+ user = properties.getProperty("bintray.user")
+ key = properties.getProperty("bintray.apikey")
+
+ configurations = ['archives']
+ pkg {
+ repo = bintrayRepo
+ name = bintrayName
+ desc = libraryDescription
+ websiteUrl = siteUrl
+ vcsUrl = gitUrl
+ licenses = allLicenses
+ dryRun = false
+ publish = true
+ override = false
+ publicDownloadNumbers = true
+ version {
+ desc = libraryDescription
+ }
+ }
+}
\ No newline at end of file
diff --git a/cutout/src/main/AndroidManifest.xml b/cutout/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..03a3126
--- /dev/null
+++ b/cutout/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java b/cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java
new file mode 100644
index 0000000..3aeacb6
--- /dev/null
+++ b/cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java
@@ -0,0 +1,61 @@
+package com.github.gabrielbb.cutout;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+
+class BitmapUtility {
+
+ static Bitmap getResizedBitmap(Bitmap bitmap, int width, int height) {
+ Bitmap background = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+
+ float originalWidth = bitmap.getWidth();
+ float originalHeight = bitmap.getHeight();
+
+ Canvas canvas = new Canvas(background);
+
+ float scale = width / originalWidth;
+
+ float xTranslation = 0.0f;
+ float yTranslation = (height - originalHeight * scale) / 2.0f;
+
+ Matrix transformation = new Matrix();
+ transformation.postTranslate(xTranslation, yTranslation);
+ transformation.preScale(scale, scale);
+
+ Paint paint = new Paint();
+ paint.setFilterBitmap(true);
+
+ canvas.drawBitmap(bitmap, transformation, paint);
+
+ return background;
+ }
+
+ static Bitmap getBorderedBitmap(Bitmap image, int borderColor, int borderSize) {
+
+ // Creating a canvas with an empty bitmap, this is the bitmap that gonna store the final canvas changes
+ Bitmap finalImage = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(finalImage);
+
+ // Make a smaller copy of the image to draw on top of original
+ Bitmap imageCopy = Bitmap.createScaledBitmap(image, image.getWidth() - borderSize, image.getHeight() - borderSize, true);
+
+ // Let's draw the bigger image using a white paint brush
+ Paint paint = new Paint();
+ paint.setColorFilter(new PorterDuffColorFilter(borderColor, PorterDuff.Mode.SRC_ATOP));
+ canvas.drawBitmap(image, 0, 0, paint);
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+ float centerX = (width - imageCopy.getWidth()) * 0.5f;
+ float centerY = (height - imageCopy.getHeight()) * 0.5f;
+ // Now let's draw the original image on top of the white image, passing a null paint because we want to keep it original
+ canvas.drawBitmap(imageCopy, centerX, centerY, null);
+
+ // Returning the image with the final results
+ return finalImage;
+ }
+}
diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java
new file mode 100644
index 0000000..7c169e4
--- /dev/null
+++ b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java
@@ -0,0 +1,139 @@
+package com.github.gabrielbb.cutout;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public class CutOut {
+
+ public static final short CUTOUT_ACTIVITY_REQUEST_CODE = 368;
+ public static final short CUTOUT_ACTIVITY_RESULT_ERROR_CODE = 3680;
+
+ static final String CUTOUT_EXTRA_SOURCE = "CUTOUT_EXTRA_SOURCE";
+ static final String CUTOUT_EXTRA_RESULT = "CUTOUT_EXTRA_RESULT";
+ static final String CUTOUT_EXTRA_BORDER_COLOR = "CUTOUT_EXTRA_BORDER_COLOR";
+ static final String CUTOUT_EXTRA_CROP = "CUTOUT_EXTRA_CROP";
+ static final String CUTOUT_EXTRA_INTRO = "CUTOUT_EXTRA_INTRO";
+
+ public static ActivityBuilder activity() {
+ return new ActivityBuilder();
+ }
+
+ /**
+ * Builder used for creating CutOut Activity by user request.
+ */
+ public static final class ActivityBuilder {
+
+ @Nullable
+ private Uri source; // The image to crop source Android uri
+ private boolean bordered;
+ private boolean crop = true; // By default the cropping activity is started
+ private boolean intro;
+ private int borderColor = Color.WHITE; // Default border color is no border color is passed
+
+ private ActivityBuilder() {
+
+ }
+
+ /**
+ * Get {@link CutOutActivity} intent to start the activity.
+ */
+ private Intent getIntent(@NonNull Context context) {
+ Intent intent = new Intent();
+ intent.setClass(context, CutOutActivity.class);
+
+ if (source != null) {
+ intent.putExtra(CUTOUT_EXTRA_SOURCE, source);
+ }
+
+ if (bordered) {
+ intent.putExtra(CUTOUT_EXTRA_BORDER_COLOR, borderColor);
+ }
+
+ if (crop) {
+ intent.putExtra(CUTOUT_EXTRA_CROP, true);
+ }
+
+ if (intro) {
+ intent.putExtra(CUTOUT_EXTRA_INTRO, true);
+ }
+
+ return intent;
+ }
+
+ /**
+ * By default the user can select images from camera or gallery but you can also call this method to load a pre-saved image
+ *
+ * @param source {@link android.net.Uri} instance of the image to be loaded
+ */
+ public ActivityBuilder src(Uri source) {
+ this.source = source;
+ return this;
+ }
+
+ /**
+ * This method adds a white border around the final PNG image
+ */
+ public ActivityBuilder bordered() {
+ this.bordered = true;
+ return this;
+ }
+
+ /**
+ * This method adds a border around the final PNG image
+ *
+ * @param borderColor The border color. You can pass any {@link android.graphics.Color}
+ */
+ public ActivityBuilder bordered(int borderColor) {
+ this.borderColor = borderColor;
+ return bordered();
+ }
+
+ /**
+ * Disables the cropping screen shown before the background removal screen
+ */
+ public ActivityBuilder noCrop() {
+ this.crop = false;
+ return this;
+ }
+
+ /**
+ * Shows an introduction to the activity, explaining every button usage. The intro is show only once.
+ */
+ public ActivityBuilder intro() {
+ this.intro = true;
+ return this;
+ }
+
+ /**
+ * Start {@link CutOutActivity}.
+ *
+ * @param activity activity to receive result
+ */
+ public void start(@NonNull Activity activity) {
+ activity.startActivityForResult(getIntent(activity), CUTOUT_ACTIVITY_REQUEST_CODE);
+ }
+ }
+
+ /**
+ * Reads the {@link android.net.Uri} from the result data. This Uri is the path to the saved PNG
+ *
+ * @param data Result data to get the Uri from
+ */
+ public static Uri getUri(@Nullable Intent data) {
+ return data != null ? data.getParcelableExtra(CUTOUT_EXTRA_RESULT) : null;
+ }
+
+ /**
+ * Gets an Exception from the result data if the {@link CutOutActivity} failed at some point
+ *
+ * @param data Result data to get the Exception from
+ */
+ public static Exception getError(@Nullable Intent data) {
+ return data != null ? (Exception) data.getSerializableExtra(CUTOUT_EXTRA_RESULT) : null;
+ }
+}
diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java
new file mode 100644
index 0000000..c09aa84
--- /dev/null
+++ b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java
@@ -0,0 +1,379 @@
+package com.github.gabrielbb.cutout;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import com.alexvasilkov.gestures.views.interfaces.GestureView;
+import com.theartofdev.edmodo.cropper.CropImage;
+import com.theartofdev.edmodo.cropper.CropImageView;
+
+import java.io.File;
+import java.io.IOException;
+
+import pl.aprilapps.easyphotopicker.DefaultCallback;
+import pl.aprilapps.easyphotopicker.EasyImage;
+import top.defaults.checkerboarddrawable.CheckerboardDrawable;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static com.github.gabrielbb.cutout.CutOut.CUTOUT_EXTRA_INTRO;
+
+public class CutOutActivity extends AppCompatActivity {
+
+ private static final int INTRO_REQUEST_CODE = 4;
+ private static final int WRITE_EXTERNAL_STORAGE_CODE = 1;
+ private static final int IMAGE_CHOOSER_REQUEST_CODE = 2;
+ private static final int CAMERA_REQUEST_CODE = 3;
+
+ private static final String INTRO_SHOWN = "INTRO_SHOWN";
+ FrameLayout loadingModal;
+ private GestureView gestureView;
+ private DrawView drawView;
+ private LinearLayout manualClearSettingsLayout;
+
+ private static final short MAX_ERASER_SIZE = 150;
+ private static final short BORDER_SIZE = 45;
+ private static final float MAX_ZOOM = 4F;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_photo_edit);
+
+ Toolbar toolbar = findViewById(R.id.photo_edit_toolbar);
+ //toolbar.setBackgroundColor(Color.BLACK);
+ //toolbar.setTitleTextColor(Color.WHITE);
+ setSupportActionBar(toolbar);
+
+ FrameLayout drawViewLayout = findViewById(R.id.drawViewLayout);
+
+ int sdk = android.os.Build.VERSION.SDK_INT;
+
+ if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ drawViewLayout.setBackgroundDrawable(CheckerboardDrawable.create());
+ } else {
+ drawViewLayout.setBackground(CheckerboardDrawable.create());
+ }
+
+ SeekBar strokeBar = findViewById(R.id.strokeBar);
+ strokeBar.setMax(MAX_ERASER_SIZE);
+ strokeBar.setProgress(50);
+
+ gestureView = findViewById(R.id.gestureView);
+
+ drawView = findViewById(R.id.drawView);
+ drawView.setDrawingCacheEnabled(true);
+ drawView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ //drawView.setDrawingCacheEnabled(true);
+ drawView.setStrokeWidth(strokeBar.getProgress());
+
+ strokeBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ drawView.setStrokeWidth(seekBar.getProgress());
+ }
+ });
+
+ loadingModal = findViewById(R.id.loadingModal);
+ loadingModal.setVisibility(INVISIBLE);
+
+ drawView.setLoadingModal(loadingModal);
+
+ manualClearSettingsLayout = findViewById(R.id.manual_clear_settings_layout);
+
+ setUndoRedo();
+ initializeActionButtons();
+
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+
+ if (toolbar.getNavigationIcon() != null) {
+ toolbar.getNavigationIcon().setColorFilter(getResources().getColor(R.color.white), PorterDuff.Mode.SRC_ATOP);
+ }
+
+ }
+
+ Button doneButton = findViewById(R.id.done);
+
+ doneButton.setOnClickListener(v -> startSaveDrawingTask());
+
+ if (getIntent().getBooleanExtra(CUTOUT_EXTRA_INTRO, false) && !getPreferences(Context.MODE_PRIVATE).getBoolean(INTRO_SHOWN, false)) {
+ Intent intent = new Intent(this, IntroActivity.class);
+ startActivityForResult(intent, INTRO_REQUEST_CODE);
+ } else {
+ start();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ // Respond to the action bar's Up/Home button
+ case android.R.id.home:
+ setResult(RESULT_CANCELED);
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private Uri getExtraSource() {
+ return getIntent().hasExtra(CutOut.CUTOUT_EXTRA_SOURCE) ? (Uri) getIntent().getParcelableExtra(CutOut.CUTOUT_EXTRA_SOURCE) : null;
+ }
+
+ private void start() {
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+
+ Uri uri = getExtraSource();
+
+ if (getIntent().getBooleanExtra(CutOut.CUTOUT_EXTRA_CROP, false)) {
+
+ CropImage.ActivityBuilder cropImageBuilder;
+ if (uri != null) {
+ cropImageBuilder = CropImage.activity(uri);
+ } else {
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
+
+ cropImageBuilder = CropImage.activity();
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA},
+ CAMERA_REQUEST_CODE);
+ return;
+ }
+ }
+
+ cropImageBuilder = cropImageBuilder.setGuidelines(CropImageView.Guidelines.ON);
+ cropImageBuilder.start(this);
+ } else {
+ if (uri != null) {
+ setDrawViewBitmap(uri);
+ } else {
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
+
+ EasyImage.openChooserWithGallery(this, getString(R.string.image_chooser_message), IMAGE_CHOOSER_REQUEST_CODE);
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA},
+ CAMERA_REQUEST_CODE);
+ }
+ }
+ }
+
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ WRITE_EXTERNAL_STORAGE_CODE);
+ }
+ }
+
+ private void startSaveDrawingTask() {
+ SaveDrawingTask task = new SaveDrawingTask(this);
+
+ int borderColor;
+ if ((borderColor = getIntent().getIntExtra(CutOut.CUTOUT_EXTRA_BORDER_COLOR, -1)) != -1) {
+ Bitmap image = BitmapUtility.getBorderedBitmap(this.drawView.getDrawingCache(), borderColor, BORDER_SIZE);
+ task.execute(image);
+ } else {
+ task.execute(this.drawView.getDrawingCache());
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ @NonNull String permissions[], @NonNull int[] grantResults) {
+ if (grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ start();
+ } else {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ }
+
+ private void activateGestureView() {
+ gestureView.getController().getSettings()
+ .setMaxZoom(MAX_ZOOM)
+ .setDoubleTapZoom(-1f) // Falls back to max zoom level
+ .setPanEnabled(true)
+ .setZoomEnabled(true)
+ .setDoubleTapEnabled(true)
+ .setOverscrollDistance(0f, 0f)
+ .setOverzoomFactor(2f);
+ }
+
+ private void deactivateGestureView() {
+ gestureView.getController().getSettings()
+ .setPanEnabled(false)
+ .setZoomEnabled(false)
+ .setDoubleTapEnabled(false);
+ }
+
+ private void initializeActionButtons() {
+ Button autoClearButton = findViewById(R.id.auto_clear_button);
+ Button manualClearButton = findViewById(R.id.manual_clear_button);
+ Button zoomButton = findViewById(R.id.zoom_button);
+
+ autoClearButton.setActivated(false);
+ autoClearButton.setOnClickListener((buttonView) -> {
+ if (!autoClearButton.isActivated()) {
+ drawView.setAction(DrawView.DrawViewAction.AUTO_CLEAR);
+ manualClearSettingsLayout.setVisibility(INVISIBLE);
+ autoClearButton.setActivated(true);
+ manualClearButton.setActivated(false);
+ zoomButton.setActivated(false);
+ deactivateGestureView();
+ }
+ });
+
+ manualClearButton.setActivated(true);
+ drawView.setAction(DrawView.DrawViewAction.MANUAL_CLEAR);
+ manualClearButton.setOnClickListener((buttonView) -> {
+ if (!manualClearButton.isActivated()) {
+ drawView.setAction(DrawView.DrawViewAction.MANUAL_CLEAR);
+ manualClearSettingsLayout.setVisibility(VISIBLE);
+ manualClearButton.setActivated(true);
+ autoClearButton.setActivated(false);
+ zoomButton.setActivated(false);
+ deactivateGestureView();
+ }
+
+ });
+
+ zoomButton.setActivated(false);
+ deactivateGestureView();
+ zoomButton.setOnClickListener((buttonView) -> {
+ if (!zoomButton.isActivated()) {
+ drawView.setAction(DrawView.DrawViewAction.ZOOM);
+ manualClearSettingsLayout.setVisibility(INVISIBLE);
+ zoomButton.setActivated(true);
+ manualClearButton.setActivated(false);
+ autoClearButton.setActivated(false);
+ activateGestureView();
+ }
+
+ });
+ }
+
+ private void setUndoRedo() {
+ Button undoButton = findViewById(R.id.undo);
+ undoButton.setEnabled(false);
+ undoButton.setOnClickListener(v -> undo());
+ Button redoButton = findViewById(R.id.redo);
+ redoButton.setEnabled(false);
+ redoButton.setOnClickListener(v -> redo());
+
+ drawView.setButtons(undoButton, redoButton);
+ }
+
+ void exitWithError(Exception e) {
+ Intent intent = new Intent();
+ intent.putExtra(CutOut.CUTOUT_EXTRA_RESULT, e);
+ setResult(CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE, intent);
+ finish();
+ }
+
+ private void setDrawViewBitmap(Uri uri) {
+ try {
+ Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
+ drawView.setBitmap(bitmap);
+ } catch (IOException e) {
+ exitWithError(e);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+ if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
+
+ CropImage.ActivityResult result = CropImage.getActivityResult(data);
+
+ if (resultCode == Activity.RESULT_OK) {
+
+ setDrawViewBitmap(result.getUri());
+
+ } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
+ exitWithError(result.getError());
+ } else {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ } else if (requestCode == INTRO_REQUEST_CODE) {
+ SharedPreferences.Editor editor = getPreferences(Context.MODE_PRIVATE).edit();
+ editor.putBoolean(INTRO_SHOWN, true);
+ editor.apply();
+ start();
+ } else {
+ EasyImage.handleActivityResult(requestCode, resultCode, data, this, new DefaultCallback() {
+ @Override
+ public void onImagePickerError(Exception e, EasyImage.ImageSource source, int type) {
+ exitWithError(e);
+ }
+
+ @Override
+ public void onImagePicked(File imageFile, EasyImage.ImageSource source, int type) {
+ setDrawViewBitmap(Uri.parse(imageFile.toURI().toString()));
+ }
+
+ @Override
+ public void onCanceled(EasyImage.ImageSource source, int type) {
+ // Cancel handling, removing taken photo if it was canceled
+ if (source == EasyImage.ImageSource.CAMERA) {
+ File photoFile = EasyImage.lastlyTakenButCanceledPhoto(CutOutActivity.this);
+ if (photoFile != null) photoFile.delete();
+ }
+
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ }
+ }
+
+ private void undo() {
+ drawView.undo();
+ }
+
+ private void redo() {
+ drawView.redo();
+ }
+
+}
\ No newline at end of file
diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java b/cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java
new file mode 100644
index 0000000..cf78880
--- /dev/null
+++ b/cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java
@@ -0,0 +1,304 @@
+package com.github.gabrielbb.cutout;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+
+import java.lang.ref.WeakReference;
+import java.util.Stack;
+
+import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.AUTO_CLEAR;
+import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.MANUAL_CLEAR;
+import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.ZOOM;
+
+class DrawView extends View {
+
+ private Path livePath;
+ private Paint pathPaint;
+
+ private Bitmap imageBitmap;
+ private final Stack, Bitmap>> cuts = new Stack<>();
+ private final Stack, Bitmap>> undoneCuts = new Stack<>();
+
+ private float pathX, pathY;
+
+ private static final float TOUCH_TOLERANCE = 4;
+ private static final float COLOR_TOLERANCE = 20;
+
+ private Button undoButton;
+ private Button redoButton;
+
+ private View loadingModal;
+
+ private DrawViewAction currentAction;
+
+ public enum DrawViewAction {
+ AUTO_CLEAR,
+ MANUAL_CLEAR,
+ ZOOM
+ }
+
+ public DrawView(Context c, AttributeSet attrs) {
+
+ super(c, attrs);
+
+ livePath = new Path();
+
+ pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ pathPaint.setDither(true);
+ pathPaint.setColor(Color.TRANSPARENT);
+ pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ pathPaint.setStyle(Paint.Style.STROKE);
+ pathPaint.setStrokeJoin(Paint.Join.ROUND);
+ pathPaint.setStrokeCap(Paint.Cap.ROUND);
+ }
+
+ public void setButtons(Button undoButton, Button redoButton) {
+ this.undoButton = undoButton;
+ this.redoButton = redoButton;
+ }
+
+
+ @Override
+ protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+ super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+
+ resizeBitmap(newWidth, newHeight);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.save();
+
+ if (imageBitmap != null) {
+
+ canvas.drawBitmap(this.imageBitmap, 0, 0, null);
+
+ for (Pair, Bitmap> action : cuts) {
+ if (action.first != null) {
+ canvas.drawPath(action.first.first, action.first.second);
+ }
+ }
+
+ if (currentAction == MANUAL_CLEAR) {
+ canvas.drawPath(livePath, pathPaint);
+ }
+ }
+
+ canvas.restore();
+ }
+
+ private void touchStart(float x, float y) {
+ pathX = x;
+ pathY = y;
+
+ undoneCuts.clear();
+ redoButton.setEnabled(false);
+
+ if (currentAction == AUTO_CLEAR) {
+ new AutomaticPixelClearingTask(this).execute((int) x, (int) y);
+ } else {
+ livePath.moveTo(x, y);
+ }
+
+ invalidate();
+ }
+
+ private void touchMove(float x, float y) {
+ if (currentAction == MANUAL_CLEAR) {
+ float dx = Math.abs(x - pathX);
+ float dy = Math.abs(y - pathY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
+ livePath.quadTo(pathX, pathY, (x + pathX) / 2, (y + pathY) / 2);
+ pathX = x;
+ pathY = y;
+ }
+ }
+ }
+
+
+ private void touchUp() {
+ if (currentAction == MANUAL_CLEAR) {
+ livePath.lineTo(pathX, pathY);
+ cuts.push(new Pair<>(new Pair<>(livePath, pathPaint), null));
+ livePath = new Path();
+ undoButton.setEnabled(true);
+ }
+ }
+
+ public void undo() {
+ if (cuts.size() > 0) {
+
+ Pair, Bitmap> cut = cuts.pop();
+
+ if (cut.second != null) {
+ undoneCuts.push(new Pair<>(null, imageBitmap));
+ this.imageBitmap = cut.second;
+ } else {
+ undoneCuts.push(cut);
+ }
+
+ if (cuts.isEmpty()) {
+ undoButton.setEnabled(false);
+ }
+
+ redoButton.setEnabled(true);
+
+ invalidate();
+ }
+ //toast the user
+ }
+
+ public void redo() {
+ if (undoneCuts.size() > 0) {
+
+ Pair, Bitmap> cut = undoneCuts.pop();
+
+ if (cut.second != null) {
+ cuts.push(new Pair<>(null, imageBitmap));
+ this.imageBitmap = cut.second;
+ } else {
+ cuts.push(cut);
+ }
+
+ if (undoneCuts.isEmpty()) {
+ redoButton.setEnabled(false);
+ }
+
+ undoButton.setEnabled(true);
+
+ invalidate();
+ }
+ //toast the user
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+
+ if (imageBitmap != null && currentAction != ZOOM) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ touchStart(ev.getX(), ev.getY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ touchMove(ev.getX(), ev.getY());
+ invalidate();
+ return true;
+ case MotionEvent.ACTION_UP:
+ touchUp();
+ invalidate();
+ return true;
+ }
+ }
+
+ return super.onTouchEvent(ev);
+ }
+
+ private void resizeBitmap(int width, int height) {
+ if (width > 0 && height > 0 && imageBitmap != null) {
+ imageBitmap = BitmapUtility.getResizedBitmap(this.imageBitmap, width, height);
+ imageBitmap.setHasAlpha(true);
+ invalidate();
+ }
+ }
+
+ public void setBitmap(Bitmap bitmap) {
+ this.imageBitmap = bitmap;
+ resizeBitmap(getWidth(), getHeight());
+ }
+
+ public Bitmap getCurrentBitmap() {
+ return this.imageBitmap;
+ }
+
+ public void setAction(DrawViewAction newAction) {
+ this.currentAction = newAction;
+ }
+
+ public void setStrokeWidth(int strokeWidth) {
+ pathPaint = new Paint(pathPaint);
+ pathPaint.setStrokeWidth(strokeWidth);
+ }
+
+ public void setLoadingModal(View loadingModal) {
+ this.loadingModal = loadingModal;
+ }
+
+ private static class AutomaticPixelClearingTask extends AsyncTask {
+
+ private WeakReference drawViewWeakReference;
+
+ AutomaticPixelClearingTask(DrawView drawView) {
+ this.drawViewWeakReference = new WeakReference<>(drawView);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ drawViewWeakReference.get().loadingModal.setVisibility(VISIBLE);
+ drawViewWeakReference.get().cuts.push(new Pair<>(null, drawViewWeakReference.get().imageBitmap));
+ }
+
+ @Override
+ protected Bitmap doInBackground(Integer... points) {
+ Bitmap oldBitmap = drawViewWeakReference.get().imageBitmap;
+
+ int colorToReplace = oldBitmap.getPixel(points[0], points[1]);
+
+ int width = oldBitmap.getWidth();
+ int height = oldBitmap.getHeight();
+ int[] pixels = new int[width * height];
+ oldBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+
+ int rA = Color.alpha(colorToReplace);
+ int rR = Color.red(colorToReplace);
+ int rG = Color.green(colorToReplace);
+ int rB = Color.blue(colorToReplace);
+
+ int pixel;
+
+ // iteration through pixels
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ // get current index in 2D-matrix
+ int index = y * width + x;
+ pixel = pixels[index];
+ int rrA = Color.alpha(pixel);
+ int rrR = Color.red(pixel);
+ int rrG = Color.green(pixel);
+ int rrB = Color.blue(pixel);
+
+ if (rA - COLOR_TOLERANCE < rrA && rrA < rA + COLOR_TOLERANCE && rR - COLOR_TOLERANCE < rrR && rrR < rR + COLOR_TOLERANCE &&
+ rG - COLOR_TOLERANCE < rrG && rrG < rG + COLOR_TOLERANCE && rB - COLOR_TOLERANCE < rrB && rrB < rB + COLOR_TOLERANCE) {
+ pixels[index] = Color.TRANSPARENT;
+ }
+ }
+ }
+
+ Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ newBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+
+ return newBitmap;
+ }
+
+ protected void onPostExecute(Bitmap result) {
+ super.onPostExecute(result);
+ drawViewWeakReference.get().imageBitmap = result;
+ drawViewWeakReference.get().undoButton.setEnabled(true);
+ drawViewWeakReference.get().loadingModal.setVisibility(INVISIBLE);
+ drawViewWeakReference.get().invalidate();
+ }
+ }
+}
\ No newline at end of file
diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java b/cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java
new file mode 100644
index 0000000..f5795a1
--- /dev/null
+++ b/cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java
@@ -0,0 +1,59 @@
+package com.github.gabrielbb.cutout;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+
+import com.github.paolorotolo.appintro.AppIntro;
+import com.github.paolorotolo.appintro.AppIntroFragment;
+
+public class IntroActivity extends AppIntro {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Note here that we DO NOT use setContentView();
+
+ // Add your slide fragments here.
+ // AppIntro will automatically generate the dots indicator and buttons.
+ addSlide(AppIntroFragment.newInstance(getString(R.string.intro_magic_title), getString(R.string.intro_magic_description), R.drawable.intro_magic_wand, getResources().getColor(R.color.intro_magic_background_color)));
+ addSlide(AppIntroFragment.newInstance(getString(R.string.intro_manual_title), getString(R.string.intro_manual_description), R.drawable.intro_pencil, getResources().getColor(R.color.intro_manual_background_color)));
+ addSlide(AppIntroFragment.newInstance(getString(R.string.intro_zoom_title), getString(R.string.intro_zoom_description), R.drawable.intro_magnifier, getResources().getColor(R.color.intro_zoom_background_color)));
+
+ // OPTIONAL METHODS
+ // Override bar/separator color.
+ setBarColor(Color.parseColor("#3F51B5"));
+ setSeparatorColor(Color.parseColor("#2196F3"));
+
+ // Hide Skip/Done button.
+ showSkipButton(false);
+ setProgressButtonEnabled(true);
+
+ // Turn vibration on and set intensity.
+ // NOTE: you will probably need to ask VIBRATE permission in Manifest.
+ setVibrate(false);
+
+ setFadeAnimation();
+ }
+
+ @Override
+ public void onSkipPressed(Fragment currentFragment) {
+ super.onSkipPressed(currentFragment);
+ // Do something when users tap on Skip button.
+ finish();
+ }
+
+ @Override
+ public void onDonePressed(Fragment currentFragment) {
+ super.onDonePressed(currentFragment);
+ // Do something when users tap on Done button.
+ finish();
+ }
+
+ @Override
+ public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) {
+ super.onSlideChanged(oldFragment, newFragment);
+ // Do something when the slide changes.
+ }
+}
\ No newline at end of file
diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java b/cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java
new file mode 100644
index 0000000..6907baa
--- /dev/null
+++ b/cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java
@@ -0,0 +1,67 @@
+package com.github.gabrielbb.cutout;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.util.Pair;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+
+import static android.view.View.VISIBLE;
+
+class SaveDrawingTask extends AsyncTask> {
+
+ private static final String SAVED_IMAGE_FORMAT = "png";
+ private static final String SAVED_IMAGE_NAME = "cutout_tmp";
+
+ private final WeakReference activityWeakReference;
+
+ SaveDrawingTask(CutOutActivity activity) {
+ this.activityWeakReference = new WeakReference<>(activity);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ activityWeakReference.get().loadingModal.setVisibility(VISIBLE);
+ }
+
+ @Override
+ protected Pair doInBackground(Bitmap... bitmaps) {
+
+ try {
+ File file = File.createTempFile(SAVED_IMAGE_NAME, SAVED_IMAGE_FORMAT, activityWeakReference.get().getApplicationContext().getCacheDir());
+
+ try (FileOutputStream out = new FileOutputStream(file)) {
+ bitmaps[0].compress(Bitmap.CompressFormat.PNG, 95, out);
+ return new Pair<>(file, null);
+ }
+ } catch (IOException e) {
+ return new Pair<>(null, e);
+ }
+ }
+
+ protected void onPostExecute(Pair result) {
+ super.onPostExecute(result);
+
+ Intent resultIntent = new Intent();
+
+ if (result.first != null) {
+ Uri uri = Uri.fromFile(result.first);
+
+ resultIntent.putExtra(CutOut.CUTOUT_EXTRA_RESULT, uri);
+ activityWeakReference.get().setResult(Activity.RESULT_OK, resultIntent);
+ activityWeakReference.get().finish();
+
+ } else {
+ activityWeakReference.get().exitWithError(result.second);
+ }
+ }
+}
\ No newline at end of file
diff --git a/cutout/src/main/res/drawable/done.png b/cutout/src/main/res/drawable/done.png
new file mode 100644
index 0000000..c11e3a5
Binary files /dev/null and b/cutout/src/main/res/drawable/done.png differ
diff --git a/cutout/src/main/res/drawable/intro_magic_wand.png b/cutout/src/main/res/drawable/intro_magic_wand.png
new file mode 100644
index 0000000..5f2097a
Binary files /dev/null and b/cutout/src/main/res/drawable/intro_magic_wand.png differ
diff --git a/cutout/src/main/res/drawable/intro_magnifier.png b/cutout/src/main/res/drawable/intro_magnifier.png
new file mode 100644
index 0000000..ccd9c13
Binary files /dev/null and b/cutout/src/main/res/drawable/intro_magnifier.png differ
diff --git a/cutout/src/main/res/drawable/intro_pencil.png b/cutout/src/main/res/drawable/intro_pencil.png
new file mode 100644
index 0000000..897e668
Binary files /dev/null and b/cutout/src/main/res/drawable/intro_pencil.png differ
diff --git a/cutout/src/main/res/drawable/magic_active.png b/cutout/src/main/res/drawable/magic_active.png
new file mode 100644
index 0000000..1b32b72
Binary files /dev/null and b/cutout/src/main/res/drawable/magic_active.png differ
diff --git a/cutout/src/main/res/drawable/magic_inactive.png b/cutout/src/main/res/drawable/magic_inactive.png
new file mode 100644
index 0000000..7507961
Binary files /dev/null and b/cutout/src/main/res/drawable/magic_inactive.png differ
diff --git a/cutout/src/main/res/drawable/magic_selector.xml b/cutout/src/main/res/drawable/magic_selector.xml
new file mode 100644
index 0000000..971e646
--- /dev/null
+++ b/cutout/src/main/res/drawable/magic_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/cutout/src/main/res/drawable/magnifier_active.png b/cutout/src/main/res/drawable/magnifier_active.png
new file mode 100644
index 0000000..136d325
Binary files /dev/null and b/cutout/src/main/res/drawable/magnifier_active.png differ
diff --git a/cutout/src/main/res/drawable/magnifier_inactive.png b/cutout/src/main/res/drawable/magnifier_inactive.png
new file mode 100644
index 0000000..db45b90
Binary files /dev/null and b/cutout/src/main/res/drawable/magnifier_inactive.png differ
diff --git a/cutout/src/main/res/drawable/magnifier_selector.xml b/cutout/src/main/res/drawable/magnifier_selector.xml
new file mode 100644
index 0000000..bac8d58
--- /dev/null
+++ b/cutout/src/main/res/drawable/magnifier_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/cutout/src/main/res/drawable/pencil_active.png b/cutout/src/main/res/drawable/pencil_active.png
new file mode 100644
index 0000000..328db1b
Binary files /dev/null and b/cutout/src/main/res/drawable/pencil_active.png differ
diff --git a/cutout/src/main/res/drawable/pencil_inactive.png b/cutout/src/main/res/drawable/pencil_inactive.png
new file mode 100644
index 0000000..6c33a47
Binary files /dev/null and b/cutout/src/main/res/drawable/pencil_inactive.png differ
diff --git a/cutout/src/main/res/drawable/pencil_selector.xml b/cutout/src/main/res/drawable/pencil_selector.xml
new file mode 100644
index 0000000..ce8595b
--- /dev/null
+++ b/cutout/src/main/res/drawable/pencil_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/cutout/src/main/res/drawable/redo_active.png b/cutout/src/main/res/drawable/redo_active.png
new file mode 100644
index 0000000..9b06719
Binary files /dev/null and b/cutout/src/main/res/drawable/redo_active.png differ
diff --git a/cutout/src/main/res/drawable/redo_inactive.png b/cutout/src/main/res/drawable/redo_inactive.png
new file mode 100644
index 0000000..83341d9
Binary files /dev/null and b/cutout/src/main/res/drawable/redo_inactive.png differ
diff --git a/cutout/src/main/res/drawable/redo_selector.xml b/cutout/src/main/res/drawable/redo_selector.xml
new file mode 100644
index 0000000..f0c9047
--- /dev/null
+++ b/cutout/src/main/res/drawable/redo_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/cutout/src/main/res/drawable/undo_active.png b/cutout/src/main/res/drawable/undo_active.png
new file mode 100644
index 0000000..6ebbe54
Binary files /dev/null and b/cutout/src/main/res/drawable/undo_active.png differ
diff --git a/cutout/src/main/res/drawable/undo_inactive.png b/cutout/src/main/res/drawable/undo_inactive.png
new file mode 100644
index 0000000..ec333fa
Binary files /dev/null and b/cutout/src/main/res/drawable/undo_inactive.png differ
diff --git a/cutout/src/main/res/drawable/undo_selector.xml b/cutout/src/main/res/drawable/undo_selector.xml
new file mode 100644
index 0000000..6c31d0c
--- /dev/null
+++ b/cutout/src/main/res/drawable/undo_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/cutout/src/main/res/layout/activity_photo_edit.xml b/cutout/src/main/res/layout/activity_photo_edit.xml
new file mode 100644
index 0000000..5d7ee84
--- /dev/null
+++ b/cutout/src/main/res/layout/activity_photo_edit.xml
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cutout/src/main/res/values/colors.xml b/cutout/src/main/res/values/colors.xml
new file mode 100644
index 0000000..ccd42e5
--- /dev/null
+++ b/cutout/src/main/res/values/colors.xml
@@ -0,0 +1,18 @@
+
+
+ @android:color/transparent
+ #BABABA
+ #56b95a
+ #B3B3B3
+ #EBEBEB
+ @color/colorAccent
+ #3a3a3a
+ #b7000000
+ #FFFFFF
+ #b7000000
+ #ff303e
+ #fcfcfc
+ #373737
+ #E64E4E
+ #4E5DE6
+
diff --git a/cutout/src/main/res/values/strings.xml b/cutout/src/main/res/values/strings.xml
new file mode 100644
index 0000000..468c0c4
--- /dev/null
+++ b/cutout/src/main/res/values/strings.xml
@@ -0,0 +1,11 @@
+
+ CutOut
+ Automatic background clearing
+ Manual background clearing
+ Zooming and Moving the picture
+ Use the Magic Wand tool and touch parts of the background you want to erase
+ Use the Pencil tool to erase the background dragging your finger around the screen
+ Use the Magnifier tool to Zoom in/out so you can make better cuts
+ Quick look
+ Choose an image to cut
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..f1c49f3
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..efac220
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Oct 23 15:53:16 PDT 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+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" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@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
+
+@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=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+: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 %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="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!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/images/Capture.JPG b/images/Capture.JPG
new file mode 100644
index 0000000..fcd818c
Binary files /dev/null and b/images/Capture.JPG differ
diff --git a/images/Capture_2.JPG b/images/Capture_2.JPG
new file mode 100644
index 0000000..d376d63
Binary files /dev/null and b/images/Capture_2.JPG differ
diff --git a/images/Magic_Wand.JPG b/images/Magic_Wand.JPG
new file mode 100644
index 0000000..22575a1
Binary files /dev/null and b/images/Magic_Wand.JPG differ
diff --git a/images/Pencil.JPG b/images/Pencil.JPG
new file mode 100644
index 0000000..fcd10e0
Binary files /dev/null and b/images/Pencil.JPG differ
diff --git a/images/Zoom.JPG b/images/Zoom.JPG
new file mode 100644
index 0000000..0ec7ef3
Binary files /dev/null and b/images/Zoom.JPG differ
diff --git a/images/before_app.jpg b/images/before_app.jpg
new file mode 100644
index 0000000..c91dd45
Binary files /dev/null and b/images/before_app.jpg differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..3202055
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':test', ':cutout'
diff --git a/test/.classpath b/test/.classpath
new file mode 100644
index 0000000..eb19361
--- /dev/null
+++ b/test/.classpath
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..01365f0
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1,3 @@
+/build
+/.externalNativeBuild
+/release
\ No newline at end of file
diff --git a/test/.project b/test/.project
new file mode 100644
index 0000000..ac485d7
--- /dev/null
+++ b/test/.project
@@ -0,0 +1,23 @@
+
+
+ app
+ Project app created by Buildship.
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/test/.settings/org.eclipse.buildship.core.prefs b/test/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
index 0000000..b1886ad
--- /dev/null
+++ b/test/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=..
+eclipse.preferences.version=1
diff --git a/test/build.gradle b/test/build.gradle
new file mode 100644
index 0000000..ae4cdec
--- /dev/null
+++ b/test/build.gradle
@@ -0,0 +1,41 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion rootProject.compileSdkVersion
+ defaultConfig {
+ applicationId rootProject.PUBLISH_GROUP_ID + "." + rootProject.PUBLISH_ARTIFACT_ID + ".test"
+ minSdkVersion rootProject.minSdkVersion
+ targetSdkVersion rootProject.compileSdkVersion
+ versionCode rootProject.versionCode
+ versionName rootProject.PUBLISH_VERSION
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility rootProject.javaVersion
+ targetCompatibility rootProject.javaVersion
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'com.android.support:design:28.0.0'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test:rules:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation project(path: ':cutout')
+ //implementation rootProject.PUBLISH_GROUP_ID + ":" + rootProject.PUBLISH_ARTIFACT_ID + ':' + rootProject.PUBLISH_VERSION
+}
diff --git a/test/proguard-rules.pro b/test/proguard-rules.pro
new file mode 100644
index 0000000..6d03d04
--- /dev/null
+++ b/test/proguard-rules.pro
@@ -0,0 +1,22 @@
+# 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
+-keep class android.support.v7.widget.** { *; }
\ No newline at end of file
diff --git a/test/src/androidTest/java/com/github/gabrielbb/cutout/MainActivityTest.java b/test/src/androidTest/java/com/github/gabrielbb/cutout/MainActivityTest.java
new file mode 100644
index 0000000..9cf0b62
--- /dev/null
+++ b/test/src/androidTest/java/com/github/gabrielbb/cutout/MainActivityTest.java
@@ -0,0 +1,178 @@
+package com.github.gabrielbb.cutout;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.rule.GrantPermissionRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.github.gabrielbb.cutout.test.MainActivity;
+import com.github.gabrielbb.cutout.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.Manifest.permission.CAMERA;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class MainActivityTest {
+
+ private ImageView imageView;
+
+ @Rule
+ public ActivityTestRule activityRule = new ActivityTestRule<>(MainActivity.class);
+
+ @Rule
+ public GrantPermissionRule permissionRule = GrantPermissionRule.grant(CAMERA, WRITE_EXTERNAL_STORAGE);
+
+ @Before
+ public void init() {
+ this.imageView = activityRule.getActivity().findViewById(R.id.imageView);
+ assertEquals(imageView.getTag(), activityRule.getActivity().getUriFromDrawable(R.drawable.image_icon));
+
+ onView(withId(R.id.fab)).perform(click());
+ }
+
+ @Test
+ public void testActivityShowUp() {
+ assertThat(getCurrentActivity(), instanceOf(CutOutActivity.class));
+ }
+
+ @Test
+ public void testSavingWithNoChange() {
+ final Uri initialUri = (Uri) imageView.getTag();
+
+ onView(withId(R.id.done)).perform(click());
+
+ assertNotEquals(imageView.getTag(), initialUri);
+ }
+
+ public void testDefaultActivatedTool() {
+ CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
+
+ assertTrue(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
+ }
+
+
+ @Test
+ public void testMagicToolButtonClick() {
+ CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
+
+ onView(withId(R.id.auto_clear_button)).perform(click());
+ assertTrue(cutOutActivity.findViewById(R.id.auto_clear_button).isActivated());
+ assertFalse(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
+ assertFalse(cutOutActivity.findViewById(R.id.zoom_button).isActivated());
+ }
+
+ @Test
+ public void testManualToolButtonClick() {
+ CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
+
+ onView(withId(R.id.manual_clear_button)).perform(click());
+ assertTrue(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
+ assertFalse(cutOutActivity.findViewById(R.id.auto_clear_button).isActivated());
+ assertFalse(cutOutActivity.findViewById(R.id.zoom_button).isActivated());
+ }
+
+ @Test
+ public void testZoomButtonClick() {
+ CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
+
+ onView(withId(R.id.zoom_button)).perform(click());
+ assertTrue(cutOutActivity.findViewById(R.id.zoom_button).isActivated());
+ assertFalse(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
+ assertFalse(cutOutActivity.findViewById(R.id.auto_clear_button).isActivated());
+ }
+
+ @Test
+ public void testMagicToolEffect() {
+ final Uri initialUri = (Uri) imageView.getTag();
+
+ final CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
+
+ testMagicToolButtonClick();
+
+ final DrawView drawView = cutOutActivity.findViewById(R.id.drawView);
+
+ final Bitmap initialBitmap = drawView.getCurrentBitmap();
+
+ onView(withId(R.id.drawView)).perform(click());
+
+ final View loadingModal = cutOutActivity.findViewById(R.id.loadingModal);
+
+ do {
+ SystemClock.sleep(2000);
+ }
+ while (loadingModal.getVisibility() == View.VISIBLE);
+
+ assertFalse(initialBitmap.sameAs(drawView.getCurrentBitmap()));
+
+ onView(withId(R.id.done)).perform(click());
+
+ assertNotEquals(imageView.getTag(), initialUri);
+ }
+
+ @Test
+ public void testUndoButton() {
+ final Uri initialUri = (Uri) imageView.getTag();
+
+ final CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
+
+ testMagicToolButtonClick();
+
+ final DrawView drawView = cutOutActivity.findViewById(R.id.drawView);
+
+ final Bitmap initialBitmap = drawView.getCurrentBitmap();
+
+ onView(withId(R.id.drawView)).perform(click());
+
+ final View loadingModal = cutOutActivity.findViewById(R.id.loadingModal);
+
+ do {
+ SystemClock.sleep(2000);
+ }
+ while (loadingModal.getVisibility() == View.VISIBLE);
+
+ assertFalse(initialBitmap.sameAs(drawView.getCurrentBitmap()));
+
+ onView(withId(R.id.undo)).perform(click());
+
+ assertTrue(initialBitmap.sameAs(drawView.getCurrentBitmap()));
+
+ onView(withId(R.id.redo)).perform(click());
+
+ assertFalse(initialBitmap.sameAs(drawView.getCurrentBitmap()));
+
+ onView(withId(R.id.done)).perform(click());
+
+ assertNotEquals(imageView.getTag(), initialUri);
+ }
+
+ private Activity getCurrentActivity() {
+ final Activity[] activity = new Activity[1];
+ onView(isRoot()).check((view, noViewFoundException) -> activity[0] = (Activity) view.getContext());
+ return activity[0];
+ }
+}
\ No newline at end of file
diff --git a/test/src/main/AndroidManifest.xml b/test/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..56a7ec5
--- /dev/null
+++ b/test/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/src/main/java/com/github/gabrielbb/cutout/test/MainActivity.java b/test/src/main/java/com/github/gabrielbb/cutout/test/MainActivity.java
new file mode 100644
index 0000000..cd7afd0
--- /dev/null
+++ b/test/src/main/java/com/github/gabrielbb/cutout/test/MainActivity.java
@@ -0,0 +1,70 @@
+package com.github.gabrielbb.cutout.test;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.widget.ImageView;
+
+import com.github.gabrielbb.cutout.CutOut;
+
+public class MainActivity extends AppCompatActivity {
+
+ private ImageView imageView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ imageView = findViewById(R.id.imageView);
+
+ final Uri imageIconUri = getUriFromDrawable(R.drawable.image_icon);
+ imageView.setImageURI(imageIconUri);
+ imageView.setTag(imageIconUri);
+
+ FloatingActionButton fab = findViewById(R.id.fab);
+
+ fab.setOnClickListener(view -> {
+ final Uri testImageUri = getUriFromDrawable(R.drawable.test_image);
+
+ CutOut.activity()
+ .src(testImageUri)
+ .bordered()
+ .noCrop()
+ .start(this);
+ });
+
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ if (requestCode == CutOut.CUTOUT_ACTIVITY_REQUEST_CODE) {
+
+ switch (resultCode) {
+ case Activity.RESULT_OK:
+ Uri imageUri = CutOut.getUri(data);
+ // Save the image using the returned Uri here
+ imageView.setImageURI(imageUri);
+ imageView.setTag(imageUri);
+ break;
+ case CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE:
+ Exception ex = CutOut.getError(data);
+ break;
+ default:
+ System.out.print("User cancelled the CutOut screen");
+ }
+ }
+ }
+
+ public Uri getUriFromDrawable(int drawableId) {
+ return Uri.parse("android.resource://" + getPackageName() + "/drawable/" + getApplicationContext().getResources().getResourceEntryName(drawableId));
+ }
+}
diff --git a/test/src/main/res/drawable/image_icon.png b/test/src/main/res/drawable/image_icon.png
new file mode 100644
index 0000000..aff55e9
Binary files /dev/null and b/test/src/main/res/drawable/image_icon.png differ
diff --git a/test/src/main/res/drawable/test_image.jpg b/test/src/main/res/drawable/test_image.jpg
new file mode 100644
index 0000000..49ad883
Binary files /dev/null and b/test/src/main/res/drawable/test_image.jpg differ
diff --git a/test/src/main/res/layout/activity_main.xml b/test/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..5652ac9
--- /dev/null
+++ b/test/src/main/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/src/main/res/layout/content_main.xml b/test/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..98cf06f
--- /dev/null
+++ b/test/src/main/res/layout/content_main.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/test/src/main/res/values/dimens.xml b/test/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..59a0b0c
--- /dev/null
+++ b/test/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+
+ 16dp
+
diff --git a/test/src/main/res/values/strings.xml b/test/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d872907
--- /dev/null
+++ b/test/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ Selfie Stickers for Whatsapp
+ MainActivity
+ Image
+ ca-app-pub-3940256099942544/6300978111
+
diff --git a/test/src/main/res/values/styles.xml b/test/src/main/res/values/styles.xml
new file mode 100644
index 0000000..f5be521
--- /dev/null
+++ b/test/src/main/res/values/styles.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+