├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── TODO.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── de
│ │ └── k3b
│ │ └── android
│ │ └── lossless_jpg_crop
│ │ └── ExampleInstrumentedTest.java
│ ├── debug
│ └── res
│ │ ├── drawable
│ │ └── qr_code_url_llcrop_fdroid.png
│ │ └── web
│ │ ├── ic_menu_crop.png
│ │ ├── ic_menu_rotate.png
│ │ ├── ic_menu_save.png
│ │ └── ic_menu_share.png
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── de
│ │ │ └── k3b
│ │ │ ├── android
│ │ │ └── lossless_jpg_crop
│ │ │ │ ├── BaseActivity.java
│ │ │ │ ├── CropAreasChooseBaseActivity.java
│ │ │ │ ├── CropAreasEditActivity.java
│ │ │ │ ├── CropAreasGetContentActivity.java
│ │ │ │ ├── CropAreasSendActivity.java
│ │ │ │ ├── DefineAspectRatioFragment.java
│ │ │ │ ├── ImageProcessor.java
│ │ │ │ └── MainApp.java
│ │ │ └── util
│ │ │ └── TempFileUtil.java
│ └── res
│ │ ├── layout
│ │ ├── activity_crop.xml
│ │ └── fragment_define_aspect_ratio.xml
│ │ ├── menu
│ │ ├── menu_aspect_ratio.xml
│ │ ├── menu_edit.xml
│ │ ├── menu_get_content.xml
│ │ ├── menu_rotate.xml
│ │ └── menu_send.xml
│ │ ├── mipmap-hdpi
│ │ └── ll_crop.png
│ │ ├── mipmap-mdpi
│ │ └── ll_crop.png
│ │ ├── mipmap-xhdpi
│ │ └── ll_crop.png
│ │ ├── mipmap-xxhdpi
│ │ └── ll_crop.png
│ │ ├── values-de
│ │ └── strings.xml
│ │ ├── values-it
│ │ └── strings.xml
│ │ ├── values-nb-rNO
│ │ └── strings.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── file_provider_paths.xml
│ └── test
│ └── java
│ └── de
│ └── k3b
│ └── util
│ └── TempFileUnitTest.java
├── build.gradle
├── cropper
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── net
│ │ └── realify
│ │ └── lib
│ │ └── androidimagecropper
│ │ ├── BitmapCroppingWorkerTask.java
│ │ ├── BitmapLoadingWorkerTask.java
│ │ ├── BitmapUtils.java
│ │ ├── CropImage.java
│ │ ├── CropImageActivity.java
│ │ ├── CropImageAnimation.java
│ │ ├── CropImageOptions.java
│ │ ├── CropImageView.java
│ │ ├── CropOverlayView.java
│ │ ├── CropWindowHandler.java
│ │ └── CropWindowMoveHandler.java
│ └── res
│ ├── drawable-hdpi
│ ├── crop_image_menu_flip.png
│ ├── crop_image_menu_rotate_left.png
│ └── crop_image_menu_rotate_right.png
│ ├── drawable-xhdpi
│ ├── crop_image_menu_flip.png
│ ├── crop_image_menu_rotate_left.png
│ └── crop_image_menu_rotate_right.png
│ ├── drawable-xxhdpi
│ ├── crop_image_menu_flip.png
│ ├── crop_image_menu_rotate_left.png
│ └── crop_image_menu_rotate_right.png
│ ├── drawable-xxxhdpi
│ ├── crop_image_menu_flip.png
│ ├── crop_image_menu_rotate_left.png
│ └── crop_image_menu_rotate_right.png
│ ├── layout
│ ├── crop_image_activity.xml
│ └── crop_image_view.xml
│ ├── menu
│ └── crop_image_menu.xml
│ ├── values-ar
│ └── lib-strings.xml
│ ├── values-cs
│ └── lib-strings.xml
│ ├── values-de
│ └── lib-strings.xml
│ ├── values-es-rGT
│ └── lib-strings.xml
│ ├── values-fr
│ └── lib-strings.xml
│ ├── values-he
│ └── lib-strings.xml
│ ├── values-it
│ └── lib-strings.xml
│ ├── values-ja
│ └── lib-strings.xml
│ ├── values-ko
│ └── lib-strings.xml
│ ├── values-nb
│ └── lib-strings.xml
│ ├── values-nl
│ └── lib-strings.xml
│ ├── values-pl
│ └── lib-strings.xml
│ ├── values-pt-rBR
│ └── lib-strings.xml
│ ├── values-ru-rRU
│ └── lib-strings.xml
│ ├── values-vi
│ └── lib-strings.xml
│ ├── values-zh-rCN
│ └── lib-strings.xml
│ ├── values-zh-rTW
│ └── lib-strings.xml
│ ├── values-zh
│ └── lib-strings.xml
│ └── values
│ ├── attrs.xml
│ └── lib-strings.xml
├── fastlane
├── fdroid
│ └── de.k3b.android.lossless_jpg_crop.yml
└── metadata
│ └── android
│ ├── de-DE
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
│ ├── en-US
│ ├── changelogs
│ │ ├── 1.txt
│ │ ├── 10.txt
│ │ ├── 11.txt
│ │ ├── 2.txt
│ │ ├── 3.txt
│ │ ├── 4.txt
│ │ ├── 5.txt
│ │ ├── 6.txt
│ │ ├── 7.txt
│ │ ├── 8.txt
│ │ └── 9.txt
│ ├── full_description.txt
│ ├── images
│ │ ├── featureGraphic.png
│ │ └── phoneScreenshots
│ │ │ └── Screenshot.png
│ ├── short_description.txt
│ └── title.txt
│ └── values-nb-NO
│ └── title.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | /.idea
15 | /cropper/build
16 | /todo.txt
17 | /advertise-llCrop.md
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # travis build for #toGoZip (https://github.com/k3b/ToGoZip/)
2 | language: android
3 |
4 | jdk:
5 | - oraclejdk8
6 |
7 | addons:
8 | apt:
9 | packages:
10 | # graphviz to render javadoc uml
11 | # https://docs.travis-ci.com/user/multi-os/
12 | - graphviz
13 |
14 | android:
15 | components:
16 | # https://github.com/travis-ci/travis-ci/issues/5036
17 | - tools
18 |
19 | - android-29
20 |
21 | - add-on
22 | - extra
23 |
24 | before_install:
25 | # http://stackoverflow.com/questions/33820638/travis-yml-gradlew-permission-denied
26 | # must execute
27 | # git update-index --chmod=+x gradlew
28 | # instead of
29 | # - chmod +x gradlew
30 | #
31 | # https://stackoverflow.com/questions/52274229/travis-ci-android-28-licenses-have-not-been-accepted
32 | # - yes | sdkmanager "platforms;android-28"
33 | - yes | sdkmanager "platforms;android-23"
34 |
35 |
36 | script:
37 | - jdk_switcher use oraclejdk8
38 | - ./gradlew assemble test
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
LLCrop
2 | Loss Less Cropping and Image Rotation: Remove unwanted parts of jpg photo without quality loss.
3 |
4 |
5 |
6 | While there are many apps capable of cropping images (often with additional features),
7 | they generally cause quality loss because they
8 | [re-encode to JPEG again](https://en.wikipedia.org/wiki/Lossy_compression) when saving the output file.
9 |
10 | LLCrop (the "LL" stands for lossless) can [crop JPEG images without quality loss](https://en.wikipedia.org/wiki/Lossy_compression#JPEG)
11 | because it crops the raw JPEG image without re-encoding the file. It also preserves embedded metadata (EXIF/IPTC and XMP).
12 |
13 | Simply load a JPEG image from the in-app image browser, adjust the rectangular selection, rotate it if necessary and save it as a new image file.
14 |
15 | Note: This app is focused on lossless JPEG image manipulation, so issues that propose additional
16 | features (e.g. support for other file formats, add resize-support or adding text to images) are out of scope.
17 |
18 | ---
19 |
20 | ## Supported Workflows and Features:
21 |
22 | * Workflow [#1](https://github.com/k3b/LosslessJpgCrop/issues/1) : From Android **app launcher**:
23 | * Pick an image and crop it to a new public file
24 | * Workflow [#1](https://github.com/k3b/LosslessJpgCrop/issues/1) : From any **file manager** or **gallery app** that supports [intent-action-EDIT](https://developer.android.com/reference/android/content/Intent#ACTION_EDIT) for MIME *image/jpeg*:
25 | * Crop current selected image to a new public file
26 | * Workflow [#2](https://github.com/k3b/LosslessJpgCrop/issues/2) : From any app that supports [intent-action-SEND](https://developer.android.com/reference/android/content/Intent#ACTION_SEND) or [intent-action-SEND-TO](https://developer.android.com/reference/android/content/Intent#ACTION_SENDTO) for MIME *image/jpeg*
27 | * Send/SendTo/Share a cropped version of the currently selected image
28 | * Workflow [#3](https://github.com/k3b/LosslessJpgCrop/issues/3)/[#8](https://github.com/k3b/LosslessJpgCrop/issues/8) : From any app that supports [intent-action-GET-CONTENT](https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT) or intent-action-PICK for MIME *image/jpeg*
29 | * Open/Pick the cropping of an uncropped image
30 | * Feature [#17](https://github.com/k3b/LosslessJpgCrop/issues/17) (Since Version 1.2) : added support for image rotation
31 | * Feature [#35](https://github.com/k3b/LosslessJpgCrop/issues/35) (Since [Version 1.3](https://github.com/k3b/LosslessJpgCrop/milestone/5)) : Display current crop box coordinates and size
32 | * Show XY offset of top left corner of crop box displayed along with it's dimensions.
33 | * You can get more predictable results by sticking to 8 or 16 multiples for offset and box size allows to target aspect ratio.
34 | * Feature [#15](https://github.com/k3b/LosslessJpgCrop/issues/15) (Since [Version 1.3](https://github.com/k3b/LosslessJpgCrop/milestone/5)) : Define crop box size or aspect ratio
35 | * if you set width and height to a value below 100 then you define the aspect ratio of the cropping result. Example 9x13
36 | * if you set width and height to a value above 100 then you define the absolute size in pixel of the cropping result. Example 400x600
37 |
38 | ---
39 |
40 | ## Requirements
41 |
42 | * Android 4.4 KitKat (API 19) or newer
43 | * CPU arm64-v8a, arbeabi-v7a, x86 or x86_64 because of the C++ cropping code
44 | * Permissions
45 | * READ_EXTERNAL_STORAGE (to open a local image)
46 | * WRITE_EXTERNAL_STORAGE (to save the cropped image)
47 |
48 | ---
49 |
50 | [
](https://f-droid.org/packages/de.k3b.android.lossless_jpg_crop)
51 | [
](https://f-droid.org/packages/de.k3b.android.lossless_jpg_crop)
52 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | * v Feature [#35](https://github.com/k3b/LosslessJpgCrop/issues/35) : Display current crop box coordinates and size
2 | * v Show XY offset of top left corner of crop box displayed along with it's dimensions.
3 | * ? snap to multible of 8
4 | * > Feature [#15](https://github.com/k3b/LosslessJpgCrop/issues/15) : Define crop box size or aspect ratio
5 | * v if you set width and height to a value below 100 then you define the aspect ratio of the cropping result
6 | * v if you set width and height to a value above 100 then you define the absolute size in pixel of the cropping result.
7 | * v menu to define free/square/predefined/userdefined
8 | * bugs
9 | > * 200x400 => swap => 377x188 (expected 400x200)
10 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | //noinspection GradleCompatible
5 | compileSdkVersion 28
6 | defaultConfig {
7 | applicationId "de.k3b.android.lossless_jpg_crop"
8 |
9 | // SAF ACTION_CREATE_DOCUMENT requires api-19 and later
10 | minSdkVersion 19 // Android 4.4 KitKat (API 19); Android 5.0 Lollipop (API 21); Android 6.0 Marshmallow (API 23); Android 7.0 Nougat (API 24)
11 | //noinspection ExpiredTargetSdkVersion
12 | targetSdkVersion 29
13 |
14 | // 1.0.0.190425 (1) initial version
15 | // 1.0.1.190507 (2) Bugfix: missing read permission; port to android-x
16 | // 1.0.2.190515 (3) Bugfix: rotation; illegal filename
17 | // 1.1.0.190518 (4) Implemented Workflow: GET_CONTENT/PICK/share/SEND/SENDTO to pick/re-send a cropped photo
18 | // 1.1.1.190522 (5) Errorhandling/display errormessage; delete temp files; action com.android.camera.action.CROP
19 | // 1.1.2.190605 (6) bugfixes send/get_content; translation-updates "en", "de","nb-rNO"
20 | // 1.2.0.191107 (7) Added jpg Image Rotation
21 | // 1.2.1.200708 (8) fix #4: Cropping result image without exif-thumbnail
22 | // 1.2.2.210416 (9) Library updates
23 | // 1.3.0.230218 (10) new features: #15:modifyable dimensions; #35:Display crop box coordinates and size
24 | versionCode 10
25 | versionName "1.3.0.230218"
26 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
27 |
28 | // all supported locales. Note: the lib has more translations which are supressed here
29 | // resConfigs "ar","de","es","fr","hi","in","it","ja","nl","pl","ro","ru","tr","uk","zz","pt-rBR","zh-rCN","zh-rTW"
30 | resConfigs "de","nb-rNO","it"
31 | }
32 | buildTypes {
33 | debug {
34 | shrinkResources true
35 | minifyEnabled true
36 | // minifyEnabled false
37 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
38 |
39 | applicationIdSuffix ".debug"
40 | versionNameSuffix "-DEBUG" }
41 | release {
42 | shrinkResources true
43 | minifyEnabled true
44 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
45 | }
46 | }
47 | lintOptions {
48 | // http://stackoverflow.com/questions/31350350/generating-signed-apk-error7-missingtranslation-in-build-generated-res-gen
49 | // MissingTranslation : not all crowdwin translations are complete so ignore them
50 | disable 'MissingTranslation'
51 |
52 | abortOnError false
53 | }
54 | compileOptions {
55 | sourceCompatibility JavaVersion.VERSION_1_8
56 | targetCompatibility JavaVersion.VERSION_1_8
57 | }
58 | }
59 |
60 | dependencies {
61 | implementation fileTree(include: ['*.jar'], dir: 'libs')
62 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
63 | implementation 'androidx.annotation:annotation:1.2.0'
64 |
65 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
66 | testImplementation 'junit:junit:4.13.2'
67 | androidTestImplementation 'androidx.test:runner:1.4.0'
68 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
69 |
70 |
71 | // implements lossless croppig
72 | // implementation 'com.facebook.spectrum:spectrum-default:1.0.0'
73 | implementation 'com.facebook.spectrum:spectrum-core:1.3.0'
74 | implementation 'com.facebook.spectrum:spectrum-jpeg:1.3.0'
75 | // the cropping gui
76 | // implementation 'com.edmodo:cropper:1.0.1'
77 | // implementation "com.naver.android.helloyako:imagecropview:1.2.2"
78 | // implementation 'com.github.realify.Android-Image-Cropper:1.0.0'
79 | implementation project(':cropper')
80 | }
81 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -dontnote MobileAds
2 |
3 | ###############
4 | # I use proguard only to remove unused stuff and to keep the app small.
5 | # I donot want to obfuscate (rename packages, classes, methods, ...) since this is open source
6 | -dontobfuscate
7 | -dontoptimize
8 | -keepnames class ** { *; }
9 | -keepnames interface ** { *; }
10 | -keepnames enum ** { *; }
11 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/de/k3b/android/lossless_jpg_crop/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package de.k3b.android.lossless_jpg_crop;
2 |
3 | import android.content.Context;
4 | import androidx.test.InstrumentationRegistry;
5 | import androidx.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("de.k3b.android.lossless_jpg_crop", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/debug/res/drawable/qr_code_url_llcrop_fdroid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k3b/LosslessJpgCrop/8fb4ad2b5ffc7cf2d8c060c40b0aac2c585a76fa/app/src/debug/res/drawable/qr_code_url_llcrop_fdroid.png
--------------------------------------------------------------------------------
/app/src/debug/res/web/ic_menu_crop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k3b/LosslessJpgCrop/8fb4ad2b5ffc7cf2d8c060c40b0aac2c585a76fa/app/src/debug/res/web/ic_menu_crop.png
--------------------------------------------------------------------------------
/app/src/debug/res/web/ic_menu_rotate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k3b/LosslessJpgCrop/8fb4ad2b5ffc7cf2d8c060c40b0aac2c585a76fa/app/src/debug/res/web/ic_menu_rotate.png
--------------------------------------------------------------------------------
/app/src/debug/res/web/ic_menu_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k3b/LosslessJpgCrop/8fb4ad2b5ffc7cf2d8c060c40b0aac2c585a76fa/app/src/debug/res/web/ic_menu_save.png
--------------------------------------------------------------------------------
/app/src/debug/res/web/ic_menu_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k3b/LosslessJpgCrop/8fb4ad2b5ffc7cf2d8c060c40b0aac2c585a76fa/app/src/debug/res/web/ic_menu_share.png
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/java/de/k3b/android/lossless_jpg_crop/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package de.k3b.android.lossless_jpg_crop;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.content.DialogInterface;
6 | import androidx.annotation.NonNull;
7 | import androidx.annotation.Nullable;
8 | import androidx.core.app.ActivityCompat;
9 |
10 | /**
11 | * Created by Oleksii Shliama (https://github.com/shliama).
12 | */
13 | public class BaseActivity extends Activity {
14 |
15 | private AlertDialog mAlertDialog;
16 |
17 | /**
18 | * Hide alert dialog if any.
19 | */
20 | @Override
21 | protected void onStop() {
22 | super.onStop();
23 | if (mAlertDialog != null && mAlertDialog.isShowing()) {
24 | mAlertDialog.dismiss();
25 | }
26 | }
27 |
28 |
29 | /**
30 | * Requests given permission.
31 | * If the permission has been denied previously, a Dialog will prompt the user to grant the
32 | * permission, otherwise it is requested directly.
33 | */
34 | protected void requestPermission(final String permission, String rationale, final int requestCode) {
35 | if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
36 | showAlertDialog(getString(R.string.permission_title_rationale), rationale,
37 | new DialogInterface.OnClickListener() {
38 | @Override
39 | public void onClick(DialogInterface dialog, int which) {
40 | ActivityCompat.requestPermissions(BaseActivity.this,
41 | new String[]{permission}, requestCode);
42 | }
43 | }, getString(android.R.string.ok), null, getString(android.R.string.cancel));
44 | } else {
45 | ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
46 | }
47 | }
48 |
49 | /**
50 | * This method shows dialog with given title & message.
51 | * Also there is an option to pass onClickListener for positive & negative button.
52 | *
53 | * @param title - dialog title
54 | * @param message - dialog message
55 | * @param onPositiveButtonClickListener - listener for positive button
56 | * @param positiveText - positive button text
57 | * @param onNegativeButtonClickListener - listener for negative button
58 | * @param negativeText - negative button text
59 | */
60 | protected void showAlertDialog(@Nullable String title, @Nullable String message,
61 | @Nullable DialogInterface.OnClickListener onPositiveButtonClickListener,
62 | @NonNull String positiveText,
63 | @Nullable DialogInterface.OnClickListener onNegativeButtonClickListener,
64 | @NonNull String negativeText) {
65 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
66 | builder.setTitle(title);
67 | builder.setMessage(message);
68 | builder.setPositiveButton(positiveText, onPositiveButtonClickListener);
69 | builder.setNegativeButton(negativeText, onNegativeButtonClickListener);
70 | mAlertDialog = builder.show();
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/de/k3b/android/lossless_jpg_crop/CropAreasEditActivity.java:
--------------------------------------------------------------------------------
1 | package de.k3b.android.lossless_jpg_crop;
2 |
3 | import android.net.Uri;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.view.Menu;
7 |
8 | /**
9 | * #1: ACTION_EDIT(uri=sourcePhoto.jpg) => crop => public-file.jpg
10 | * #1: ACTION_MAIN => ACTION_EDIT(uri=GetContent(mime=image/jpeg)) => crop => public-file.jpg
11 | *
12 | * Handles ACTION_EDIT(uri=DATA) and ACTION_MAIN:
13 | */
14 |
15 | public class CropAreasEditActivity extends CropAreasChooseBaseActivity {
16 | public CropAreasEditActivity() {
17 | super(R.id.menu_save);
18 | }
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | Uri uri = getSourceImageUri(getIntent());
24 |
25 | if (uri == null) {
26 | Log.d(TAG, getInstanceNo4Debug() + "Intent.data has not initial image uri. Opening Image Picker");
27 | // must be called with image uri
28 | edit.pickFromGalleryForEdit();
29 | } else {
30 | SetImageUriAndLastCropArea(uri, savedInstanceState);
31 | }
32 | }
33 |
34 | @Override
35 | public boolean onCreateOptionsMenu(final Menu menu) {
36 | getMenuInflater().inflate(R.menu.menu_edit, menu);
37 | super.onCreateOptionsMenu(menu);
38 | return true;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/de/k3b/android/lossless_jpg_crop/CropAreasGetContentActivity.java:
--------------------------------------------------------------------------------
1 | package de.k3b.android.lossless_jpg_crop;
2 |
3 | import android.content.Intent;
4 | import android.graphics.Rect;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import android.view.Menu;
8 |
9 | /**
10 | * Handles ACTION_GET_CONTENT and ACTION_PICK to pick a cropped image
11 | *
12 | * #3: GET_CONTENT => LLCrop => sourcePhoto.jpg=GET_CONTENT(mime=image/jpeg) => return crop(sourcePhoto.jpg)
13 | */
14 | public class CropAreasGetContentActivity extends CropAreasChooseBaseActivity {
15 | public CropAreasGetContentActivity() {
16 | super(R.id.menu_get_content);
17 | }
18 |
19 | private static final String KEY_SOURCE_IMAGE_URI = "mSourceImageUri";
20 | private Uri mSourceImageUri = null;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 |
26 | if (savedInstanceState != null) {
27 | mSourceImageUri = savedInstanceState.getParcelable(KEY_SOURCE_IMAGE_URI);
28 | }
29 |
30 | if (mSourceImageUri == null) {
31 | content.pickFromGalleryForContent();
32 | } else {
33 | SetImageUriAndLastCropArea(mSourceImageUri, savedInstanceState);
34 | }
35 | }
36 |
37 | @Override
38 | protected void onSaveInstanceState(Bundle outState) {
39 | super.onSaveInstanceState(outState);
40 | outState.putParcelable(KEY_SOURCE_IMAGE_URI, mSourceImageUri);
41 | }
42 |
43 | @Override
44 | public boolean onCreateOptionsMenu(final Menu menu) {
45 | super.onCreateOptionsMenu(menu);
46 | getMenuInflater().inflate(R.menu.menu_get_content, menu);
47 | return true;
48 | }
49 |
50 | /** get uri of image that will be cropped */
51 | @Override
52 | protected Uri getSourceImageUri(Intent intent) {
53 | return mSourceImageUri;
54 | }
55 |
56 | @Override
57 | protected void SetImageUriAndLastCropArea(Uri uri, Rect crop) {
58 | this.mSourceImageUri = uri;
59 | super.SetImageUriAndLastCropArea(uri, crop);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/de/k3b/android/lossless_jpg_crop/CropAreasSendActivity.java:
--------------------------------------------------------------------------------
1 | package de.k3b.android.lossless_jpg_crop;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.view.Menu;
7 |
8 | /**
9 | * Handles ACTION_SENDTO(uri=DATA) and ACTION_SEND(uri=EXTRA_STREAM) to re-send a cropped image
10 | *
11 | * #2: SEND/SENDTO(uri=sourcePhoto.jpg) => crop => tempfile.jpg => SEND/SENDTO(uri=tempfile.jpg)
12 | */
13 | public class CropAreasSendActivity extends CropAreasChooseBaseActivity {
14 | public CropAreasSendActivity() {
15 | super(R.id.menu_send);
16 | }
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | Uri uri = getSourceImageUri(getIntent());
22 |
23 | send.onGetSendImage(uri, savedInstanceState);
24 | }
25 |
26 | @Override
27 | public boolean onCreateOptionsMenu(final Menu menu) {
28 | getMenuInflater().inflate(R.menu.menu_send, menu);
29 | super.onCreateOptionsMenu(menu);
30 | return true;
31 | }
32 |
33 | /** get uri from intent: ACTION_SENDTO(uri=DATA) and ACTION_SEND(uri=EXTRA_STREAM) */
34 | @Override
35 | protected Uri getSourceImageUri(Intent intent) {
36 | Uri uri = super.getSourceImageUri(intent);
37 |
38 | if ((uri == null) && (intent != null)) {
39 | Bundle extras = (uri != null) ? null : intent.getExtras();
40 | Object stream = (extras == null) ? null : extras.get(Intent.EXTRA_STREAM);
41 | if (stream != null) {
42 | uri = Uri.parse(stream.toString());
43 | }
44 |
45 | }
46 | return uri;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/de/k3b/android/lossless_jpg_crop/DefineAspectRatioFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2022-2023 by k3b
3 |
4 | This file is part of de.k3b.android.lossless_jpg_crop (https://github.com/k3b/losslessJpgCrop/)
5 |
6 | This program is free software: you can redistribute it and/or modify it
7 | under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful, but WITHOUT
12 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 | FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 | for more details.
15 |
16 | You should have received a copy of the GNU General Public License along with
17 | this program. If not, see
18 | */
19 | package de.k3b.android.lossless_jpg_crop;
20 |
21 | import android.app.DialogFragment;
22 | import android.os.Bundle;
23 |
24 | import android.view.LayoutInflater;
25 | import android.view.View;
26 | import android.view.ViewGroup;
27 | import android.widget.Button;
28 | import android.widget.EditText;
29 |
30 | /**
31 | * Define the aspect ratio of the result. (i.e 10x15).
32 | *
33 | * Use the {@link DefineAspectRatioFragment#newInstance} factory method to
34 | * create an instance of this fragment.
35 | */
36 | public class DefineAspectRatioFragment extends DialogFragment {
37 |
38 | private static final String PARAM_WIDTH = "paramWidth";
39 | private static final String PARAM_HEIGHT = "paramHeight";
40 |
41 | private String mParamWidth;
42 | private String mParamHeight;
43 |
44 | private EditText editWidth;
45 | private EditText editHeight;
46 |
47 | /** must be implemented by calling activity to receive change in AspectRatio */
48 | public interface AspectRatioHandler {
49 | void onDefineAspectRatio(String width, String height);
50 | }
51 |
52 | public DefineAspectRatioFragment() {
53 | // Required empty public constructor
54 | }
55 |
56 | /**
57 | * Use this factory method to create a new instance of
58 | * this fragment using the provided parameters.
59 | *
60 | * @param paramXY aspect ratio width and height
61 | * @return A new instance of fragment DefineAspectRatioFragment.
62 | */
63 | public static DefineAspectRatioFragment newInstance(String... paramXY) {
64 | DefineAspectRatioFragment fragment = new DefineAspectRatioFragment();
65 | Bundle args = new Bundle();
66 | args.putString(PARAM_WIDTH, paramXY[0]);
67 | args.putString(PARAM_HEIGHT, paramXY[1]);
68 | fragment.setArguments(args);
69 | return fragment;
70 | }
71 |
72 | @Override
73 | public void onCreate(Bundle savedInstanceState) {
74 | super.onCreate(savedInstanceState);
75 | if (getArguments() != null) {
76 | mParamWidth = getArguments().getString(PARAM_WIDTH);
77 | mParamHeight = getArguments().getString(PARAM_HEIGHT);
78 | }
79 | }
80 |
81 | @Override
82 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
83 | Bundle savedInstanceState) {
84 | // Inflate the layout for this fragment
85 | View view = inflater.inflate(R.layout.fragment_define_aspect_ratio, container, false);
86 | if (getShowsDialog()) {
87 | onCreateViewDialog(view);
88 | }
89 |
90 | return view;
91 | }
92 |
93 | @Override
94 | public void onSaveInstanceState(Bundle outState) {
95 | super.onSaveInstanceState(outState);
96 | saveDialog();
97 | outState.putString(PARAM_WIDTH, mParamWidth);
98 | outState.putString(PARAM_HEIGHT, mParamHeight);
99 | }
100 |
101 | /** handle init for dialog-only controlls: cmdOk, cmdCancel, status */
102 | private void onCreateViewDialog(View view) {
103 | view.