From 01eb77d89f8dcd34e77c6ab2b8dbbcc7ca65354e Mon Sep 17 00:00:00 2001 From: ASHIR14 Date: Thu, 27 Aug 2020 17:21:07 +0500 Subject: [PATCH] Initial commit --- .gitignore | 10 + .project | 17 + .settings/org.eclipse.buildship.core.prefs | 2 + .travis.yml | 28 ++ LICENSE.txt | 19 + README.md | 130 ++++++ build.gradle | 41 ++ cutout/.gitignore | 1 + cutout/build.gradle | 54 +++ cutout/proguard-rules.pro | 21 + cutout/publish.gradle | 106 +++++ cutout/src/main/AndroidManifest.xml | 20 + .../gabrielbb/cutout/BitmapUtility.java | 61 +++ .../com/github/gabrielbb/cutout/CutOut.java | 139 +++++++ .../gabrielbb/cutout/CutOutActivity.java | 379 ++++++++++++++++++ .../com/github/gabrielbb/cutout/DrawView.java | 304 ++++++++++++++ .../gabrielbb/cutout/IntroActivity.java | 59 +++ .../gabrielbb/cutout/SaveDrawingTask.java | 67 ++++ cutout/src/main/res/drawable/done.png | Bin 0 -> 218 bytes .../main/res/drawable/intro_magic_wand.png | Bin 0 -> 6015 bytes .../src/main/res/drawable/intro_magnifier.png | Bin 0 -> 11572 bytes cutout/src/main/res/drawable/intro_pencil.png | Bin 0 -> 5501 bytes cutout/src/main/res/drawable/magic_active.png | Bin 0 -> 363 bytes .../src/main/res/drawable/magic_inactive.png | Bin 0 -> 445 bytes .../src/main/res/drawable/magic_selector.xml | 5 + .../main/res/drawable/magnifier_active.png | Bin 0 -> 435 bytes .../main/res/drawable/magnifier_inactive.png | Bin 0 -> 531 bytes .../main/res/drawable/magnifier_selector.xml | 5 + .../src/main/res/drawable/pencil_active.png | Bin 0 -> 309 bytes .../src/main/res/drawable/pencil_inactive.png | Bin 0 -> 383 bytes .../src/main/res/drawable/pencil_selector.xml | 5 + cutout/src/main/res/drawable/redo_active.png | Bin 0 -> 352 bytes .../src/main/res/drawable/redo_inactive.png | Bin 0 -> 411 bytes .../src/main/res/drawable/redo_selector.xml | 5 + cutout/src/main/res/drawable/undo_active.png | Bin 0 -> 352 bytes .../src/main/res/drawable/undo_inactive.png | Bin 0 -> 412 bytes .../src/main/res/drawable/undo_selector.xml | 5 + .../main/res/layout/activity_photo_edit.xml | 279 +++++++++++++ cutout/src/main/res/values/colors.xml | 18 + cutout/src/main/res/values/strings.xml | 11 + gradle.properties | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 ++++++++ gradlew.bat | 90 +++++ images/Capture.JPG | Bin 0 -> 44685 bytes images/Capture_2.JPG | Bin 0 -> 40383 bytes images/Magic_Wand.JPG | Bin 0 -> 22457 bytes images/Pencil.JPG | Bin 0 -> 18984 bytes images/Zoom.JPG | Bin 0 -> 20720 bytes images/before_app.jpg | Bin 0 -> 2103570 bytes settings.gradle | 1 + test/.classpath | 6 + test/.gitignore | 3 + test/.project | 23 ++ .../org.eclipse.buildship.core.prefs | 2 + test/build.gradle | 41 ++ test/proguard-rules.pro | 22 + .../gabrielbb/cutout/MainActivityTest.java | 178 ++++++++ test/src/main/AndroidManifest.xml | 19 + .../gabrielbb/cutout/test/MainActivity.java | 70 ++++ test/src/main/res/drawable/image_icon.png | Bin 0 -> 7652 bytes test/src/main/res/drawable/test_image.jpg | Bin 0 -> 50218 bytes test/src/main/res/layout/activity_main.xml | 33 ++ test/src/main/res/layout/content_main.xml | 21 + test/src/main/res/values/dimens.xml | 3 + test/src/main/res/values/strings.xml | 6 + test/src/main/res/values/styles.xml | 13 + 68 files changed, 2505 insertions(+) create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.buildship.core.prefs create mode 100644 .travis.yml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 build.gradle create mode 100644 cutout/.gitignore create mode 100644 cutout/build.gradle create mode 100644 cutout/proguard-rules.pro create mode 100644 cutout/publish.gradle create mode 100644 cutout/src/main/AndroidManifest.xml create mode 100644 cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java create mode 100644 cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java create mode 100644 cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java create mode 100644 cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java create mode 100644 cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java create mode 100644 cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java create mode 100644 cutout/src/main/res/drawable/done.png create mode 100644 cutout/src/main/res/drawable/intro_magic_wand.png create mode 100644 cutout/src/main/res/drawable/intro_magnifier.png create mode 100644 cutout/src/main/res/drawable/intro_pencil.png create mode 100644 cutout/src/main/res/drawable/magic_active.png create mode 100644 cutout/src/main/res/drawable/magic_inactive.png create mode 100644 cutout/src/main/res/drawable/magic_selector.xml create mode 100644 cutout/src/main/res/drawable/magnifier_active.png create mode 100644 cutout/src/main/res/drawable/magnifier_inactive.png create mode 100644 cutout/src/main/res/drawable/magnifier_selector.xml create mode 100644 cutout/src/main/res/drawable/pencil_active.png create mode 100644 cutout/src/main/res/drawable/pencil_inactive.png create mode 100644 cutout/src/main/res/drawable/pencil_selector.xml create mode 100644 cutout/src/main/res/drawable/redo_active.png create mode 100644 cutout/src/main/res/drawable/redo_inactive.png create mode 100644 cutout/src/main/res/drawable/redo_selector.xml create mode 100644 cutout/src/main/res/drawable/undo_active.png create mode 100644 cutout/src/main/res/drawable/undo_inactive.png create mode 100644 cutout/src/main/res/drawable/undo_selector.xml create mode 100644 cutout/src/main/res/layout/activity_photo_edit.xml create mode 100644 cutout/src/main/res/values/colors.xml create mode 100644 cutout/src/main/res/values/strings.xml create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 images/Capture.JPG create mode 100644 images/Capture_2.JPG create mode 100644 images/Magic_Wand.JPG create mode 100644 images/Pencil.JPG create mode 100644 images/Zoom.JPG create mode 100644 images/before_app.jpg create mode 100644 settings.gradle create mode 100644 test/.classpath create mode 100644 test/.gitignore create mode 100644 test/.project create mode 100644 test/.settings/org.eclipse.buildship.core.prefs create mode 100644 test/build.gradle create mode 100644 test/proguard-rules.pro create mode 100644 test/src/androidTest/java/com/github/gabrielbb/cutout/MainActivityTest.java create mode 100644 test/src/main/AndroidManifest.xml create mode 100644 test/src/main/java/com/github/gabrielbb/cutout/test/MainActivity.java create mode 100644 test/src/main/res/drawable/image_icon.png create mode 100644 test/src/main/res/drawable/test_image.jpg create mode 100644 test/src/main/res/layout/activity_main.xml create mode 100644 test/src/main/res/layout/content_main.xml create mode 100644 test/src/main/res/values/dimens.xml create mode 100644 test/src/main/res/values/strings.xml create mode 100644 test/src/main/res/values/styles.xml 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 0000000000000000000000000000000000000000..c11e3a56652c41221a9c3920580db7ce1be4160c GIT binary patch literal 218 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 bMfmB2Uk`Y?aB8>kHEwI=U|Dn8_tt*^f0#fL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ccd9c13e4ed20c75b8bbd21ecf830c225f6936b6 GIT binary patch literal 11572 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 JPDHLkV1h$DrJVo( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..75079612237b252c0cde98def2823e745b0e03dd GIT binary patch literal 445 zcmV;u0Yd(XP)8$-Q9iDy2X{gUe@BKRV1MucZfKoDL4@#Zg~>g$0E)@yu-QY#o?QazUTbL z=RVJMV{5$L*|1zR;t-4*qYG4_9mf!{po4gVGtdPp9R>9Nj^HUyU_%A2VF0C4V%Y-n z`60w(1g|z)U_bsLpiAgopu&U5#|dnBHyl6|bct-#;xKf0o(i{8A{HSe!r&5a!idlC zTMcLQzWs_fFwo!M$4{Ju*_k72dKeFINN)vk6epA;HjjlU4C9ZycYc2t&FDe5=s}~c z@c#&676EyG5Jp@^21-%lkZL15+7TW$ghwzD8INav&Ca1!g$M8t2VwA3N}hw&d%-t6 z!DIADxF5f907iU5(0Q@4wa-K7#S_sY;rpS>#_AGQ4mi<|7dRtftG;Y+I9!DA0|v|f zAOA3hcd!y3l+O=fwU_OuI1Ts0ZC+&d!uP`W!tIaa3gv#d6i7nA&2U|y6ccWS>jGy` ngeh0U)j@a`)kwxc7`J8)|8=Xa0Kx$R00000NkvXXu0mjfOew~+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..136d3255448246e665e24018b959f5423c7d3756 GIT binary patch literal 435 zcmV;k0ZjghP)kdg0004cNkl;LGr{rQ5>1x;upjC$(A@A zVuKI2t5*NTD1ND3$AK|1S2CY2zx9B{atEDO3OT>&#)ntd8u@TbN^Kzlxaxe(<9G&|~ z5%3B>NeqrTZRi+-BtCh#hZ7?dPkGOehTiiOVSL0`RvB?i0v3rVlY{vp;@nZ;AMat_ dhy*!4Kpil-L%R9C^G*N&002ovPDHLkV1kOh#H#=R literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..db45b9042bcd6992b5f10b2bef1744f26bb20e6b GIT binary patch literal 531 zcmV+u0_^>XP)xD~d&ee3=>hAFJ#3A)&lE}mjdHOKz+71}gpKjsji50{{?+Bx;+`(QbX6%64r z8mjlKW@Y|x4;vW48QbXMt911@XN)w%OC3Um=g^7Ry|>|`4Nuxw&|27G+R)6@$9x6O zc!Vh&u;1q0JMA^rFr`{&tY~&(fH@3I^F3VAdYZ#`Sv~F#&76;&q08rC$Tz=|_87i- zqY(6j?1>JJ#08i_&m8k<4a|HMskd+T!bDtvRg?G@4kGWRd=R(H%q6XbHa#(ykdId0 z0OFfVIl%s?09`ogqK%?=(lIml1x`c-*n*R<@K7{>r&&%u;9}DOE=2|SWe&kueTSg_ zld!hS@V1$GU2Bcpi24|B@>cv1M^O30=t{`LsD~M?H}W`sh{LG=g+zX+Z#QatC9{5| z3ZY$E^rf_m2l$>nMEGlY$DW*FR1;tZ2jSyap%8Ke6Ieok7x*AA5NK^&wJq6QEJS#i z{WytBauWM>=xk5%5XH9iAt-Gu#1Aa0w2=^7SXWtXAr|lx+qi*~DycQZBGz#Vt=|m8 VQD6Im_6q<2002ovPDHLkV1lkA{HOo` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..328db1bc9ed434ff105224492beb41167c78ea60 GIT binary patch literal 309 zcmV-50m}Y~P)kdg0002|NkloVh1XAG+Ee;)2*f!GPun}Ynsc>&`_Xdf(&V9k*zx8)!5U-O{_NhSQ zYB|J$g>}l%v=X+kIBrv#(4hmMj7@n0bhOitF(9!{g(~mljC+QH5__ur6blyaf)XbJ z9+cSPkWK*)N|aEGPQrr|sPW*tXF$S(?~?9V_yv9FzbF6aP#GJC0+zIYUhhgQXnQ@I zQ4)g^g3q_|z!dR82}KU$Sa=CaDDyN{ummI))j8Dhh$SFVL@fpsXL50OA_37-F=O$$ zM@G>hSR=PC`os$3w33BrGg!F8jEP#(&>`Lub2N~O4o$NH(WW5jjhg?l00000NkvXX Hu0mjfka>b# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6c33a4763c2fea16cdcafecc597aacedf4ca49f3 GIT binary patch literal 383 zcmV-_0f7FAP)zSZHs+p8R{ zGc{$L$w|YbDa9J*kqQTn>Y1Lt8bKx&VXloMDKJ7iARd!20VcE}Zbn_#9Dp%KqeW

uQAaQkSKA~0urd==)}o)m!=)CN* zebmE&!nkY`6+(d8_y(%sejSf_UdLa@uP>osQq{+WfPWZxh?m`iz=(eB@xXweXu*3S zLhWTQ!~weSR*0}LetiOl+F)>kP9eg|cwoR648sT!1K6}R9vF~?Ll`0AKQiHg)yrug zOn|_MF1&*Sm3z3)@WVuLh!(iC0&=kn6W|ELXh$J_Xubm7FxZ11K4jsGwP!5^45Amg d_$-WCKj+~USb`9E=+yuK002ovPDHLkV1hJKqb2|V literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9b06719e9ace42fc35cf967dcaacffb08d2104cf GIT binary patch literal 352 zcmV-m0iXVfP)kdg0003eNklSJN zPLpr;;4}M}#wfFVckrHcy%9?Ru!l_jwjBpHG71r^P|zqcFqdHcrk=-4M4DLyiVVEg zH>{aEj6s@7d=?a$ZRF^?{he`kzkd-n7=^wx2`n7KcO@jyjgl^|Ax1y6lHg`TLe98%d;Pr!5JL2M|( z7tDfE4pdaW6kZS&lJOh2aT5D*4A<}t@hE{PLhuT?5IwbgCBh(zV4TJ@bb<@&u^liO z$Kaz3sg(W%(+~jB$;Ca4g$qW%m|?hxbfW`$<1<7j6}!<6R=)G@cyDyTQRS9pVhdVW zEN_pq$TKov3WAhx`aD|OEYCOk4qS%l{6oK5EmsD(p%8)stc3=%oT%Xzoefj_Z002ovPDHLk FV1f#tvo`<$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6ebbe5460d929ac2ef11d13871940a43ae7d34fd GIT binary patch literal 352 zcmV-m0iXVfP)kdg0003eNkl5K?9Qn_8EO8~3RwYR`*EI!- zjFHcOPAJ{WES7N&Ys%zf%DJsg(20ay2r-L;c+f8$SI8M*K_sit&O$!ZE*%-oS|Pyz z)+|Chb9h6y^l^V{3ZyThQ!x*F=?^L47>t{s*hq%-x+Q4+GIlIRJI@$moMAGcRxou` y@_{*MV*}y%@POU0S4;M>Q-@#{E0~Ljh?EI~tI9VTEQ)?+KyVID@KB`QOF{wz%eNW@}4Y9glxZi&UX zjem$j5~M*ABJdN}F$0YtkY50lh;)sx8lR8=X+bRBV*zSIAkP3bVmO}IcwkiwZlWgy z@(2K;4KKA+B*7QYa01)09=mWBuMr3d)fY^HfD(|uBz(hm^hHew7(Xi&P#c4=2Y(?I zE|>-Z(*dD4jP`21IwhtXE+87x;1>o%z*N8kG=SBww?K8QK`^AjV>B@v5TkrWtwvPD zQiLmS>9%j*%me_j4kN1Ipz_=C2YuCm!i;E+cgm}9RM_}&If$CD768mc93Rgza6n!uN0000 + + + + 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 @@ + + + + + + + + + +