diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..564a3ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.gradle +.DS_Store +.idea +build/ +local.properties +localhost/ +obj/ +*.iml +Gemfile.lock +_site/ \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..d278faf --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Android + Project Android created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..e889521 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..66d37dc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +language: android +jdk: oraclejdk8 +sudo: false + +android: + + components: + - tools + - platform-tools + - build-tools-28.0.3 + - android-28 + - android-22 + - extra-google-google_play_services + - extra-google-m2repository + - extra-android-m2repository + - sys-img-armeabi-v7a-android-22 + +before_script: + - echo yes | android update sdk --all --filter build-tools-28.0.3 --no-ui --force + - touch local.properties + - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a -c 100M + - emulator -avd test -no-audio -no-window & + - android-wait-for-emulator + - adb shell input keyevent 82 & + - chmod +x gradlew + +script: + - ./gradlew clean build connectedCheck diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e2fc278 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2018 Gabriel Basilio Brito + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to use, +copy, modify, merge, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1 - You cannot use this software to make an Android Backgroung Removal App and publish it to the Google Play Store, but you can use it to integrate the functionality in an existing app. + +2 - The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..22311b2 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +
+ +

+ ✂️ + Android-CutOut +

+ Android image background cutting library +
+ +[ ![Version](https://api.bintray.com/packages/gabrielbb/Android-CutOut/Android-CutOut/images/download.svg) ](https://bintray.com/gabrielbb/Android-CutOut/Android-CutOut/_latestVersion) +[ ![Build](https://api.travis-ci.org/GabrielBB/Android-CutOut.svg?branch=master) ](https://api.travis-ci.org/GabrielBB/Android-CutOut.svg?branch=master) + +## Usage + +Add Gradle dependency: +```groovy +implementation 'com.github.gabrielbb:cutout:0.1.2' +``` + +Start the CutOut screen with this single line: + +```java +CutOut.activity().start(this); +``` + +   + +### Getting the result + +```java +@Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (requestCode == CutOut.CUTOUT_ACTIVITY_REQUEST_CODE) { + + switch (resultCode) { + case Activity.RESULT_OK: + Uri imageUri = CutOut.getUri(data); + // Save the image using the returned Uri here + break; + case CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE: + Exception ex = CutOut.getError(data); + break; + default: + System.out.print("User cancelled the CutOut screen"); + } + } + } +``` + +## Features + +     + + +## Options + +You can use one or more options from these: + +```java + CutOut.activity() + .src(uri) + .bordered() + .noCrop() + .intro() + .start(this); +``` + + - #### src + +By default the user can select images from camera or gallery but you can also pass an `android.net.Uri` of an image that is already saved: + + ```java +Uri uri = Uri.parse("/images/cat.jpg"); + +CutOut.activity().src(uri).start(this); +``` + + + - #### bordered + + ```java +CutOut.activity().bordered().start(this); +``` + +This option makes the final PNG have a border around it. The default border color is White. You can also pass the `android.graphics.Color` of your choice. + + + - #### noCrop + + ```java +CutOut.activity().noCrop().start(this); +``` + +By default and thanks to this library: [Android-Image-Cropper](https://github.com/ArthurHub/Android-Image-Cropper), the user can crop or rotate the image. This option disables that cropping screen. + + + + - #### intro + + ```java +CutOut.activity().intro().start(this); +``` + +Display an intro explaining every button usage. The user can skip the intro and it is only shown once. The images displayed in the intro are the same you saw in the "Features" section of this document. + +## Change log +*0.1.2* +- Removed Admob Ads automatic integration. I will probably add it later. For now, it was causing problems. +- Images are now saved as temporary files in the cache directory. This guarantees that these images will be deleted when users uninstall your app or when the disk memory is low. If you want the images to live forever on the Gallery, you should take the returned Uri and save the image there by yourself. + +## License +Copyright (c) 2018 Gabriel Basilio Brito + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to use, +copy, modify, merge, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +1 - You cannot use this software to make an Android app that its main goal is to remove background from images and publish it to the Google Play Store, but you can use it to integrate the functionality in an existing app or a new app that does more than just this. + +2 - The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d856e4a --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + maven { + url 'https://jitpack.io' + } + maven { url 'https://dl.bintray.com/gabrielbb/Android-CutOut' } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext { + compileSdkVersion = 28 + minSdkVersion = 16 + buildToolsVersion = '28.0.3' + versionCode = 2 + javaVersion = JavaVersion.VERSION_1_8 + PUBLISH_GROUP_ID = 'com.github.gabrielbb' + PUBLISH_ARTIFACT_ID = 'cutout' + PUBLISH_VERSION = '0.1.2' +} \ No newline at end of file diff --git a/cutout/.gitignore b/cutout/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/cutout/.gitignore @@ -0,0 +1 @@ +/build diff --git a/cutout/build.gradle b/cutout/build.gradle new file mode 100644 index 0000000..fa0c312 --- /dev/null +++ b/cutout/build.gradle @@ -0,0 +1,54 @@ +apply plugin: 'com.android.library' +apply from: 'publish.gradle' + +android { + compileSdkVersion rootProject.compileSdkVersion + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.compileSdkVersion + versionCode rootProject.versionCode + versionName rootProject.PUBLISH_VERSION + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility rootProject.javaVersion + targetCompatibility rootProject.javaVersion + } + + lintOptions { + abortOnError false + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.github.duanhong169:checkerboarddrawable:1.0.2' + implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0' + implementation 'com.alexvasilkov:gesture-views:2.5.2' + implementation 'com.github.apl-devs:appintro:v4.2.3' + implementation 'com.github.jkwiecien:EasyImage:1.3.1' + + implementation 'com.intuit.sdp:sdp-android:1.0.6' +} + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' + } +} diff --git a/cutout/proguard-rules.pro b/cutout/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/cutout/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/cutout/publish.gradle b/cutout/publish.gradle new file mode 100644 index 0000000..eaa821e --- /dev/null +++ b/cutout/publish.gradle @@ -0,0 +1,106 @@ +apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'com.jfrog.bintray' + +android { + compileSdkVersion rootProject.compileSdkVersion +} + + +ext { + libraryName = 'Android-CutOut' + bintrayRepo = libraryName + bintrayName = bintrayRepo + libraryDescription = 'Android image background removing library' + + gitUrl = 'https://github.com/GabrielBB/Android-CutOut.git' + siteUrl = gitUrl + + developerId = 'gabrielbb' + developerName = 'Gabriel Basilio Brito' + developerEmail = 'gabrielbb0306@gmail.com' + + licenseName = 'The Apache Software License, Version 2.0' + licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + allLicenses = ["Apache-2.0"] +} + +group = rootProject.PUBLISH_GROUP_ID +version = rootProject.PUBLISH_VERSION + +install { + repositories.mavenInstaller { + pom.project { + packaging 'aar' + groupId rootProject.PUBLISH_GROUP_ID + artifactId rootProject.PUBLISH_ARTIFACT_ID + + name libraryName + description libraryDescription + url siteUrl + + licenses { + license { + name licenseName + url licenseUrl + } + } + developers { + developer { + id developerId + name developerName + email developerEmail + } + } + scm { + connection gitUrl + developerConnection gitUrl + url siteUrl + } + } + } +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.srcDirs +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar +} + +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + +bintray { + user = properties.getProperty("bintray.user") + key = properties.getProperty("bintray.apikey") + + configurations = ['archives'] + pkg { + repo = bintrayRepo + name = bintrayName + desc = libraryDescription + websiteUrl = siteUrl + vcsUrl = gitUrl + licenses = allLicenses + dryRun = false + publish = true + override = false + publicDownloadNumbers = true + version { + desc = libraryDescription + } + } +} \ No newline at end of file diff --git a/cutout/src/main/AndroidManifest.xml b/cutout/src/main/AndroidManifest.xml new file mode 100644 index 0000000..03a3126 --- /dev/null +++ b/cutout/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java b/cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java new file mode 100644 index 0000000..3aeacb6 --- /dev/null +++ b/cutout/src/main/java/com/github/gabrielbb/cutout/BitmapUtility.java @@ -0,0 +1,61 @@ +package com.github.gabrielbb.cutout; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; + +class BitmapUtility { + + static Bitmap getResizedBitmap(Bitmap bitmap, int width, int height) { + Bitmap background = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + float originalWidth = bitmap.getWidth(); + float originalHeight = bitmap.getHeight(); + + Canvas canvas = new Canvas(background); + + float scale = width / originalWidth; + + float xTranslation = 0.0f; + float yTranslation = (height - originalHeight * scale) / 2.0f; + + Matrix transformation = new Matrix(); + transformation.postTranslate(xTranslation, yTranslation); + transformation.preScale(scale, scale); + + Paint paint = new Paint(); + paint.setFilterBitmap(true); + + canvas.drawBitmap(bitmap, transformation, paint); + + return background; + } + + static Bitmap getBorderedBitmap(Bitmap image, int borderColor, int borderSize) { + + // Creating a canvas with an empty bitmap, this is the bitmap that gonna store the final canvas changes + Bitmap finalImage = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(finalImage); + + // Make a smaller copy of the image to draw on top of original + Bitmap imageCopy = Bitmap.createScaledBitmap(image, image.getWidth() - borderSize, image.getHeight() - borderSize, true); + + // Let's draw the bigger image using a white paint brush + Paint paint = new Paint(); + paint.setColorFilter(new PorterDuffColorFilter(borderColor, PorterDuff.Mode.SRC_ATOP)); + canvas.drawBitmap(image, 0, 0, paint); + + int width = image.getWidth(); + int height = image.getHeight(); + float centerX = (width - imageCopy.getWidth()) * 0.5f; + float centerY = (height - imageCopy.getHeight()) * 0.5f; + // Now let's draw the original image on top of the white image, passing a null paint because we want to keep it original + canvas.drawBitmap(imageCopy, centerX, centerY, null); + + // Returning the image with the final results + return finalImage; + } +} diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java new file mode 100644 index 0000000..7c169e4 --- /dev/null +++ b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOut.java @@ -0,0 +1,139 @@ +package com.github.gabrielbb.cutout; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class CutOut { + + public static final short CUTOUT_ACTIVITY_REQUEST_CODE = 368; + public static final short CUTOUT_ACTIVITY_RESULT_ERROR_CODE = 3680; + + static final String CUTOUT_EXTRA_SOURCE = "CUTOUT_EXTRA_SOURCE"; + static final String CUTOUT_EXTRA_RESULT = "CUTOUT_EXTRA_RESULT"; + static final String CUTOUT_EXTRA_BORDER_COLOR = "CUTOUT_EXTRA_BORDER_COLOR"; + static final String CUTOUT_EXTRA_CROP = "CUTOUT_EXTRA_CROP"; + static final String CUTOUT_EXTRA_INTRO = "CUTOUT_EXTRA_INTRO"; + + public static ActivityBuilder activity() { + return new ActivityBuilder(); + } + + /** + * Builder used for creating CutOut Activity by user request. + */ + public static final class ActivityBuilder { + + @Nullable + private Uri source; // The image to crop source Android uri + private boolean bordered; + private boolean crop = true; // By default the cropping activity is started + private boolean intro; + private int borderColor = Color.WHITE; // Default border color is no border color is passed + + private ActivityBuilder() { + + } + + /** + * Get {@link CutOutActivity} intent to start the activity. + */ + private Intent getIntent(@NonNull Context context) { + Intent intent = new Intent(); + intent.setClass(context, CutOutActivity.class); + + if (source != null) { + intent.putExtra(CUTOUT_EXTRA_SOURCE, source); + } + + if (bordered) { + intent.putExtra(CUTOUT_EXTRA_BORDER_COLOR, borderColor); + } + + if (crop) { + intent.putExtra(CUTOUT_EXTRA_CROP, true); + } + + if (intro) { + intent.putExtra(CUTOUT_EXTRA_INTRO, true); + } + + return intent; + } + + /** + * By default the user can select images from camera or gallery but you can also call this method to load a pre-saved image + * + * @param source {@link android.net.Uri} instance of the image to be loaded + */ + public ActivityBuilder src(Uri source) { + this.source = source; + return this; + } + + /** + * This method adds a white border around the final PNG image + */ + public ActivityBuilder bordered() { + this.bordered = true; + return this; + } + + /** + * This method adds a border around the final PNG image + * + * @param borderColor The border color. You can pass any {@link android.graphics.Color} + */ + public ActivityBuilder bordered(int borderColor) { + this.borderColor = borderColor; + return bordered(); + } + + /** + * Disables the cropping screen shown before the background removal screen + */ + public ActivityBuilder noCrop() { + this.crop = false; + return this; + } + + /** + * Shows an introduction to the activity, explaining every button usage. The intro is show only once. + */ + public ActivityBuilder intro() { + this.intro = true; + return this; + } + + /** + * Start {@link CutOutActivity}. + * + * @param activity activity to receive result + */ + public void start(@NonNull Activity activity) { + activity.startActivityForResult(getIntent(activity), CUTOUT_ACTIVITY_REQUEST_CODE); + } + } + + /** + * Reads the {@link android.net.Uri} from the result data. This Uri is the path to the saved PNG + * + * @param data Result data to get the Uri from + */ + public static Uri getUri(@Nullable Intent data) { + return data != null ? data.getParcelableExtra(CUTOUT_EXTRA_RESULT) : null; + } + + /** + * Gets an Exception from the result data if the {@link CutOutActivity} failed at some point + * + * @param data Result data to get the Exception from + */ + public static Exception getError(@Nullable Intent data) { + return data != null ? (Exception) data.getSerializableExtra(CUTOUT_EXTRA_RESULT) : null; + } +} diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java new file mode 100644 index 0000000..c09aa84 --- /dev/null +++ b/cutout/src/main/java/com/github/gabrielbb/cutout/CutOutActivity.java @@ -0,0 +1,379 @@ +package com.github.gabrielbb.cutout; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.SeekBar; + +import com.alexvasilkov.gestures.views.interfaces.GestureView; +import com.theartofdev.edmodo.cropper.CropImage; +import com.theartofdev.edmodo.cropper.CropImageView; + +import java.io.File; +import java.io.IOException; + +import pl.aprilapps.easyphotopicker.DefaultCallback; +import pl.aprilapps.easyphotopicker.EasyImage; +import top.defaults.checkerboarddrawable.CheckerboardDrawable; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static com.github.gabrielbb.cutout.CutOut.CUTOUT_EXTRA_INTRO; + +public class CutOutActivity extends AppCompatActivity { + + private static final int INTRO_REQUEST_CODE = 4; + private static final int WRITE_EXTERNAL_STORAGE_CODE = 1; + private static final int IMAGE_CHOOSER_REQUEST_CODE = 2; + private static final int CAMERA_REQUEST_CODE = 3; + + private static final String INTRO_SHOWN = "INTRO_SHOWN"; + FrameLayout loadingModal; + private GestureView gestureView; + private DrawView drawView; + private LinearLayout manualClearSettingsLayout; + + private static final short MAX_ERASER_SIZE = 150; + private static final short BORDER_SIZE = 45; + private static final float MAX_ZOOM = 4F; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_photo_edit); + + Toolbar toolbar = findViewById(R.id.photo_edit_toolbar); + //toolbar.setBackgroundColor(Color.BLACK); + //toolbar.setTitleTextColor(Color.WHITE); + setSupportActionBar(toolbar); + + FrameLayout drawViewLayout = findViewById(R.id.drawViewLayout); + + int sdk = android.os.Build.VERSION.SDK_INT; + + if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { + drawViewLayout.setBackgroundDrawable(CheckerboardDrawable.create()); + } else { + drawViewLayout.setBackground(CheckerboardDrawable.create()); + } + + SeekBar strokeBar = findViewById(R.id.strokeBar); + strokeBar.setMax(MAX_ERASER_SIZE); + strokeBar.setProgress(50); + + gestureView = findViewById(R.id.gestureView); + + drawView = findViewById(R.id.drawView); + drawView.setDrawingCacheEnabled(true); + drawView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + //drawView.setDrawingCacheEnabled(true); + drawView.setStrokeWidth(strokeBar.getProgress()); + + strokeBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + drawView.setStrokeWidth(seekBar.getProgress()); + } + }); + + loadingModal = findViewById(R.id.loadingModal); + loadingModal.setVisibility(INVISIBLE); + + drawView.setLoadingModal(loadingModal); + + manualClearSettingsLayout = findViewById(R.id.manual_clear_settings_layout); + + setUndoRedo(); + initializeActionButtons(); + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(false); + + if (toolbar.getNavigationIcon() != null) { + toolbar.getNavigationIcon().setColorFilter(getResources().getColor(R.color.white), PorterDuff.Mode.SRC_ATOP); + } + + } + + Button doneButton = findViewById(R.id.done); + + doneButton.setOnClickListener(v -> startSaveDrawingTask()); + + if (getIntent().getBooleanExtra(CUTOUT_EXTRA_INTRO, false) && !getPreferences(Context.MODE_PRIVATE).getBoolean(INTRO_SHOWN, false)) { + Intent intent = new Intent(this, IntroActivity.class); + startActivityForResult(intent, INTRO_REQUEST_CODE); + } else { + start(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + // Respond to the action bar's Up/Home button + case android.R.id.home: + setResult(RESULT_CANCELED); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private Uri getExtraSource() { + return getIntent().hasExtra(CutOut.CUTOUT_EXTRA_SOURCE) ? (Uri) getIntent().getParcelableExtra(CutOut.CUTOUT_EXTRA_SOURCE) : null; + } + + private void start() { + if (ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + + Uri uri = getExtraSource(); + + if (getIntent().getBooleanExtra(CutOut.CUTOUT_EXTRA_CROP, false)) { + + CropImage.ActivityBuilder cropImageBuilder; + if (uri != null) { + cropImageBuilder = CropImage.activity(uri); + } else { + if (ContextCompat.checkSelfPermission(this, + Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + + cropImageBuilder = CropImage.activity(); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.CAMERA}, + CAMERA_REQUEST_CODE); + return; + } + } + + cropImageBuilder = cropImageBuilder.setGuidelines(CropImageView.Guidelines.ON); + cropImageBuilder.start(this); + } else { + if (uri != null) { + setDrawViewBitmap(uri); + } else { + if (ContextCompat.checkSelfPermission(this, + Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { + + EasyImage.openChooserWithGallery(this, getString(R.string.image_chooser_message), IMAGE_CHOOSER_REQUEST_CODE); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.CAMERA}, + CAMERA_REQUEST_CODE); + } + } + } + + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + WRITE_EXTERNAL_STORAGE_CODE); + } + } + + private void startSaveDrawingTask() { + SaveDrawingTask task = new SaveDrawingTask(this); + + int borderColor; + if ((borderColor = getIntent().getIntExtra(CutOut.CUTOUT_EXTRA_BORDER_COLOR, -1)) != -1) { + Bitmap image = BitmapUtility.getBorderedBitmap(this.drawView.getDrawingCache(), borderColor, BORDER_SIZE); + task.execute(image); + } else { + task.execute(this.drawView.getDrawingCache()); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String permissions[], @NonNull int[] grantResults) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + start(); + } else { + setResult(Activity.RESULT_CANCELED); + finish(); + } + } + + private void activateGestureView() { + gestureView.getController().getSettings() + .setMaxZoom(MAX_ZOOM) + .setDoubleTapZoom(-1f) // Falls back to max zoom level + .setPanEnabled(true) + .setZoomEnabled(true) + .setDoubleTapEnabled(true) + .setOverscrollDistance(0f, 0f) + .setOverzoomFactor(2f); + } + + private void deactivateGestureView() { + gestureView.getController().getSettings() + .setPanEnabled(false) + .setZoomEnabled(false) + .setDoubleTapEnabled(false); + } + + private void initializeActionButtons() { + Button autoClearButton = findViewById(R.id.auto_clear_button); + Button manualClearButton = findViewById(R.id.manual_clear_button); + Button zoomButton = findViewById(R.id.zoom_button); + + autoClearButton.setActivated(false); + autoClearButton.setOnClickListener((buttonView) -> { + if (!autoClearButton.isActivated()) { + drawView.setAction(DrawView.DrawViewAction.AUTO_CLEAR); + manualClearSettingsLayout.setVisibility(INVISIBLE); + autoClearButton.setActivated(true); + manualClearButton.setActivated(false); + zoomButton.setActivated(false); + deactivateGestureView(); + } + }); + + manualClearButton.setActivated(true); + drawView.setAction(DrawView.DrawViewAction.MANUAL_CLEAR); + manualClearButton.setOnClickListener((buttonView) -> { + if (!manualClearButton.isActivated()) { + drawView.setAction(DrawView.DrawViewAction.MANUAL_CLEAR); + manualClearSettingsLayout.setVisibility(VISIBLE); + manualClearButton.setActivated(true); + autoClearButton.setActivated(false); + zoomButton.setActivated(false); + deactivateGestureView(); + } + + }); + + zoomButton.setActivated(false); + deactivateGestureView(); + zoomButton.setOnClickListener((buttonView) -> { + if (!zoomButton.isActivated()) { + drawView.setAction(DrawView.DrawViewAction.ZOOM); + manualClearSettingsLayout.setVisibility(INVISIBLE); + zoomButton.setActivated(true); + manualClearButton.setActivated(false); + autoClearButton.setActivated(false); + activateGestureView(); + } + + }); + } + + private void setUndoRedo() { + Button undoButton = findViewById(R.id.undo); + undoButton.setEnabled(false); + undoButton.setOnClickListener(v -> undo()); + Button redoButton = findViewById(R.id.redo); + redoButton.setEnabled(false); + redoButton.setOnClickListener(v -> redo()); + + drawView.setButtons(undoButton, redoButton); + } + + void exitWithError(Exception e) { + Intent intent = new Intent(); + intent.putExtra(CutOut.CUTOUT_EXTRA_RESULT, e); + setResult(CutOut.CUTOUT_ACTIVITY_RESULT_ERROR_CODE, intent); + finish(); + } + + private void setDrawViewBitmap(Uri uri) { + try { + Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri); + drawView.setBitmap(bitmap); + } catch (IOException e) { + exitWithError(e); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + + if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) { + + CropImage.ActivityResult result = CropImage.getActivityResult(data); + + if (resultCode == Activity.RESULT_OK) { + + setDrawViewBitmap(result.getUri()); + + } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) { + exitWithError(result.getError()); + } else { + setResult(Activity.RESULT_CANCELED); + finish(); + } + } else if (requestCode == INTRO_REQUEST_CODE) { + SharedPreferences.Editor editor = getPreferences(Context.MODE_PRIVATE).edit(); + editor.putBoolean(INTRO_SHOWN, true); + editor.apply(); + start(); + } else { + EasyImage.handleActivityResult(requestCode, resultCode, data, this, new DefaultCallback() { + @Override + public void onImagePickerError(Exception e, EasyImage.ImageSource source, int type) { + exitWithError(e); + } + + @Override + public void onImagePicked(File imageFile, EasyImage.ImageSource source, int type) { + setDrawViewBitmap(Uri.parse(imageFile.toURI().toString())); + } + + @Override + public void onCanceled(EasyImage.ImageSource source, int type) { + // Cancel handling, removing taken photo if it was canceled + if (source == EasyImage.ImageSource.CAMERA) { + File photoFile = EasyImage.lastlyTakenButCanceledPhoto(CutOutActivity.this); + if (photoFile != null) photoFile.delete(); + } + + setResult(RESULT_CANCELED); + finish(); + } + }); + } + } + + private void undo() { + drawView.undo(); + } + + private void redo() { + drawView.redo(); + } + +} \ No newline at end of file diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java b/cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java new file mode 100644 index 0000000..cf78880 --- /dev/null +++ b/cutout/src/main/java/com/github/gabrielbb/cutout/DrawView.java @@ -0,0 +1,304 @@ +package com.github.gabrielbb.cutout; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.os.AsyncTask; +import android.util.AttributeSet; +import android.util.Pair; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; + +import java.lang.ref.WeakReference; +import java.util.Stack; + +import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.AUTO_CLEAR; +import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.MANUAL_CLEAR; +import static com.github.gabrielbb.cutout.DrawView.DrawViewAction.ZOOM; + +class DrawView extends View { + + private Path livePath; + private Paint pathPaint; + + private Bitmap imageBitmap; + private final Stack, Bitmap>> cuts = new Stack<>(); + private final Stack, Bitmap>> undoneCuts = new Stack<>(); + + private float pathX, pathY; + + private static final float TOUCH_TOLERANCE = 4; + private static final float COLOR_TOLERANCE = 20; + + private Button undoButton; + private Button redoButton; + + private View loadingModal; + + private DrawViewAction currentAction; + + public enum DrawViewAction { + AUTO_CLEAR, + MANUAL_CLEAR, + ZOOM + } + + public DrawView(Context c, AttributeSet attrs) { + + super(c, attrs); + + livePath = new Path(); + + pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + pathPaint.setDither(true); + pathPaint.setColor(Color.TRANSPARENT); + pathPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + pathPaint.setStyle(Paint.Style.STROKE); + pathPaint.setStrokeJoin(Paint.Join.ROUND); + pathPaint.setStrokeCap(Paint.Cap.ROUND); + } + + public void setButtons(Button undoButton, Button redoButton) { + this.undoButton = undoButton; + this.redoButton = redoButton; + } + + + @Override + protected void onSizeChanged(int newWidth, int newHeight, int oldWidth, int oldHeight) { + super.onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); + + resizeBitmap(newWidth, newHeight); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.save(); + + if (imageBitmap != null) { + + canvas.drawBitmap(this.imageBitmap, 0, 0, null); + + for (Pair, Bitmap> action : cuts) { + if (action.first != null) { + canvas.drawPath(action.first.first, action.first.second); + } + } + + if (currentAction == MANUAL_CLEAR) { + canvas.drawPath(livePath, pathPaint); + } + } + + canvas.restore(); + } + + private void touchStart(float x, float y) { + pathX = x; + pathY = y; + + undoneCuts.clear(); + redoButton.setEnabled(false); + + if (currentAction == AUTO_CLEAR) { + new AutomaticPixelClearingTask(this).execute((int) x, (int) y); + } else { + livePath.moveTo(x, y); + } + + invalidate(); + } + + private void touchMove(float x, float y) { + if (currentAction == MANUAL_CLEAR) { + float dx = Math.abs(x - pathX); + float dy = Math.abs(y - pathY); + if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { + livePath.quadTo(pathX, pathY, (x + pathX) / 2, (y + pathY) / 2); + pathX = x; + pathY = y; + } + } + } + + + private void touchUp() { + if (currentAction == MANUAL_CLEAR) { + livePath.lineTo(pathX, pathY); + cuts.push(new Pair<>(new Pair<>(livePath, pathPaint), null)); + livePath = new Path(); + undoButton.setEnabled(true); + } + } + + public void undo() { + if (cuts.size() > 0) { + + Pair, Bitmap> cut = cuts.pop(); + + if (cut.second != null) { + undoneCuts.push(new Pair<>(null, imageBitmap)); + this.imageBitmap = cut.second; + } else { + undoneCuts.push(cut); + } + + if (cuts.isEmpty()) { + undoButton.setEnabled(false); + } + + redoButton.setEnabled(true); + + invalidate(); + } + //toast the user + } + + public void redo() { + if (undoneCuts.size() > 0) { + + Pair, Bitmap> cut = undoneCuts.pop(); + + if (cut.second != null) { + cuts.push(new Pair<>(null, imageBitmap)); + this.imageBitmap = cut.second; + } else { + cuts.push(cut); + } + + if (undoneCuts.isEmpty()) { + redoButton.setEnabled(false); + } + + undoButton.setEnabled(true); + + invalidate(); + } + //toast the user + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + + if (imageBitmap != null && currentAction != ZOOM) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + touchStart(ev.getX(), ev.getY()); + return true; + case MotionEvent.ACTION_MOVE: + touchMove(ev.getX(), ev.getY()); + invalidate(); + return true; + case MotionEvent.ACTION_UP: + touchUp(); + invalidate(); + return true; + } + } + + return super.onTouchEvent(ev); + } + + private void resizeBitmap(int width, int height) { + if (width > 0 && height > 0 && imageBitmap != null) { + imageBitmap = BitmapUtility.getResizedBitmap(this.imageBitmap, width, height); + imageBitmap.setHasAlpha(true); + invalidate(); + } + } + + public void setBitmap(Bitmap bitmap) { + this.imageBitmap = bitmap; + resizeBitmap(getWidth(), getHeight()); + } + + public Bitmap getCurrentBitmap() { + return this.imageBitmap; + } + + public void setAction(DrawViewAction newAction) { + this.currentAction = newAction; + } + + public void setStrokeWidth(int strokeWidth) { + pathPaint = new Paint(pathPaint); + pathPaint.setStrokeWidth(strokeWidth); + } + + public void setLoadingModal(View loadingModal) { + this.loadingModal = loadingModal; + } + + private static class AutomaticPixelClearingTask extends AsyncTask { + + private WeakReference drawViewWeakReference; + + AutomaticPixelClearingTask(DrawView drawView) { + this.drawViewWeakReference = new WeakReference<>(drawView); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + drawViewWeakReference.get().loadingModal.setVisibility(VISIBLE); + drawViewWeakReference.get().cuts.push(new Pair<>(null, drawViewWeakReference.get().imageBitmap)); + } + + @Override + protected Bitmap doInBackground(Integer... points) { + Bitmap oldBitmap = drawViewWeakReference.get().imageBitmap; + + int colorToReplace = oldBitmap.getPixel(points[0], points[1]); + + int width = oldBitmap.getWidth(); + int height = oldBitmap.getHeight(); + int[] pixels = new int[width * height]; + oldBitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + int rA = Color.alpha(colorToReplace); + int rR = Color.red(colorToReplace); + int rG = Color.green(colorToReplace); + int rB = Color.blue(colorToReplace); + + int pixel; + + // iteration through pixels + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // get current index in 2D-matrix + int index = y * width + x; + pixel = pixels[index]; + int rrA = Color.alpha(pixel); + int rrR = Color.red(pixel); + int rrG = Color.green(pixel); + int rrB = Color.blue(pixel); + + if (rA - COLOR_TOLERANCE < rrA && rrA < rA + COLOR_TOLERANCE && rR - COLOR_TOLERANCE < rrR && rrR < rR + COLOR_TOLERANCE && + rG - COLOR_TOLERANCE < rrG && rrG < rG + COLOR_TOLERANCE && rB - COLOR_TOLERANCE < rrB && rrB < rB + COLOR_TOLERANCE) { + pixels[index] = Color.TRANSPARENT; + } + } + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + + return newBitmap; + } + + protected void onPostExecute(Bitmap result) { + super.onPostExecute(result); + drawViewWeakReference.get().imageBitmap = result; + drawViewWeakReference.get().undoButton.setEnabled(true); + drawViewWeakReference.get().loadingModal.setVisibility(INVISIBLE); + drawViewWeakReference.get().invalidate(); + } + } +} \ No newline at end of file diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java b/cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java new file mode 100644 index 0000000..f5795a1 --- /dev/null +++ b/cutout/src/main/java/com/github/gabrielbb/cutout/IntroActivity.java @@ -0,0 +1,59 @@ +package com.github.gabrielbb.cutout; + +import android.graphics.Color; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; + +import com.github.paolorotolo.appintro.AppIntro; +import com.github.paolorotolo.appintro.AppIntroFragment; + +public class IntroActivity extends AppIntro { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Note here that we DO NOT use setContentView(); + + // Add your slide fragments here. + // AppIntro will automatically generate the dots indicator and buttons. + addSlide(AppIntroFragment.newInstance(getString(R.string.intro_magic_title), getString(R.string.intro_magic_description), R.drawable.intro_magic_wand, getResources().getColor(R.color.intro_magic_background_color))); + addSlide(AppIntroFragment.newInstance(getString(R.string.intro_manual_title), getString(R.string.intro_manual_description), R.drawable.intro_pencil, getResources().getColor(R.color.intro_manual_background_color))); + addSlide(AppIntroFragment.newInstance(getString(R.string.intro_zoom_title), getString(R.string.intro_zoom_description), R.drawable.intro_magnifier, getResources().getColor(R.color.intro_zoom_background_color))); + + // OPTIONAL METHODS + // Override bar/separator color. + setBarColor(Color.parseColor("#3F51B5")); + setSeparatorColor(Color.parseColor("#2196F3")); + + // Hide Skip/Done button. + showSkipButton(false); + setProgressButtonEnabled(true); + + // Turn vibration on and set intensity. + // NOTE: you will probably need to ask VIBRATE permission in Manifest. + setVibrate(false); + + setFadeAnimation(); + } + + @Override + public void onSkipPressed(Fragment currentFragment) { + super.onSkipPressed(currentFragment); + // Do something when users tap on Skip button. + finish(); + } + + @Override + public void onDonePressed(Fragment currentFragment) { + super.onDonePressed(currentFragment); + // Do something when users tap on Done button. + finish(); + } + + @Override + public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) { + super.onSlideChanged(oldFragment, newFragment); + // Do something when the slide changes. + } +} \ No newline at end of file diff --git a/cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java b/cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java new file mode 100644 index 0000000..6907baa --- /dev/null +++ b/cutout/src/main/java/com/github/gabrielbb/cutout/SaveDrawingTask.java @@ -0,0 +1,67 @@ +package com.github.gabrielbb.cutout; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.util.Pair; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.UUID; + +import static android.view.View.VISIBLE; + +class SaveDrawingTask extends AsyncTask> { + + private static final String SAVED_IMAGE_FORMAT = "png"; + private static final String SAVED_IMAGE_NAME = "cutout_tmp"; + + private final WeakReference activityWeakReference; + + SaveDrawingTask(CutOutActivity activity) { + this.activityWeakReference = new WeakReference<>(activity); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + activityWeakReference.get().loadingModal.setVisibility(VISIBLE); + } + + @Override + protected Pair doInBackground(Bitmap... bitmaps) { + + try { + File file = File.createTempFile(SAVED_IMAGE_NAME, SAVED_IMAGE_FORMAT, activityWeakReference.get().getApplicationContext().getCacheDir()); + + try (FileOutputStream out = new FileOutputStream(file)) { + bitmaps[0].compress(Bitmap.CompressFormat.PNG, 95, out); + return new Pair<>(file, null); + } + } catch (IOException e) { + return new Pair<>(null, e); + } + } + + protected void onPostExecute(Pair result) { + super.onPostExecute(result); + + Intent resultIntent = new Intent(); + + if (result.first != null) { + Uri uri = Uri.fromFile(result.first); + + resultIntent.putExtra(CutOut.CUTOUT_EXTRA_RESULT, uri); + activityWeakReference.get().setResult(Activity.RESULT_OK, resultIntent); + activityWeakReference.get().finish(); + + } else { + activityWeakReference.get().exitWithError(result.second); + } + } +} \ No newline at end of file diff --git a/cutout/src/main/res/drawable/done.png b/cutout/src/main/res/drawable/done.png new file mode 100644 index 0000000..c11e3a5 Binary files /dev/null and b/cutout/src/main/res/drawable/done.png differ diff --git a/cutout/src/main/res/drawable/intro_magic_wand.png b/cutout/src/main/res/drawable/intro_magic_wand.png new file mode 100644 index 0000000..5f2097a Binary files /dev/null and b/cutout/src/main/res/drawable/intro_magic_wand.png differ diff --git a/cutout/src/main/res/drawable/intro_magnifier.png b/cutout/src/main/res/drawable/intro_magnifier.png new file mode 100644 index 0000000..ccd9c13 Binary files /dev/null and b/cutout/src/main/res/drawable/intro_magnifier.png differ diff --git a/cutout/src/main/res/drawable/intro_pencil.png b/cutout/src/main/res/drawable/intro_pencil.png new file mode 100644 index 0000000..897e668 Binary files /dev/null and b/cutout/src/main/res/drawable/intro_pencil.png differ diff --git a/cutout/src/main/res/drawable/magic_active.png b/cutout/src/main/res/drawable/magic_active.png new file mode 100644 index 0000000..1b32b72 Binary files /dev/null and b/cutout/src/main/res/drawable/magic_active.png differ diff --git a/cutout/src/main/res/drawable/magic_inactive.png b/cutout/src/main/res/drawable/magic_inactive.png new file mode 100644 index 0000000..7507961 Binary files /dev/null and b/cutout/src/main/res/drawable/magic_inactive.png differ diff --git a/cutout/src/main/res/drawable/magic_selector.xml b/cutout/src/main/res/drawable/magic_selector.xml new file mode 100644 index 0000000..971e646 --- /dev/null +++ b/cutout/src/main/res/drawable/magic_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cutout/src/main/res/drawable/magnifier_active.png b/cutout/src/main/res/drawable/magnifier_active.png new file mode 100644 index 0000000..136d325 Binary files /dev/null and b/cutout/src/main/res/drawable/magnifier_active.png differ diff --git a/cutout/src/main/res/drawable/magnifier_inactive.png b/cutout/src/main/res/drawable/magnifier_inactive.png new file mode 100644 index 0000000..db45b90 Binary files /dev/null and b/cutout/src/main/res/drawable/magnifier_inactive.png differ diff --git a/cutout/src/main/res/drawable/magnifier_selector.xml b/cutout/src/main/res/drawable/magnifier_selector.xml new file mode 100644 index 0000000..bac8d58 --- /dev/null +++ b/cutout/src/main/res/drawable/magnifier_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cutout/src/main/res/drawable/pencil_active.png b/cutout/src/main/res/drawable/pencil_active.png new file mode 100644 index 0000000..328db1b Binary files /dev/null and b/cutout/src/main/res/drawable/pencil_active.png differ diff --git a/cutout/src/main/res/drawable/pencil_inactive.png b/cutout/src/main/res/drawable/pencil_inactive.png new file mode 100644 index 0000000..6c33a47 Binary files /dev/null and b/cutout/src/main/res/drawable/pencil_inactive.png differ diff --git a/cutout/src/main/res/drawable/pencil_selector.xml b/cutout/src/main/res/drawable/pencil_selector.xml new file mode 100644 index 0000000..ce8595b --- /dev/null +++ b/cutout/src/main/res/drawable/pencil_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cutout/src/main/res/drawable/redo_active.png b/cutout/src/main/res/drawable/redo_active.png new file mode 100644 index 0000000..9b06719 Binary files /dev/null and b/cutout/src/main/res/drawable/redo_active.png differ diff --git a/cutout/src/main/res/drawable/redo_inactive.png b/cutout/src/main/res/drawable/redo_inactive.png new file mode 100644 index 0000000..83341d9 Binary files /dev/null and b/cutout/src/main/res/drawable/redo_inactive.png differ diff --git a/cutout/src/main/res/drawable/redo_selector.xml b/cutout/src/main/res/drawable/redo_selector.xml new file mode 100644 index 0000000..f0c9047 --- /dev/null +++ b/cutout/src/main/res/drawable/redo_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cutout/src/main/res/drawable/undo_active.png b/cutout/src/main/res/drawable/undo_active.png new file mode 100644 index 0000000..6ebbe54 Binary files /dev/null and b/cutout/src/main/res/drawable/undo_active.png differ diff --git a/cutout/src/main/res/drawable/undo_inactive.png b/cutout/src/main/res/drawable/undo_inactive.png new file mode 100644 index 0000000..ec333fa Binary files /dev/null and b/cutout/src/main/res/drawable/undo_inactive.png differ diff --git a/cutout/src/main/res/drawable/undo_selector.xml b/cutout/src/main/res/drawable/undo_selector.xml new file mode 100644 index 0000000..6c31d0c --- /dev/null +++ b/cutout/src/main/res/drawable/undo_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cutout/src/main/res/layout/activity_photo_edit.xml b/cutout/src/main/res/layout/activity_photo_edit.xml new file mode 100644 index 0000000..5d7ee84 --- /dev/null +++ b/cutout/src/main/res/layout/activity_photo_edit.xml @@ -0,0 +1,279 @@ + + + + + + + + + +