├── .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 logo LLCrop 2 | Loss Less Cropping and Image Rotation: Remove unwanted parts of jpg photo without quality loss. 3 | 4 | Featuregraphic of llcrop 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 save icon 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 save icon 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 share icon 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 rotationrotate icon 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 aspect ratio icon 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 | [available on F-Droid app store](https://f-droid.org/packages/de.k3b.android.lossless_jpg_crop)
51 | [available on F-Droid app store](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.