├── .gitignore
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── cutout
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── publish.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── gabrielbb
│ │ └── cutout
│ │ ├── BitmapUtility.java
│ │ ├── CutOut.java
│ │ ├── CutOutActivity.java
│ │ ├── DrawView.java
│ │ ├── IntroActivity.java
│ │ └── SaveDrawingTask.java
│ └── res
│ ├── drawable
│ ├── done.png
│ ├── intro_magic_wand.png
│ ├── intro_magnifier.png
│ ├── intro_pencil.png
│ ├── magic_active.png
│ ├── magic_inactive.png
│ ├── magic_selector.xml
│ ├── magnifier_active.png
│ ├── magnifier_inactive.png
│ ├── magnifier_selector.xml
│ ├── pencil_active.png
│ ├── pencil_inactive.png
│ ├── pencil_selector.xml
│ ├── redo_active.png
│ ├── redo_inactive.png
│ ├── redo_selector.xml
│ ├── undo_active.png
│ ├── undo_inactive.png
│ └── undo_selector.xml
│ ├── layout
│ └── activity_photo_edit.xml
│ └── values
│ ├── colors.xml
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── Capture.JPG
├── Capture_2.JPG
├── Magic_Wand.JPG
├── Pencil.JPG
├── Zoom.JPG
└── before_app.jpg
├── settings.gradle
└── test
├── .classpath
├── .gitignore
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── build.gradle
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── github
│ └── gabrielbb
│ └── cutout
│ └── MainActivityTest.java
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── github
│ └── gabrielbb
│ └── cutout
│ └── test
│ └── MainActivity.java
└── res
├── drawable
├── image_icon.png
└── test_image.jpg
├── layout
├── activity_main.xml
└── content_main.xml
└── values
├── dimens.xml
├── strings.xml
└── styles.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .DS_Store
3 | .idea
4 | build/
5 | local.properties
6 | localhost/
7 | obj/
8 | *.iml
9 | Gemfile.lock
10 | _site/
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Android
4 | Project Android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 | sudo: false
4 |
5 | android:
6 |
7 | components:
8 | - tools
9 | - platform-tools
10 | - build-tools-28.0.3
11 | - android-28
12 | - android-22
13 | - extra-google-google_play_services
14 | - extra-google-m2repository
15 | - extra-android-m2repository
16 | - sys-img-armeabi-v7a-android-22
17 |
18 | before_script:
19 | - echo yes | android update sdk --all --filter build-tools-28.0.3 --no-ui --force
20 | - touch local.properties
21 | - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a -c 100M
22 | - emulator -avd test -no-audio -no-window &
23 | - android-wait-for-emulator
24 | - adb shell input keyevent 82 &
25 | - chmod +x gradlew
26 |
27 | script:
28 | - ./gradlew clean build connectedCheck
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Gabriel Basilio Brito
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to use,
5 | copy, modify, merge, and to permit persons to whom the Software is furnished to do so,
6 | subject to the following conditions:
7 |
8 | 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.
9 |
10 | 2 - The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ✂️
5 | Android-CutOut
6 |
7 | Android image background cutting library
8 |
9 |
10 | [  ](https://bintray.com/gabrielbb/Android-CutOut/Android-CutOut/_latestVersion)
11 | [  ](https://api.travis-ci.org/GabrielBB/Android-CutOut.svg?branch=master)
12 |
13 | ## Usage
14 |
15 | Add Gradle dependency:
16 | ```groovy
17 | implementation 'com.github.gabrielbb:cutout:0.1.2'
18 | ```
19 |
20 | Start the CutOut screen with this single line:
21 |
22 | ```java
23 | CutOut.activity().start(this);
24 | ```
25 |
26 |
27 |
28 | ### Getting the result
29 |
30 | ```java
31 | @Override
32 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
33 | if (requestCode == CutOut.CUTOUT_ACTIVITY_REQUEST_CODE) {
34 |
35 | switch (resultCode) {
36 | case Activity.RESULT_OK:
37 | Uri imageUri = CutOut.getUri(data);
38 | // Save the image using the returned Uri here
39 | break;
40 | case CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE:
41 | Exception ex = CutOut.getError(data);
42 | break;
43 | default:
44 | System.out.print("User cancelled the CutOut screen");
45 | }
46 | }
47 | }
48 | ```
49 |
50 | ## Features
51 |
52 |
53 |
54 |
55 | ## Options
56 |
57 | You can use one or more options from these:
58 |
59 | ```java
60 | CutOut.activity()
61 | .src(uri)
62 | .bordered()
63 | .noCrop()
64 | .intro()
65 | .start(this);
66 | ```
67 |
68 | - #### src
69 |
70 | 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:
71 |
72 | ```java
73 | Uri uri = Uri.parse("/images/cat.jpg");
74 |
75 | CutOut.activity().src(uri).start(this);
76 | ```
77 |
78 |
79 | - #### bordered
80 |
81 | ```java
82 | CutOut.activity().bordered().start(this);
83 | ```
84 |
85 | 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.
86 |
87 |
88 | - #### noCrop
89 |
90 | ```java
91 | CutOut.activity().noCrop().start(this);
92 | ```
93 |
94 | 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.
95 |
96 |
97 |
98 | - #### intro
99 |
100 | ```java
101 | CutOut.activity().intro().start(this);
102 | ```
103 |
104 | 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.
105 |
106 | ## Change log
107 | *0.1.2*
108 | - Removed Admob Ads automatic integration. I will probably add it later. For now, it was causing problems.
109 | - 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.
110 |
111 | ## License
112 | Copyright (c) 2018 Gabriel Basilio Brito
113 |
114 | Permission is hereby granted, free of charge, to any person obtaining a copy
115 | of this software and associated documentation files (the "Software"), to use,
116 | copy, modify, merge, and to permit persons to whom the Software is furnished to do so,
117 | subject to the following conditions:
118 |
119 | 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.
120 |
121 | 2 - The above copyright notice and this permission notice shall be included in all
122 | copies or substantial portions of the Software.
123 |
124 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
125 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
126 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
127 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
128 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
129 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
130 | SOFTWARE.
131 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.2.1'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | maven {
22 | url 'https://jitpack.io'
23 | }
24 | maven { url 'https://dl.bintray.com/gabrielbb/Android-CutOut' }
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
32 | ext {
33 | compileSdkVersion = 28
34 | minSdkVersion = 15
35 | buildToolsVersion = '28.0.3'
36 | versionCode = 2
37 | javaVersion = JavaVersion.VERSION_1_8
38 | PUBLISH_GROUP_ID = 'com.github.gabrielbb'
39 | PUBLISH_ARTIFACT_ID = 'cutout'
40 | PUBLISH_VERSION = '0.1.2'
41 | }
--------------------------------------------------------------------------------
/cutout/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/cutout/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply from: 'publish.gradle'
3 |
4 | android {
5 | compileSdkVersion rootProject.compileSdkVersion
6 | defaultConfig {
7 | minSdkVersion rootProject.minSdkVersion
8 | targetSdkVersion rootProject.compileSdkVersion
9 | versionCode rootProject.versionCode
10 | versionName rootProject.PUBLISH_VERSION
11 |
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 |
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | compileOptions {
24 | sourceCompatibility rootProject.javaVersion
25 | targetCompatibility rootProject.javaVersion
26 | }
27 |
28 | lintOptions {
29 | abortOnError false
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation fileTree(dir: 'libs', include: ['*.jar'])
35 |
36 | implementation 'com.android.support:appcompat-v7:28.0.0'
37 | implementation 'com.github.duanhong169:checkerboarddrawable:1.0.2'
38 | implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0'
39 | implementation 'com.alexvasilkov:gesture-views:2.5.2'
40 | implementation 'com.github.apl-devs:appintro:v4.2.3'
41 | implementation 'com.github.jkwiecien:EasyImage:1.3.1'
42 | }
43 |
44 | buildscript {
45 | repositories {
46 | jcenter()
47 | }
48 | dependencies {
49 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
50 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/cutout/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/cutout/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.github.dcendents.android-maven'
2 | apply plugin: 'com.jfrog.bintray'
3 |
4 | android {
5 | compileSdkVersion rootProject.compileSdkVersion
6 | }
7 |
8 |
9 | ext {
10 | libraryName = 'Android-CutOut'
11 | bintrayRepo = libraryName
12 | bintrayName = bintrayRepo
13 | libraryDescription = 'Android image background removing library'
14 |
15 | gitUrl = 'https://github.com/GabrielBB/Android-CutOut.git'
16 | siteUrl = gitUrl
17 |
18 | developerId = 'gabrielbb'
19 | developerName = 'Gabriel Basilio Brito'
20 | developerEmail = 'gabrielbb0306@gmail.com'
21 |
22 | licenseName = 'The Apache Software License, Version 2.0'
23 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
24 | allLicenses = ["Apache-2.0"]
25 | }
26 |
27 | group = rootProject.PUBLISH_GROUP_ID
28 | version = rootProject.PUBLISH_VERSION
29 |
30 | install {
31 | repositories.mavenInstaller {
32 | pom.project {
33 | packaging 'aar'
34 | groupId rootProject.PUBLISH_GROUP_ID
35 | artifactId rootProject.PUBLISH_ARTIFACT_ID
36 |
37 | name libraryName
38 | description libraryDescription
39 | url siteUrl
40 |
41 | licenses {
42 | license {
43 | name licenseName
44 | url licenseUrl
45 | }
46 | }
47 | developers {
48 | developer {
49 | id developerId
50 | name developerName
51 | email developerEmail
52 | }
53 | }
54 | scm {
55 | connection gitUrl
56 | developerConnection gitUrl
57 | url siteUrl
58 | }
59 | }
60 | }
61 | }
62 |
63 | task sourcesJar(type: Jar) {
64 | classifier = 'sources'
65 | from android.sourceSets.main.java.srcDirs
66 | }
67 |
68 | task javadoc(type: Javadoc) {
69 | source = android.sourceSets.main.java.srcDirs
70 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
71 | }
72 |
73 | task javadocJar(type: Jar, dependsOn: javadoc) {
74 | classifier = 'javadoc'
75 | from javadoc.destinationDir
76 | }
77 |
78 | artifacts {
79 | archives javadocJar
80 | archives sourcesJar
81 | }
82 |
83 | Properties properties = new Properties()
84 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
85 |
86 | bintray {
87 | user = properties.getProperty("bintray.user")
88 | key = properties.getProperty("bintray.apikey")
89 |
90 | configurations = ['archives']
91 | pkg {
92 | repo = bintrayRepo
93 | name = bintrayName
94 | desc = libraryDescription
95 | websiteUrl = siteUrl
96 | vcsUrl = gitUrl
97 | licenses = allLicenses
98 | dryRun = false
99 | publish = true
100 | override = false
101 | publicDownloadNumbers = true
102 | version {
103 | desc = libraryDescription
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/cutout/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
14 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Canvas;
5 | import android.graphics.Matrix;
6 | import android.graphics.Paint;
7 | import android.graphics.PorterDuff;
8 | import android.graphics.PorterDuffColorFilter;
9 |
10 | class BitmapUtility {
11 |
12 | static Bitmap getResizedBitmap(Bitmap bitmap, int width, int height) {
13 | Bitmap background = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
14 |
15 | float originalWidth = bitmap.getWidth();
16 | float originalHeight = bitmap.getHeight();
17 |
18 | Canvas canvas = new Canvas(background);
19 |
20 | float scale = width / originalWidth;
21 |
22 | float xTranslation = 0.0f;
23 | float yTranslation = (height - originalHeight * scale) / 2.0f;
24 |
25 | Matrix transformation = new Matrix();
26 | transformation.postTranslate(xTranslation, yTranslation);
27 | transformation.preScale(scale, scale);
28 |
29 | Paint paint = new Paint();
30 | paint.setFilterBitmap(true);
31 |
32 | canvas.drawBitmap(bitmap, transformation, paint);
33 |
34 | return background;
35 | }
36 |
37 | static Bitmap getBorderedBitmap(Bitmap image, int borderColor, int borderSize) {
38 |
39 | // Creating a canvas with an empty bitmap, this is the bitmap that gonna store the final canvas changes
40 | Bitmap finalImage = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
41 | Canvas canvas = new Canvas(finalImage);
42 |
43 | // Make a smaller copy of the image to draw on top of original
44 | Bitmap imageCopy = Bitmap.createScaledBitmap(image, image.getWidth() - borderSize, image.getHeight() - borderSize, true);
45 |
46 | // Let's draw the bigger image using a white paint brush
47 | Paint paint = new Paint();
48 | paint.setColorFilter(new PorterDuffColorFilter(borderColor, PorterDuff.Mode.SRC_ATOP));
49 | canvas.drawBitmap(image, 0, 0, paint);
50 |
51 | int width = image.getWidth();
52 | int height = image.getHeight();
53 | float centerX = (width - imageCopy.getWidth()) * 0.5f;
54 | float centerY = (height - imageCopy.getHeight()) * 0.5f;
55 | // Now let's draw the original image on top of the white image, passing a null paint because we want to keep it original
56 | canvas.drawBitmap(imageCopy, centerX, centerY, null);
57 |
58 | // Returning the image with the final results
59 | return finalImage;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.graphics.Color;
7 | import android.net.Uri;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 |
11 | public class CutOut {
12 |
13 | public static final short CUTOUT_ACTIVITY_REQUEST_CODE = 368;
14 | public static final short CUTOUT_ACTIVITY_RESULT_ERROR_CODE = 3680;
15 |
16 | static final String CUTOUT_EXTRA_SOURCE = "CUTOUT_EXTRA_SOURCE";
17 | static final String CUTOUT_EXTRA_RESULT = "CUTOUT_EXTRA_RESULT";
18 | static final String CUTOUT_EXTRA_BORDER_COLOR = "CUTOUT_EXTRA_BORDER_COLOR";
19 | static final String CUTOUT_EXTRA_CROP = "CUTOUT_EXTRA_CROP";
20 | static final String CUTOUT_EXTRA_INTRO = "CUTOUT_EXTRA_INTRO";
21 |
22 | public static ActivityBuilder activity() {
23 | return new ActivityBuilder();
24 | }
25 |
26 | /**
27 | * Builder used for creating CutOut Activity by user request.
28 | */
29 | public static final class ActivityBuilder {
30 |
31 | @Nullable
32 | private Uri source; // The image to crop source Android uri
33 | private boolean bordered;
34 | private boolean crop = true; // By default the cropping activity is started
35 | private boolean intro;
36 | private int borderColor = Color.WHITE; // Default border color is no border color is passed
37 |
38 | private ActivityBuilder() {
39 |
40 | }
41 |
42 | /**
43 | * Get {@link CutOutActivity} intent to start the activity.
44 | */
45 | private Intent getIntent(@NonNull Context context) {
46 | Intent intent = new Intent();
47 | intent.setClass(context, CutOutActivity.class);
48 |
49 | if (source != null) {
50 | intent.putExtra(CUTOUT_EXTRA_SOURCE, source);
51 | }
52 |
53 | if (bordered) {
54 | intent.putExtra(CUTOUT_EXTRA_BORDER_COLOR, borderColor);
55 | }
56 |
57 | if (crop) {
58 | intent.putExtra(CUTOUT_EXTRA_CROP, true);
59 | }
60 |
61 | if (intro) {
62 | intent.putExtra(CUTOUT_EXTRA_INTRO, true);
63 | }
64 |
65 | return intent;
66 | }
67 |
68 | /**
69 | * By default the user can select images from camera or gallery but you can also call this method to load a pre-saved image
70 | *
71 | * @param source {@link android.net.Uri} instance of the image to be loaded
72 | */
73 | public ActivityBuilder src(Uri source) {
74 | this.source = source;
75 | return this;
76 | }
77 |
78 | /**
79 | * This method adds a white border around the final PNG image
80 | */
81 | public ActivityBuilder bordered() {
82 | this.bordered = true;
83 | return this;
84 | }
85 |
86 | /**
87 | * This method adds a border around the final PNG image
88 | *
89 | * @param borderColor The border color. You can pass any {@link android.graphics.Color}
90 | */
91 | public ActivityBuilder bordered(int borderColor) {
92 | this.borderColor = borderColor;
93 | return bordered();
94 | }
95 |
96 | /**
97 | * Disables the cropping screen shown before the background removal screen
98 | */
99 | public ActivityBuilder noCrop() {
100 | this.crop = false;
101 | return this;
102 | }
103 |
104 | /**
105 | * Shows an introduction to the activity, explaining every button usage. The intro is show only once.
106 | */
107 | public ActivityBuilder intro() {
108 | this.intro = true;
109 | return this;
110 | }
111 |
112 | /**
113 | * Start {@link CutOutActivity}.
114 | *
115 | * @param activity activity to receive result
116 | */
117 | public void start(@NonNull Activity activity) {
118 | activity.startActivityForResult(getIntent(activity), CUTOUT_ACTIVITY_REQUEST_CODE);
119 | }
120 | }
121 |
122 | /**
123 | * Reads the {@link android.net.Uri} from the result data. This Uri is the path to the saved PNG
124 | *
125 | * @param data Result data to get the Uri from
126 | */
127 | public static Uri getUri(@Nullable Intent data) {
128 | return data != null ? data.getParcelableExtra(CUTOUT_EXTRA_RESULT) : null;
129 | }
130 |
131 | /**
132 | * Gets an Exception from the result data if the {@link CutOutActivity} failed at some point
133 | *
134 | * @param data Result data to get the Exception from
135 | */
136 | public static Exception getError(@Nullable Intent data) {
137 | return data != null ? (Exception) data.getSerializableExtra(CUTOUT_EXTRA_RESULT) : null;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.content.pm.PackageManager;
9 | import android.graphics.Bitmap;
10 | import android.graphics.Color;
11 | import android.graphics.PorterDuff;
12 | import android.net.Uri;
13 | import android.os.Bundle;
14 | import android.provider.MediaStore;
15 | import android.support.annotation.NonNull;
16 | import android.support.v4.app.ActivityCompat;
17 | import android.support.v4.content.ContextCompat;
18 | import android.support.v7.app.AppCompatActivity;
19 | import android.support.v7.widget.Toolbar;
20 | import android.view.MenuItem;
21 | import android.view.View;
22 | import android.widget.Button;
23 | import android.widget.FrameLayout;
24 | import android.widget.LinearLayout;
25 | import android.widget.SeekBar;
26 |
27 | import com.alexvasilkov.gestures.views.interfaces.GestureView;
28 | import com.theartofdev.edmodo.cropper.CropImage;
29 | import com.theartofdev.edmodo.cropper.CropImageView;
30 |
31 | import java.io.File;
32 | import java.io.IOException;
33 |
34 | import pl.aprilapps.easyphotopicker.DefaultCallback;
35 | import pl.aprilapps.easyphotopicker.EasyImage;
36 | import top.defaults.checkerboarddrawable.CheckerboardDrawable;
37 |
38 | import static android.view.View.INVISIBLE;
39 | import static android.view.View.VISIBLE;
40 | import static com.github.gabrielbb.cutout.CutOut.CUTOUT_EXTRA_INTRO;
41 |
42 | public class CutOutActivity extends AppCompatActivity {
43 |
44 | private static final int INTRO_REQUEST_CODE = 4;
45 | private static final int WRITE_EXTERNAL_STORAGE_CODE = 1;
46 | private static final int IMAGE_CHOOSER_REQUEST_CODE = 2;
47 | private static final int CAMERA_REQUEST_CODE = 3;
48 |
49 | private static final String INTRO_SHOWN = "INTRO_SHOWN";
50 | FrameLayout loadingModal;
51 | private GestureView gestureView;
52 | private DrawView drawView;
53 | private LinearLayout manualClearSettingsLayout;
54 |
55 | private static final short MAX_ERASER_SIZE = 150;
56 | private static final short BORDER_SIZE = 45;
57 | private static final float MAX_ZOOM = 4F;
58 |
59 | @Override
60 | protected void onCreate(Bundle savedInstanceState) {
61 | super.onCreate(savedInstanceState);
62 |
63 | setContentView(R.layout.activity_photo_edit);
64 |
65 | Toolbar toolbar = findViewById(R.id.photo_edit_toolbar);
66 | toolbar.setBackgroundColor(Color.BLACK);
67 | toolbar.setTitleTextColor(Color.WHITE);
68 | setSupportActionBar(toolbar);
69 |
70 | FrameLayout drawViewLayout = findViewById(R.id.drawViewLayout);
71 |
72 | int sdk = android.os.Build.VERSION.SDK_INT;
73 |
74 | if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
75 | drawViewLayout.setBackgroundDrawable(CheckerboardDrawable.create());
76 | } else {
77 | drawViewLayout.setBackground(CheckerboardDrawable.create());
78 | }
79 |
80 | SeekBar strokeBar = findViewById(R.id.strokeBar);
81 | strokeBar.setMax(MAX_ERASER_SIZE);
82 | strokeBar.setProgress(50);
83 |
84 | gestureView = findViewById(R.id.gestureView);
85 |
86 | drawView = findViewById(R.id.drawView);
87 | drawView.setDrawingCacheEnabled(true);
88 | drawView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
89 | //drawView.setDrawingCacheEnabled(true);
90 | drawView.setStrokeWidth(strokeBar.getProgress());
91 |
92 | strokeBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
93 | @Override
94 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
95 |
96 | }
97 |
98 | @Override
99 | public void onStartTrackingTouch(SeekBar seekBar) {
100 |
101 | }
102 |
103 | @Override
104 | public void onStopTrackingTouch(SeekBar seekBar) {
105 | drawView.setStrokeWidth(seekBar.getProgress());
106 | }
107 | });
108 |
109 | loadingModal = findViewById(R.id.loadingModal);
110 | loadingModal.setVisibility(INVISIBLE);
111 |
112 | drawView.setLoadingModal(loadingModal);
113 |
114 | manualClearSettingsLayout = findViewById(R.id.manual_clear_settings_layout);
115 |
116 | setUndoRedo();
117 | initializeActionButtons();
118 |
119 | if (getSupportActionBar() != null) {
120 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
121 | getSupportActionBar().setDisplayShowHomeEnabled(true);
122 | getSupportActionBar().setDisplayShowTitleEnabled(false);
123 |
124 | if (toolbar.getNavigationIcon() != null) {
125 | toolbar.getNavigationIcon().setColorFilter(getResources().getColor(R.color.white), PorterDuff.Mode.SRC_ATOP);
126 | }
127 |
128 | }
129 |
130 | Button doneButton = findViewById(R.id.done);
131 |
132 | doneButton.setOnClickListener(v -> startSaveDrawingTask());
133 |
134 | if (getIntent().getBooleanExtra(CUTOUT_EXTRA_INTRO, false) && !getPreferences(Context.MODE_PRIVATE).getBoolean(INTRO_SHOWN, false)) {
135 | Intent intent = new Intent(this, IntroActivity.class);
136 | startActivityForResult(intent, INTRO_REQUEST_CODE);
137 | } else {
138 | start();
139 | }
140 | }
141 |
142 | @Override
143 | public boolean onOptionsItemSelected(MenuItem item) {
144 | switch (item.getItemId()) {
145 | // Respond to the action bar's Up/Home button
146 | case android.R.id.home:
147 | setResult(RESULT_CANCELED);
148 | finish();
149 | return true;
150 | }
151 | return super.onOptionsItemSelected(item);
152 | }
153 |
154 | private Uri getExtraSource() {
155 | return getIntent().hasExtra(CutOut.CUTOUT_EXTRA_SOURCE) ? (Uri) getIntent().getParcelableExtra(CutOut.CUTOUT_EXTRA_SOURCE) : null;
156 | }
157 |
158 | private void start() {
159 | if (ContextCompat.checkSelfPermission(this,
160 | Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
161 |
162 | Uri uri = getExtraSource();
163 |
164 | if (getIntent().getBooleanExtra(CutOut.CUTOUT_EXTRA_CROP, false)) {
165 |
166 | CropImage.ActivityBuilder cropImageBuilder;
167 | if (uri != null) {
168 | cropImageBuilder = CropImage.activity(uri);
169 | } else {
170 | if (ContextCompat.checkSelfPermission(this,
171 | Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
172 |
173 | cropImageBuilder = CropImage.activity();
174 | } else {
175 | ActivityCompat.requestPermissions(this,
176 | new String[]{Manifest.permission.CAMERA},
177 | CAMERA_REQUEST_CODE);
178 | return;
179 | }
180 | }
181 |
182 | cropImageBuilder = cropImageBuilder.setGuidelines(CropImageView.Guidelines.ON);
183 | cropImageBuilder.start(this);
184 | } else {
185 | if (uri != null) {
186 | setDrawViewBitmap(uri);
187 | } else {
188 | if (ContextCompat.checkSelfPermission(this,
189 | Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
190 |
191 | EasyImage.openChooserWithGallery(this, getString(R.string.image_chooser_message), IMAGE_CHOOSER_REQUEST_CODE);
192 | } else {
193 | ActivityCompat.requestPermissions(this,
194 | new String[]{Manifest.permission.CAMERA},
195 | CAMERA_REQUEST_CODE);
196 | }
197 | }
198 | }
199 |
200 | } else {
201 | ActivityCompat.requestPermissions(this,
202 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
203 | WRITE_EXTERNAL_STORAGE_CODE);
204 | }
205 | }
206 |
207 | private void startSaveDrawingTask() {
208 | SaveDrawingTask task = new SaveDrawingTask(this);
209 |
210 | int borderColor;
211 | if ((borderColor = getIntent().getIntExtra(CutOut.CUTOUT_EXTRA_BORDER_COLOR, -1)) != -1) {
212 | Bitmap image = BitmapUtility.getBorderedBitmap(this.drawView.getDrawingCache(), borderColor, BORDER_SIZE);
213 | task.execute(image);
214 | } else {
215 | task.execute(this.drawView.getDrawingCache());
216 | }
217 | }
218 |
219 | @Override
220 | public void onRequestPermissionsResult(int requestCode,
221 | @NonNull String permissions[], @NonNull int[] grantResults) {
222 | if (grantResults.length > 0
223 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
224 | start();
225 | } else {
226 | setResult(Activity.RESULT_CANCELED);
227 | finish();
228 | }
229 | }
230 |
231 | private void activateGestureView() {
232 | gestureView.getController().getSettings()
233 | .setMaxZoom(MAX_ZOOM)
234 | .setDoubleTapZoom(-1f) // Falls back to max zoom level
235 | .setPanEnabled(true)
236 | .setZoomEnabled(true)
237 | .setDoubleTapEnabled(true)
238 | .setOverscrollDistance(0f, 0f)
239 | .setOverzoomFactor(2f);
240 | }
241 |
242 | private void deactivateGestureView() {
243 | gestureView.getController().getSettings()
244 | .setPanEnabled(false)
245 | .setZoomEnabled(false)
246 | .setDoubleTapEnabled(false);
247 | }
248 |
249 | private void initializeActionButtons() {
250 | Button autoClearButton = findViewById(R.id.auto_clear_button);
251 | Button manualClearButton = findViewById(R.id.manual_clear_button);
252 | Button zoomButton = findViewById(R.id.zoom_button);
253 |
254 | autoClearButton.setActivated(false);
255 | autoClearButton.setOnClickListener((buttonView) -> {
256 | if (!autoClearButton.isActivated()) {
257 | drawView.setAction(DrawView.DrawViewAction.AUTO_CLEAR);
258 | manualClearSettingsLayout.setVisibility(INVISIBLE);
259 | autoClearButton.setActivated(true);
260 | manualClearButton.setActivated(false);
261 | zoomButton.setActivated(false);
262 | deactivateGestureView();
263 | }
264 | });
265 |
266 | manualClearButton.setActivated(true);
267 | drawView.setAction(DrawView.DrawViewAction.MANUAL_CLEAR);
268 | manualClearButton.setOnClickListener((buttonView) -> {
269 | if (!manualClearButton.isActivated()) {
270 | drawView.setAction(DrawView.DrawViewAction.MANUAL_CLEAR);
271 | manualClearSettingsLayout.setVisibility(VISIBLE);
272 | manualClearButton.setActivated(true);
273 | autoClearButton.setActivated(false);
274 | zoomButton.setActivated(false);
275 | deactivateGestureView();
276 | }
277 |
278 | });
279 |
280 | zoomButton.setActivated(false);
281 | deactivateGestureView();
282 | zoomButton.setOnClickListener((buttonView) -> {
283 | if (!zoomButton.isActivated()) {
284 | drawView.setAction(DrawView.DrawViewAction.ZOOM);
285 | manualClearSettingsLayout.setVisibility(INVISIBLE);
286 | zoomButton.setActivated(true);
287 | manualClearButton.setActivated(false);
288 | autoClearButton.setActivated(false);
289 | activateGestureView();
290 | }
291 |
292 | });
293 | }
294 |
295 | private void setUndoRedo() {
296 | Button undoButton = findViewById(R.id.undo);
297 | undoButton.setEnabled(false);
298 | undoButton.setOnClickListener(v -> undo());
299 | Button redoButton = findViewById(R.id.redo);
300 | redoButton.setEnabled(false);
301 | redoButton.setOnClickListener(v -> redo());
302 |
303 | drawView.setButtons(undoButton, redoButton);
304 | }
305 |
306 | void exitWithError(Exception e) {
307 | Intent intent = new Intent();
308 | intent.putExtra(CutOut.CUTOUT_EXTRA_RESULT, e);
309 | setResult(CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE, intent);
310 | finish();
311 | }
312 |
313 | private void setDrawViewBitmap(Uri uri) {
314 | try {
315 | Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
316 | drawView.setBitmap(bitmap);
317 | } catch (IOException e) {
318 | exitWithError(e);
319 | }
320 | }
321 |
322 | @Override
323 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
324 |
325 | if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
326 |
327 | CropImage.ActivityResult result = CropImage.getActivityResult(data);
328 |
329 | if (resultCode == Activity.RESULT_OK) {
330 |
331 | setDrawViewBitmap(result.getUri());
332 |
333 | } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
334 | exitWithError(result.getError());
335 | } else {
336 | setResult(Activity.RESULT_CANCELED);
337 | finish();
338 | }
339 | } else if (requestCode == INTRO_REQUEST_CODE) {
340 | SharedPreferences.Editor editor = getPreferences(Context.MODE_PRIVATE).edit();
341 | editor.putBoolean(INTRO_SHOWN, true);
342 | editor.apply();
343 | start();
344 | } else {
345 | EasyImage.handleActivityResult(requestCode, resultCode, data, this, new DefaultCallback() {
346 | @Override
347 | public void onImagePickerError(Exception e, EasyImage.ImageSource source, int type) {
348 | exitWithError(e);
349 | }
350 |
351 | @Override
352 | public void onImagePicked(File imageFile, EasyImage.ImageSource source, int type) {
353 | setDrawViewBitmap(Uri.parse(imageFile.toURI().toString()));
354 | }
355 |
356 | @Override
357 | public void onCanceled(EasyImage.ImageSource source, int type) {
358 | // Cancel handling, removing taken photo if it was canceled
359 | if (source == EasyImage.ImageSource.CAMERA) {
360 | File photoFile = EasyImage.lastlyTakenButCanceledPhoto(CutOutActivity.this);
361 | if (photoFile != null) photoFile.delete();
362 | }
363 |
364 | setResult(RESULT_CANCELED);
365 | finish();
366 | }
367 | });
368 | }
369 | }
370 |
371 | private void undo() {
372 | drawView.undo();
373 | }
374 |
375 | private void redo() {
376 | drawView.redo();
377 | }
378 |
379 | }
--------------------------------------------------------------------------------
/cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PorterDuff;
10 | import android.graphics.PorterDuffXfermode;
11 | import android.os.AsyncTask;
12 | import android.util.AttributeSet;
13 | import android.util.Pair;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 | import android.widget.Button;
17 |
18 | import java.lang.ref.WeakReference;
19 | import java.util.Stack;
20 |
21 | import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.AUTO_CLEAR;
22 | import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.MANUAL_CLEAR;
23 | import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.ZOOM;
24 |
25 | class DrawView extends View {
26 |
27 | private Path livePath;
28 | private Paint pathPaint;
29 |
30 | private Bitmap imageBitmap;
31 | private final Stack, Bitmap>> cuts = new Stack<>();
32 | private final Stack, Bitmap>> undoneCuts = new Stack<>();
33 |
34 | private float pathX, pathY;
35 |
36 | private static final float TOUCH_TOLERANCE = 4;
37 | private static final float COLOR_TOLERANCE = 20;
38 |
39 | private Button undoButton;
40 | private Button redoButton;
41 |
42 | private View loadingModal;
43 |
44 | private DrawViewAction currentAction;
45 |
46 | public enum DrawViewAction {
47 | AUTO_CLEAR,
48 | MANUAL_CLEAR,
49 | ZOOM
50 | }
51 |
52 | public DrawView(Context c, AttributeSet attrs) {
53 |
54 | super(c, attrs);
55 |
56 | livePath = new Path();
57 |
58 | pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
59 | pathPaint.setDither(true);
60 | pathPaint.setColor(Color.TRANSPARENT);
61 | pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
62 | pathPaint.setStyle(Paint.Style.STROKE);
63 | pathPaint.setStrokeJoin(Paint.Join.ROUND);
64 | pathPaint.setStrokeCap(Paint.Cap.ROUND);
65 | }
66 |
67 | public void setButtons(Button undoButton, Button redoButton) {
68 | this.undoButton = undoButton;
69 | this.redoButton = redoButton;
70 | }
71 |
72 |
73 | @Override
74 | protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) {
75 | super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
76 |
77 | resizeBitmap(newWidth, newHeight);
78 | }
79 |
80 | @Override
81 | protected void onDraw(Canvas canvas) {
82 | super.onDraw(canvas);
83 | canvas.save();
84 |
85 | if (imageBitmap != null) {
86 |
87 | canvas.drawBitmap(this.imageBitmap, 0, 0, null);
88 |
89 | for (Pair, Bitmap> action : cuts) {
90 | if (action.first != null) {
91 | canvas.drawPath(action.first.first, action.first.second);
92 | }
93 | }
94 |
95 | if (currentAction == MANUAL_CLEAR) {
96 | canvas.drawPath(livePath, pathPaint);
97 | }
98 | }
99 |
100 | canvas.restore();
101 | }
102 |
103 | private void touchStart(float x, float y) {
104 | pathX = x;
105 | pathY = y;
106 |
107 | undoneCuts.clear();
108 | redoButton.setEnabled(false);
109 |
110 | if (currentAction == AUTO_CLEAR) {
111 | new AutomaticPixelClearingTask(this).execute((int) x, (int) y);
112 | } else {
113 | livePath.moveTo(x, y);
114 | }
115 |
116 | invalidate();
117 | }
118 |
119 | private void touchMove(float x, float y) {
120 | if (currentAction == MANUAL_CLEAR) {
121 | float dx = Math.abs(x - pathX);
122 | float dy = Math.abs(y - pathY);
123 | if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
124 | livePath.quadTo(pathX, pathY, (x + pathX) / 2, (y + pathY) / 2);
125 | pathX = x;
126 | pathY = y;
127 | }
128 | }
129 | }
130 |
131 |
132 | private void touchUp() {
133 | if (currentAction == MANUAL_CLEAR) {
134 | livePath.lineTo(pathX, pathY);
135 | cuts.push(new Pair<>(new Pair<>(livePath, pathPaint), null));
136 | livePath = new Path();
137 | undoButton.setEnabled(true);
138 | }
139 | }
140 |
141 | public void undo() {
142 | if (cuts.size() > 0) {
143 |
144 | Pair, Bitmap> cut = cuts.pop();
145 |
146 | if (cut.second != null) {
147 | undoneCuts.push(new Pair<>(null, imageBitmap));
148 | this.imageBitmap = cut.second;
149 | } else {
150 | undoneCuts.push(cut);
151 | }
152 |
153 | if (cuts.isEmpty()) {
154 | undoButton.setEnabled(false);
155 | }
156 |
157 | redoButton.setEnabled(true);
158 |
159 | invalidate();
160 | }
161 | //toast the user
162 | }
163 |
164 | public void redo() {
165 | if (undoneCuts.size() > 0) {
166 |
167 | Pair, Bitmap> cut = undoneCuts.pop();
168 |
169 | if (cut.second != null) {
170 | cuts.push(new Pair<>(null, imageBitmap));
171 | this.imageBitmap = cut.second;
172 | } else {
173 | cuts.push(cut);
174 | }
175 |
176 | if (undoneCuts.isEmpty()) {
177 | redoButton.setEnabled(false);
178 | }
179 |
180 | undoButton.setEnabled(true);
181 |
182 | invalidate();
183 | }
184 | //toast the user
185 | }
186 |
187 | @Override
188 | public boolean onTouchEvent(MotionEvent ev) {
189 |
190 | if (imageBitmap != null && currentAction != ZOOM) {
191 | switch (ev.getAction()) {
192 | case MotionEvent.ACTION_DOWN:
193 | touchStart(ev.getX(), ev.getY());
194 | return true;
195 | case MotionEvent.ACTION_MOVE:
196 | touchMove(ev.getX(), ev.getY());
197 | invalidate();
198 | return true;
199 | case MotionEvent.ACTION_UP:
200 | touchUp();
201 | invalidate();
202 | return true;
203 | }
204 | }
205 |
206 | return super.onTouchEvent(ev);
207 | }
208 |
209 | private void resizeBitmap(int width, int height) {
210 | if (width > 0 && height > 0 && imageBitmap != null) {
211 | imageBitmap = BitmapUtility.getResizedBitmap(this.imageBitmap, width, height);
212 | imageBitmap.setHasAlpha(true);
213 | invalidate();
214 | }
215 | }
216 |
217 | public void setBitmap(Bitmap bitmap) {
218 | this.imageBitmap = bitmap;
219 | resizeBitmap(getWidth(), getHeight());
220 | }
221 |
222 | public Bitmap getCurrentBitmap() {
223 | return this.imageBitmap;
224 | }
225 |
226 | public void setAction(DrawViewAction newAction) {
227 | this.currentAction = newAction;
228 | }
229 |
230 | public void setStrokeWidth(int strokeWidth) {
231 | pathPaint = new Paint(pathPaint);
232 | pathPaint.setStrokeWidth(strokeWidth);
233 | }
234 |
235 | public void setLoadingModal(View loadingModal) {
236 | this.loadingModal = loadingModal;
237 | }
238 |
239 | private static class AutomaticPixelClearingTask extends AsyncTask {
240 |
241 | private WeakReference drawViewWeakReference;
242 |
243 | AutomaticPixelClearingTask(DrawView drawView) {
244 | this.drawViewWeakReference = new WeakReference<>(drawView);
245 | }
246 |
247 | @Override
248 | protected void onPreExecute() {
249 | super.onPreExecute();
250 | drawViewWeakReference.get().loadingModal.setVisibility(VISIBLE);
251 | drawViewWeakReference.get().cuts.push(new Pair<>(null, drawViewWeakReference.get().imageBitmap));
252 | }
253 |
254 | @Override
255 | protected Bitmap doInBackground(Integer... points) {
256 | Bitmap oldBitmap = drawViewWeakReference.get().imageBitmap;
257 |
258 | int colorToReplace = oldBitmap.getPixel(points[0], points[1]);
259 |
260 | int width = oldBitmap.getWidth();
261 | int height = oldBitmap.getHeight();
262 | int[] pixels = new int[width * height];
263 | oldBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
264 |
265 | int rA = Color.alpha(colorToReplace);
266 | int rR = Color.red(colorToReplace);
267 | int rG = Color.green(colorToReplace);
268 | int rB = Color.blue(colorToReplace);
269 |
270 | int pixel;
271 |
272 | // iteration through pixels
273 | for (int y = 0; y < height; ++y) {
274 | for (int x = 0; x < width; ++x) {
275 | // get current index in 2D-matrix
276 | int index = y * width + x;
277 | pixel = pixels[index];
278 | int rrA = Color.alpha(pixel);
279 | int rrR = Color.red(pixel);
280 | int rrG = Color.green(pixel);
281 | int rrB = Color.blue(pixel);
282 |
283 | if (rA - COLOR_TOLERANCE < rrA && rrA < rA + COLOR_TOLERANCE && rR - COLOR_TOLERANCE < rrR && rrR < rR + COLOR_TOLERANCE &&
284 | rG - COLOR_TOLERANCE < rrG && rrG < rG + COLOR_TOLERANCE && rB - COLOR_TOLERANCE < rrB && rrB < rB + COLOR_TOLERANCE) {
285 | pixels[index] = Color.TRANSPARENT;
286 | }
287 | }
288 | }
289 |
290 | Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
291 | newBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
292 |
293 | return newBitmap;
294 | }
295 |
296 | protected void onPostExecute(Bitmap result) {
297 | super.onPostExecute(result);
298 | drawViewWeakReference.get().imageBitmap = result;
299 | drawViewWeakReference.get().undoButton.setEnabled(true);
300 | drawViewWeakReference.get().loadingModal.setVisibility(INVISIBLE);
301 | drawViewWeakReference.get().invalidate();
302 | }
303 | }
304 | }
--------------------------------------------------------------------------------
/cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout;
2 |
3 | import android.graphics.Color;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.v4.app.Fragment;
7 |
8 | import com.github.paolorotolo.appintro.AppIntro;
9 | import com.github.paolorotolo.appintro.AppIntroFragment;
10 |
11 | public class IntroActivity extends AppIntro {
12 | @Override
13 | protected void onCreate(@Nullable Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 |
16 | // Note here that we DO NOT use setContentView();
17 |
18 | // Add your slide fragments here.
19 | // AppIntro will automatically generate the dots indicator and buttons.
20 | 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)));
21 | 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)));
22 | 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)));
23 |
24 | // OPTIONAL METHODS
25 | // Override bar/separator color.
26 | setBarColor(Color.parseColor("#3F51B5"));
27 | setSeparatorColor(Color.parseColor("#2196F3"));
28 |
29 | // Hide Skip/Done button.
30 | showSkipButton(false);
31 | setProgressButtonEnabled(true);
32 |
33 | // Turn vibration on and set intensity.
34 | // NOTE: you will probably need to ask VIBRATE permission in Manifest.
35 | setVibrate(false);
36 |
37 | setFadeAnimation();
38 | }
39 |
40 | @Override
41 | public void onSkipPressed(Fragment currentFragment) {
42 | super.onSkipPressed(currentFragment);
43 | // Do something when users tap on Skip button.
44 | finish();
45 | }
46 |
47 | @Override
48 | public void onDonePressed(Fragment currentFragment) {
49 | super.onDonePressed(currentFragment);
50 | // Do something when users tap on Done button.
51 | finish();
52 | }
53 |
54 | @Override
55 | public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) {
56 | super.onSlideChanged(oldFragment, newFragment);
57 | // Do something when the slide changes.
58 | }
59 | }
--------------------------------------------------------------------------------
/cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.graphics.Bitmap;
6 | import android.net.Uri;
7 | import android.os.AsyncTask;
8 | import android.os.Environment;
9 | import android.util.Pair;
10 |
11 | import java.io.File;
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.lang.ref.WeakReference;
15 | import java.util.UUID;
16 |
17 | import static android.view.View.VISIBLE;
18 |
19 | class SaveDrawingTask extends AsyncTask> {
20 |
21 | private static final String SAVED_IMAGE_FORMAT = "png";
22 | private static final String SAVED_IMAGE_NAME = "cutout_tmp";
23 |
24 | private final WeakReference activityWeakReference;
25 |
26 | SaveDrawingTask(CutOutActivity activity) {
27 | this.activityWeakReference = new WeakReference<>(activity);
28 | }
29 |
30 | @Override
31 | protected void onPreExecute() {
32 | super.onPreExecute();
33 | activityWeakReference.get().loadingModal.setVisibility(VISIBLE);
34 | }
35 |
36 | @Override
37 | protected Pair doInBackground(Bitmap... bitmaps) {
38 |
39 | try {
40 | File file = File.createTempFile(SAVED_IMAGE_NAME, SAVED_IMAGE_FORMAT, activityWeakReference.get().getApplicationContext().getCacheDir());
41 |
42 | try (FileOutputStream out = new FileOutputStream(file)) {
43 | bitmaps[0].compress(Bitmap.CompressFormat.PNG, 95, out);
44 | return new Pair<>(file, null);
45 | }
46 | } catch (IOException e) {
47 | return new Pair<>(null, e);
48 | }
49 | }
50 |
51 | protected void onPostExecute(Pair result) {
52 | super.onPostExecute(result);
53 |
54 | Intent resultIntent = new Intent();
55 |
56 | if (result.first != null) {
57 | Uri uri = Uri.fromFile(result.first);
58 |
59 | resultIntent.putExtra(CutOut.CUTOUT_EXTRA_RESULT, uri);
60 | activityWeakReference.get().setResult(Activity.RESULT_OK, resultIntent);
61 | activityWeakReference.get().finish();
62 |
63 | } else {
64 | activityWeakReference.get().exitWithError(result.second);
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/done.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/intro_magic_wand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/intro_magic_wand.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/intro_magnifier.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/intro_magnifier.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/intro_pencil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/intro_pencil.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/magic_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/magic_active.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/magic_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/magic_inactive.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/magic_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/magnifier_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/magnifier_active.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/magnifier_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/magnifier_inactive.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/magnifier_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/pencil_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/pencil_active.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/pencil_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/pencil_inactive.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/pencil_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/redo_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/redo_active.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/redo_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/redo_inactive.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/redo_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/undo_active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/undo_active.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/undo_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/cutout/src/main/res/drawable/undo_inactive.png
--------------------------------------------------------------------------------
/cutout/src/main/res/drawable/undo_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/cutout/src/main/res/layout/activity_photo_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
20 |
21 |
26 |
27 |
32 |
33 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
53 |
54 |
62 |
63 |
71 |
72 |
80 |
81 |
86 |
87 |
94 |
95 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
114 |
115 |
116 |
117 |
121 |
122 |
126 |
127 |
131 |
132 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/cutout/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/transparent
4 | #BABABA
5 | #56b95a
6 | #B3B3B3
7 | #EBEBEB
8 | @color/colorAccent
9 | #3a3a3a
10 | #b7000000
11 | #FFFFFF
12 | #b7000000
13 | #ff303e
14 | #fcfcfc
15 | #373737
16 | #E64E4E
17 | #4E5DE6
18 |
19 |
--------------------------------------------------------------------------------
/cutout/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CutOut
3 | Automatic background clearing
4 | Manual background clearing
5 | Zooming and Moving the picture
6 | Use the Magic Wand tool and touch parts of the background you want to erase
7 | Use the Pencil tool to erase the background dragging your finger around the screen
8 | Use the Magnifier tool to Zoom in/out so you can make better cuts
9 | Quick look
10 | Choose an image to cut
11 |
12 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Oct 23 15:53:16 PDT 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/images/Capture.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/images/Capture.JPG
--------------------------------------------------------------------------------
/images/Capture_2.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/images/Capture_2.JPG
--------------------------------------------------------------------------------
/images/Magic_Wand.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/images/Magic_Wand.JPG
--------------------------------------------------------------------------------
/images/Pencil.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/images/Pencil.JPG
--------------------------------------------------------------------------------
/images/Zoom.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/images/Zoom.JPG
--------------------------------------------------------------------------------
/images/before_app.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/images/before_app.jpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':test', ':cutout'
2 |
--------------------------------------------------------------------------------
/test/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /.externalNativeBuild
3 | /release
--------------------------------------------------------------------------------
/test/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | app
4 | Project app created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/test/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion rootProject.compileSdkVersion
5 | defaultConfig {
6 | applicationId rootProject.PUBLISH_GROUP_ID + "." + rootProject.PUBLISH_ARTIFACT_ID + ".test"
7 | minSdkVersion rootProject.minSdkVersion
8 | targetSdkVersion rootProject.compileSdkVersion
9 | versionCode rootProject.versionCode
10 | versionName rootProject.PUBLISH_VERSION
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 |
20 | compileOptions {
21 | sourceCompatibility rootProject.javaVersion
22 | targetCompatibility rootProject.javaVersion
23 | }
24 |
25 | lintOptions {
26 | abortOnError false
27 | }
28 | }
29 |
30 | dependencies {
31 | implementation fileTree(include: ['*.jar'], dir: 'libs')
32 | implementation 'com.android.support:appcompat-v7:28.0.0'
33 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
34 | implementation 'com.android.support:design:28.0.0'
35 | testImplementation 'junit:junit:4.12'
36 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
37 | androidTestImplementation 'com.android.support.test:rules:1.0.2'
38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
39 | implementation project(path: ':cutout')
40 | //implementation rootProject.PUBLISH_GROUP_ID + ":" + rootProject.PUBLISH_ARTIFACT_ID + ':' + rootProject.PUBLISH_VERSION
41 | }
42 |
--------------------------------------------------------------------------------
/test/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class android.support.v7.widget.** { *; }
--------------------------------------------------------------------------------
/test/src/androidTest/java/com/github/gabrielbb/cutout/MainActivityTest.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Bitmap;
5 | import android.net.Uri;
6 | import android.os.SystemClock;
7 | import android.support.test.rule.ActivityTestRule;
8 | import android.support.test.rule.GrantPermissionRule;
9 | import android.support.test.runner.AndroidJUnit4;
10 | import android.view.View;
11 | import android.widget.ImageView;
12 |
13 | import com.github.gabrielbb.cutout.test.MainActivity;
14 | import com.github.gabrielbb.cutout.test.R;
15 |
16 | import org.junit.Before;
17 | import org.junit.Rule;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 |
21 | import static android.Manifest.permission.CAMERA;
22 | import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
23 | import static android.support.test.espresso.Espresso.onView;
24 | import static android.support.test.espresso.action.ViewActions.click;
25 | import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
26 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
27 | import static org.hamcrest.CoreMatchers.instanceOf;
28 | import static org.junit.Assert.assertEquals;
29 | import static org.junit.Assert.assertFalse;
30 | import static org.junit.Assert.assertNotEquals;
31 | import static org.junit.Assert.assertThat;
32 | import static org.junit.Assert.assertTrue;
33 |
34 | /**
35 | * Instrumented test, which will execute on an Android device.
36 | *
37 | * @see Testing documentation
38 | */
39 | @RunWith(AndroidJUnit4.class)
40 | public class MainActivityTest {
41 |
42 | private ImageView imageView;
43 |
44 | @Rule
45 | public ActivityTestRule activityRule = new ActivityTestRule<>(MainActivity.class);
46 |
47 | @Rule
48 | public GrantPermissionRule permissionRule = GrantPermissionRule.grant(CAMERA, WRITE_EXTERNAL_STORAGE);
49 |
50 | @Before
51 | public void init() {
52 | this.imageView = activityRule.getActivity().findViewById(R.id.imageView);
53 | assertEquals(imageView.getTag(), activityRule.getActivity().getUriFromDrawable(R.drawable.image_icon));
54 |
55 | onView(withId(R.id.fab)).perform(click());
56 | }
57 |
58 | @Test
59 | public void testActivityShowUp() {
60 | assertThat(getCurrentActivity(), instanceOf(CutOutActivity.class));
61 | }
62 |
63 | @Test
64 | public void testSavingWithNoChange() {
65 | final Uri initialUri = (Uri) imageView.getTag();
66 |
67 | onView(withId(R.id.done)).perform(click());
68 |
69 | assertNotEquals(imageView.getTag(), initialUri);
70 | }
71 |
72 | public void testDefaultActivatedTool() {
73 | CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
74 |
75 | assertTrue(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
76 | }
77 |
78 |
79 | @Test
80 | public void testMagicToolButtonClick() {
81 | CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
82 |
83 | onView(withId(R.id.auto_clear_button)).perform(click());
84 | assertTrue(cutOutActivity.findViewById(R.id.auto_clear_button).isActivated());
85 | assertFalse(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
86 | assertFalse(cutOutActivity.findViewById(R.id.zoom_button).isActivated());
87 | }
88 |
89 | @Test
90 | public void testManualToolButtonClick() {
91 | CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
92 |
93 | onView(withId(R.id.manual_clear_button)).perform(click());
94 | assertTrue(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
95 | assertFalse(cutOutActivity.findViewById(R.id.auto_clear_button).isActivated());
96 | assertFalse(cutOutActivity.findViewById(R.id.zoom_button).isActivated());
97 | }
98 |
99 | @Test
100 | public void testZoomButtonClick() {
101 | CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
102 |
103 | onView(withId(R.id.zoom_button)).perform(click());
104 | assertTrue(cutOutActivity.findViewById(R.id.zoom_button).isActivated());
105 | assertFalse(cutOutActivity.findViewById(R.id.manual_clear_button).isActivated());
106 | assertFalse(cutOutActivity.findViewById(R.id.auto_clear_button).isActivated());
107 | }
108 |
109 | @Test
110 | public void testMagicToolEffect() {
111 | final Uri initialUri = (Uri) imageView.getTag();
112 |
113 | final CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
114 |
115 | testMagicToolButtonClick();
116 |
117 | final DrawView drawView = cutOutActivity.findViewById(R.id.drawView);
118 |
119 | final Bitmap initialBitmap = drawView.getCurrentBitmap();
120 |
121 | onView(withId(R.id.drawView)).perform(click());
122 |
123 | final View loadingModal = cutOutActivity.findViewById(R.id.loadingModal);
124 |
125 | do {
126 | SystemClock.sleep(2000);
127 | }
128 | while (loadingModal.getVisibility() == View.VISIBLE);
129 |
130 | assertFalse(initialBitmap.sameAs(drawView.getCurrentBitmap()));
131 |
132 | onView(withId(R.id.done)).perform(click());
133 |
134 | assertNotEquals(imageView.getTag(), initialUri);
135 | }
136 |
137 | @Test
138 | public void testUndoButton() {
139 | final Uri initialUri = (Uri) imageView.getTag();
140 |
141 | final CutOutActivity cutOutActivity = (CutOutActivity) getCurrentActivity();
142 |
143 | testMagicToolButtonClick();
144 |
145 | final DrawView drawView = cutOutActivity.findViewById(R.id.drawView);
146 |
147 | final Bitmap initialBitmap = drawView.getCurrentBitmap();
148 |
149 | onView(withId(R.id.drawView)).perform(click());
150 |
151 | final View loadingModal = cutOutActivity.findViewById(R.id.loadingModal);
152 |
153 | do {
154 | SystemClock.sleep(2000);
155 | }
156 | while (loadingModal.getVisibility() == View.VISIBLE);
157 |
158 | assertFalse(initialBitmap.sameAs(drawView.getCurrentBitmap()));
159 |
160 | onView(withId(R.id.undo)).perform(click());
161 |
162 | assertTrue(initialBitmap.sameAs(drawView.getCurrentBitmap()));
163 |
164 | onView(withId(R.id.redo)).perform(click());
165 |
166 | assertFalse(initialBitmap.sameAs(drawView.getCurrentBitmap()));
167 |
168 | onView(withId(R.id.done)).perform(click());
169 |
170 | assertNotEquals(imageView.getTag(), initialUri);
171 | }
172 |
173 | private Activity getCurrentActivity() {
174 | final Activity[] activity = new Activity[1];
175 | onView(isRoot()).check((view, noViewFoundException) -> activity[0] = (Activity) view.getContext());
176 | return activity[0];
177 | }
178 | }
--------------------------------------------------------------------------------
/test/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/src/main/java/com/github/gabrielbb/cutout/test/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.gabrielbb.cutout.test;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.graphics.Color;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.support.annotation.Nullable;
9 | import android.support.design.widget.FloatingActionButton;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.support.v7.widget.Toolbar;
12 | import android.widget.ImageView;
13 |
14 | import com.github.gabrielbb.cutout.CutOut;
15 |
16 | public class MainActivity extends AppCompatActivity {
17 |
18 | private ImageView imageView;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_main);
24 | Toolbar toolbar = findViewById(R.id.toolbar);
25 | setSupportActionBar(toolbar);
26 |
27 | imageView = findViewById(R.id.imageView);
28 |
29 | final Uri imageIconUri = getUriFromDrawable(R.drawable.image_icon);
30 | imageView.setImageURI(imageIconUri);
31 | imageView.setTag(imageIconUri);
32 |
33 | FloatingActionButton fab = findViewById(R.id.fab);
34 |
35 | fab.setOnClickListener(view -> {
36 | final Uri testImageUri = getUriFromDrawable(R.drawable.test_image);
37 |
38 | CutOut.activity()
39 | .src(testImageUri)
40 | .bordered()
41 | .noCrop()
42 | .start(this);
43 | });
44 |
45 | }
46 |
47 | @Override
48 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
49 | if (requestCode == CutOut.CUTOUT_ACTIVITY_REQUEST_CODE) {
50 |
51 | switch (resultCode) {
52 | case Activity.RESULT_OK:
53 | Uri imageUri = CutOut.getUri(data);
54 | // Save the image using the returned Uri here
55 | imageView.setImageURI(imageUri);
56 | imageView.setTag(imageUri);
57 | break;
58 | case CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE:
59 | Exception ex = CutOut.getError(data);
60 | break;
61 | default:
62 | System.out.print("User cancelled the CutOut screen");
63 | }
64 | }
65 | }
66 |
67 | public Uri getUriFromDrawable(int drawableId) {
68 | return Uri.parse("android.resource://" + getPackageName() + "/drawable/" + getApplicationContext().getResources().getResourceEntryName(drawableId));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/test/src/main/res/drawable/image_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/test/src/main/res/drawable/image_icon.png
--------------------------------------------------------------------------------
/test/src/main/res/drawable/test_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielBB/Android-CutOut/b2c81188e00d212d945a59e29ad2b2dce9b28e72/test/src/main/res/drawable/test_image.jpg
--------------------------------------------------------------------------------
/test/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
--------------------------------------------------------------------------------
/test/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
--------------------------------------------------------------------------------
/test/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/test/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Selfie Stickers for Whatsapp
3 | MainActivity
4 | Image
5 | ca-app-pub-3940256099942544/6300978111
6 |
7 |
--------------------------------------------------------------------------------
/test/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------