diff --git a/README.md b/README.md
index 5bbfd1f30..65c01e91b 100644
--- a/README.md
+++ b/README.md
@@ -13,21 +13,21 @@
-1. Include the library as local library project.
+1. Include the library as a local library project.
```
allprojects {
repositories {
- jcenter()
+ ...
maven { url "https://jitpack.io" }
}
}
```
- ``` compile 'com.github.yalantis:ucrop:2.2.2' ``` - lightweight general solution
-
- ``` compile 'com.github.yalantis:ucrop:2.2.2-native' ``` - get power of the native code to preserve image quality (+ about 1.5 MB to an apk size)
-
+ ``` implementation 'com.github.yalantis:ucrop:2.2.3' ``` - lightweight general solution
+
+ ``` implementation 'com.github.yalantis:ucrop:2.2.3-native' ``` - get power of the native code to preserve image quality (+ about 1.5 MB to an apk size)
+
2. Add UCropActivity into your AndroidManifest.xml
```
@@ -73,29 +73,46 @@ If you want to let your users choose crop ratio dynamically, just do not call `w
uCrop builder class has method `withOptions(UCrop.Options options)` which extends library configurations.
-Currently you can change:
+Currently, you can change:
* image compression format (e.g. PNG, JPEG, WEBP), compression
* image compression quality [0 - 100]. PNG which is lossless, will ignore the quality setting.
* whether all gestures are enabled simultaneously
- * maximum size for Bitmap that is decoded from source Uri and used within crop view. If you want to override default behaviour.
+ * maximum size for Bitmap that is decoded from source Uri and used within crop view. If you want to override the default behaviour.
* toggle whether to show crop frame/guidelines
* setup color/width/count of crop frame/rows/columns
- * choose whether you want rectangle or oval crop area
+ * choose whether you want rectangle or oval(`options.setCircleDimmedLayer(true)`) crop area
* the UI colors (Toolbar, StatusBar, active widget state)
* and more...
-
+
+Since version 2.2.7 in case if you need to change transport protocol, setup timeout etc. You may set your `OkHttpClient` next way:
+
+ ```java
+ new UCropInitializer().setOkHttpClient(client);
+ ```
+
# Compatibility
-
+
* Library - Android ICS 4.0+ (API 14) (Android GINGERBREAD 2.3+ (API 10) for versions <= 1.3.2)
* Sample - Android ICS 4.0+ (API 14)
* CPU - armeabi armeabi-v7a x86 x86_64 arm64-v8a (for versions >= 2.1.2)
-
+
# Changelog
-### Version: 2.2.2
-* uCrop fragment added
-* bugfix
+### Version: 2.2.8
+
+* Merged pending pull requests with improvements and bugfixes
+* Update compileSdk and targetSdk versions up to 31
+* Add localizations
+* Fixed [#609](https://github.com/Yalantis/uCrop/issues/609)
+* Fixed [#794](https://github.com/Yalantis/uCrop/issues/794)
+
+### Version: 2.2.3
+
+ * Several fixes including [#445](https://github.com/Yalantis/uCrop/issues/445), [#465](https://github.com/Yalantis/uCrop/issues/465) and more!
+ * Material design support
+ * uCrop fragment as child fragment
+ * Added the Italian language
### Version: 2.2.2
@@ -114,7 +131,7 @@ Currently you can change:
### Version: 2.1
* Fixes issue with EXIF data (images taken on front camera with Samsung devices mostly) [#130](https://github.com/Yalantis/uCrop/issues/130) [#111](https://github.com/Yalantis/uCrop/issues/111)
- * Added API to set custom set of aspect ratio options for user. [#131](https://github.com/Yalantis/uCrop/issues/131)
+ * Added API to set custom set of aspect ratio options for the user. [#131](https://github.com/Yalantis/uCrop/issues/131)
* Added API to set all configs via UCrop.Options class. [#126](https://github.com/Yalantis/uCrop/issues/126)
* Added ABI x86_64 support. [#105](https://github.com/Yalantis/uCrop/issues/105)
@@ -123,7 +140,7 @@ Currently you can change:
* Native image crop (able to crop high-resolution images, e.g. 16MP & 32MP images on Nexus 5X).
* WebP compression format is not supported at the moment (choose JPEG or PNG).
* Now library copies EXIF data to cropped image (size and orientation are updated).
-
+
### Version: 1.5
* Introduced "Freestyle" crop (you can resize crop rectangle by dragging it corners) [#32](https://github.com/Yalantis/uCrop/issues/32)
@@ -132,8 +149,8 @@ Currently you can change:
### Version: 1.4
- * Introduced http(s) Uri support!
- * Image is cropped in background thread.
+ * Introduced HTTP(s) Uri support!
+ * Image is cropped in a background thread.
* Showing loader while Bitmap is processed (both loading and cropping).
* Several bug fixes.
* Couple new things to configure.
@@ -141,12 +158,12 @@ Currently you can change:
### Version: 1.3
- * Image is loaded in background thread. Better error-handling for image decoding.
+ * Image is loaded in a background thread. Better error-handling for image decoding.
* Improved EXIF data support (rotation and mirror).
* Small UI updates.
* Couple new things to configure.
-
- * Sample updated with possibility to choose custom aspect ratio.
+
+ * Sample updated with the possibility to choose custom aspect ratio.
### Version: 1.2
@@ -162,7 +179,7 @@ Currently you can change:
### Let us know!
-We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the library.
+We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the library.
#### Apps using uCrop
diff --git a/build.gradle b/build.gradle
index cbd5640a7..1a4d907ab 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,14 @@
buildscript {
ext {
- androidx_appcompat_version = "1.1.0"
+ androidx_appcompat_version = "1.3.0"
androidx_core_version = "1.1.0"
- androidx_exifinterface_version = "1.1.0-beta01"
- androidx_transition_version = "1.2.0-rc01"
+ androidx_exifinterface_version = "1.3.2"
+ androidx_transition_version = "1.4.1"
constraintlayout_version = "1.1.3"
}
repositories {
- jcenter()
+ mavenCentral()
maven {
url 'https://maven.google.com/'
name 'Google'
@@ -28,7 +28,7 @@ allprojects {
group = GROUP
repositories {
- jcenter()
+ mavenCentral()
maven {
url 'https://maven.google.com/'
name 'Google'
diff --git a/gradle.properties b/gradle.properties
index d13941087..1278a05e1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,8 +17,8 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
-VERSION_NAME=2.2.2-non-native
-VERSION_CODE=24
+VERSION_NAME=2.2.8-native
+VERSION_CODE=26
GROUP=com.yalantis
POM_DESCRIPTION=Android Library for cropping images
diff --git a/sample/build.gradle b/sample/build.gradle
index fe59af377..95fb140fe 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -1,12 +1,12 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 29
- buildToolsVersion '28.0.3'
+ compileSdkVersion 31
+ buildToolsVersion '30.0.3'
defaultConfig {
applicationId "com.yalantis.ucrop.sample"
minSdkVersion 14
- targetSdkVersion 29
+ targetSdkVersion 31
versionCode 13
versionName "1.2.4"
}
@@ -38,5 +38,6 @@ dependencies {
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.core:core:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
+ implementation "com.squareup.okhttp3:okhttp:3.12.13"
implementation project(':ucrop')
}
\ No newline at end of file
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 940116d88..414a1dcd0 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
+ android:screenOrientation="portrait"
+ android:exported="true">
diff --git a/sample/src/main/java/com/yalantis/ucrop/sample/ResultActivity.java b/sample/src/main/java/com/yalantis/ucrop/sample/ResultActivity.java
index 7d7d935f2..55fb1f832 100755
--- a/sample/src/main/java/com/yalantis/ucrop/sample/ResultActivity.java
+++ b/sample/src/main/java/com/yalantis/ucrop/sample/ResultActivity.java
@@ -18,24 +18,30 @@
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.FileProvider;
+
+import com.yalantis.ucrop.util.BitmapLoadUtils;
import com.yalantis.ucrop.view.UCropView;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.util.Calendar;
import java.util.List;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.app.ActivityCompat;
-import androidx.core.app.NotificationCompat;
-import androidx.core.content.FileProvider;
-
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
@@ -59,6 +65,8 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_result);
Uri uri = getIntent().getData();
+ int width = 0;
+ int height = 0;
if (uri != null) {
try {
UCropView uCropView = findViewById(R.id.ucrop);
@@ -66,20 +74,49 @@ protected void onCreate(Bundle savedInstanceState) {
uCropView.getOverlayView().setShowCropFrame(false);
uCropView.getOverlayView().setShowCropGrid(false);
uCropView.getOverlayView().setDimmedColor(Color.TRANSPARENT);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && "content".equals(uri.getScheme())) {
+ TextView textViewExifWarning = findViewById(R.id.text_view_content_warning);
+ textViewExifWarning.setVisibility(View.VISIBLE);
+ }
} catch (Exception e) {
Log.e(TAG, "setImageUri", e);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
}
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+
+ if ("content".equals(uri.getScheme())) {
+ InputStream is = null;
+ try {
+ is = getContentResolver().openInputStream(uri);
+ BitmapFactory.decodeStream(is, null, options);
+ } catch (FileNotFoundException e) {
+ Log.d(TAG, e.getMessage(), e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+ }
+ } else {
+ File file = new File(getIntent().getData().getPath());
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ }
+
+ width = options.outWidth;
+ height = options.outHeight;
}
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(new File(getIntent().getData().getPath()).getAbsolutePath(), options);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(getString(R.string.format_crop_result_d_d, options.outWidth, options.outHeight));
+ actionBar.setTitle(getString(R.string.format_crop_result_d_d, width, height));
}
}
@@ -131,6 +168,9 @@ private void saveCroppedImage() {
Toast.makeText(ResultActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, imageUri.toString(), e);
}
+ } else if (BitmapLoadUtils.hasContentScheme(imageUri)){
+ Toast.makeText(ResultActivity.this, getString(R.string.toast_already_saved), Toast.LENGTH_SHORT).show();
+ finish();
} else {
Toast.makeText(ResultActivity.this, getString(R.string.toast_unexpected_error), Toast.LENGTH_SHORT).show();
}
diff --git a/sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java b/sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java
index 0eca122f4..e3f4cf030 100644
--- a/sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java
+++ b/sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java
@@ -2,6 +2,7 @@
import android.Manifest;
import android.annotation.TargetApi;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -11,6 +12,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
@@ -19,8 +21,11 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.Button;
import android.widget.CheckBox;
+import android.widget.CompoundButton;
import android.widget.EditText;
+import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.SeekBar;
@@ -40,9 +45,11 @@
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
+import androidx.core.content.FileProvider;
/**
* Created by Oleksii Shliama (https://github.com/shliama).
@@ -53,9 +60,10 @@ public class SampleActivity extends BaseActivity implements UCropFragmentCallbac
private static final int REQUEST_SELECT_PICTURE = 0x01;
private static final int REQUEST_SELECT_PICTURE_FOR_FRAGMENT = 0x02;
+ private static final int DESTINATION_IMAGE_FILE_REQUEST_CODE = 0x03;
private static final String SAMPLE_CROPPED_IMAGE_NAME = "SampleCropImage";
- private RadioGroup mRadioGroupAspectRatio, mRadioGroupCompressionSettings;
+ private RadioGroup mRadioGroupAspectRatio, mRadioGroupCompressionSettings, mRadioGroupChooseDestination;
private EditText mEditTextMaxWidth, mEditTextMaxHeight;
private EditText mEditTextRatioX, mEditTextRatioY;
private CheckBox mCheckBoxMaxSize;
@@ -80,6 +88,11 @@ public class SampleActivity extends BaseActivity implements UCropFragmentCallbac
private int mStatusBarColor;
private int mToolbarWidgetColor;
+ private TextView mFileDestinationTextView;
+ private CheckBox mCheckBoxUseDocumentProvider;
+ private CheckBox mCheckBoxUseFileProvider;
+ private Uri destinationUri;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -89,6 +102,7 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == requestMode) {
final Uri selectedUri = data.getData();
@@ -99,6 +113,9 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
}
} else if (requestCode == UCrop.REQUEST_CROP) {
handleCropResult(data);
+ } else if (requestCode == DESTINATION_IMAGE_FILE_REQUEST_CODE) {
+ destinationUri = data.getData();
+ mFileDestinationTextView.setText(destinationUri.toString());
}
}
if (resultCode == UCrop.RESULT_ERROR) {
@@ -117,22 +134,134 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
pickFromGallery();
}
break;
+ case REQUEST_STORAGE_WRITE_ACCESS_PERMISSION:
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ showChooseFileDestinationAlertDialog();
+ }
+ break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
+
+ private TextWatcher mAspectRatioTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ mRadioGroupAspectRatio.clearCheck();
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+ };
+ private TextWatcher mMaxSizeTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s != null && !s.toString().trim().isEmpty()) {
+ if (Integer.valueOf(s.toString()) < UCrop.MIN_SIZE) {
+ Toast.makeText(SampleActivity.this, String.format(getString(R.string.format_max_cropped_image_size), UCrop.MIN_SIZE), Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ };
+
+ private abstract class OcCropButtonClickListener implements View.OnClickListener {
+
+ @Override
+ public void onClick(View v) {
+ if (mRadioGroupChooseDestination.getCheckedRadioButtonId() == R.id.radio_choose_destination && destinationUri == null) {
+ Toast.makeText(SampleActivity.this,
+ "Please, select a destination file or set Tmp Files destination option",
+ Toast.LENGTH_LONG).show();
+ } else {
+ onValidatedClick(v);
+ }
+ }
+
+ abstract void onValidatedClick(View v);
+ }
+
+ private class ButtonFileDestinationClickListener implements View.OnClickListener {
+
+ @Override
+ public void onClick(View v) {
+ String destinationFileName = SAMPLE_CROPPED_IMAGE_NAME;
+ String mimeType = "image/jpeg";
+ switch (mRadioGroupCompressionSettings.getCheckedRadioButtonId()) {
+ case R.id.radio_png:
+ mimeType = "image/png";
+ destinationFileName += ".png";
+ break;
+ case R.id.radio_jpeg:
+ mimeType = "image/jpeg";
+ destinationFileName += ".jpg";
+ break;
+ }
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+
+ if (mCheckBoxUseDocumentProvider.isChecked()) {
+
+ Intent createDocumentIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ createDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
+ createDocumentIntent.setType(mimeType);
+ createDocumentIntent.putExtra(Intent.EXTRA_TITLE, destinationFileName);
+
+ if (createDocumentIntent.resolveActivity(getPackageManager()) != null) {
+
+ startActivityForResult(createDocumentIntent, DESTINATION_IMAGE_FILE_REQUEST_CODE);
+ } else {
+ Toast.makeText(SampleActivity.this,
+ R.string.no_file_chooser_error,
+ Toast.LENGTH_LONG).show();
+ }
+ } else if(ActivityCompat.checkSelfPermission(SampleActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ getString(R.string.permission_write_storage_rationale),
+ REQUEST_STORAGE_WRITE_ACCESS_PERMISSION);
+ } else {
+ showChooseFileDestinationAlertDialog();
+ }
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+ && ActivityCompat.checkSelfPermission(SampleActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ getString(R.string.permission_write_storage_rationale),
+ REQUEST_STORAGE_WRITE_ACCESS_PERMISSION);
+ } else {
+ showChooseFileDestinationAlertDialog();
+ }
+ }
+ }
+
@SuppressWarnings("ConstantConditions")
private void setupUI() {
- findViewById(R.id.button_crop).setOnClickListener(new View.OnClickListener() {
+ findViewById(R.id.button_crop).setOnClickListener(new OcCropButtonClickListener() {
@Override
- public void onClick(View v) {
+ public void onValidatedClick(View v) {
pickFromGallery();
}
});
- findViewById(R.id.button_random_image).setOnClickListener(new View.OnClickListener() {
+ findViewById(R.id.button_random_image).setOnClickListener(new OcCropButtonClickListener() {
@Override
- public void onClick(View v) {
+ public void onValidatedClick(View v) {
Random random = new Random();
int minSizePixels = 800;
int maxSizePixels = 2400;
@@ -143,6 +272,47 @@ public void onClick(View v) {
startCrop(uri);
}
});
+ mFileDestinationTextView = findViewById(R.id.file_destination_label);
+ mCheckBoxUseDocumentProvider = findViewById(R.id.checkbox_use_document_provider);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ mCheckBoxUseDocumentProvider.setVisibility(View.VISIBLE);
+ }
+ mCheckBoxUseFileProvider = findViewById(R.id.checkbox_use_file_provider);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ mCheckBoxUseFileProvider.setVisibility(View.VISIBLE);
+ }
+ mCheckBoxUseDocumentProvider.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mCheckBoxUseFileProvider.setEnabled(!isChecked);
+ }
+ });
+ final Button buttonFileDestination = findViewById(R.id.button_file_destination);
+ buttonFileDestination.setOnClickListener(new ButtonFileDestinationClickListener());
+ mRadioGroupChooseDestination = findViewById(R.id.radio_group_choose_destination);
+ mRadioGroupChooseDestination.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ boolean isEnabledChoseDestination = R.id.radio_choose_destination == checkedId;
+ buttonFileDestination.setEnabled(isEnabledChoseDestination);
+ mCheckBoxUseDocumentProvider.setEnabled(isEnabledChoseDestination);
+
+ if (isEnabledChoseDestination) {
+ mFileDestinationTextView.setVisibility(View.VISIBLE);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ mCheckBoxUseFileProvider.setEnabled(true);
+ } else {
+ mCheckBoxUseFileProvider.setEnabled(!mCheckBoxUseDocumentProvider.isChecked());
+ }
+ } else {
+ mCheckBoxUseFileProvider.setEnabled(false);
+ mFileDestinationTextView.setVisibility(View.GONE);
+ mFileDestinationTextView.setText("");
+ destinationUri = null;
+ }
+ }
+ });
+ mRadioGroupChooseDestination.check(R.id.radio_tmp_destination);
settingsView = findViewById(R.id.settings);
mRadioGroupAspectRatio = findViewById(R.id.radio_group_aspect_ratio);
mRadioGroupCompressionSettings = findViewById(R.id.radio_group_compression_settings);
@@ -189,43 +359,96 @@ public void onStopTrackingTouch(SeekBar seekBar) {
mEditTextMaxWidth.addTextChangedListener(mMaxSizeTextWatcher);
}
+ private void showChooseFileDestinationAlertDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Destination File");
- private TextWatcher mAspectRatioTextWatcher = new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- mRadioGroupAspectRatio.clearCheck();
- }
+ builder.setView(R.layout.dialog_file_destination);
+ builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ AlertDialog thisDialog = ((AlertDialog) dialog);
+ RadioGroup radioGroupDirectory = thisDialog.findViewById(R.id.radio_group_directory);
+ EditText editTextFilename = thisDialog.findViewById(R.id.edit_text_file_name);
+
+ String fileName = editTextFilename.getText().toString();
+ if (!fileName.endsWith("jpg")) {
+ fileName = fileName.concat(".jpg");
+ }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
+ File directory = null;
+
+ switch (radioGroupDirectory.getCheckedRadioButtonId()) {
+ case R.id.radio_external_storage_dcim:
+ directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
+ break;
+ case R.id.radio_external_storage_pictures:
+ directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+ break;
+ case R.id.radio_app_external_storage_dcim:
+ directory = SampleActivity.this.getExternalFilesDir(Environment.DIRECTORY_DCIM);
+ break;
+ case R.id.radio_app_external_storage_pictures:
+ directory = SampleActivity.this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ break;
+ }
- }
+ File file = new File(directory, fileName);
+ if (mCheckBoxUseFileProvider.isChecked()) {
+ destinationUri = FileProvider.getUriForFile(SampleActivity.this,
+ getString(R.string.file_provider_authorities),
+ file);
+ } else {
+ destinationUri = Uri.fromFile(file);
+ }
+ mFileDestinationTextView.setText(destinationUri.toString());
+ }
+ });
+ builder.setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Do nothing
+ }
+ });
- @Override
- public void afterTextChanged(Editable s) {
+ // create and show the alert dialog
+ final AlertDialog dialog = builder.create();
+ dialog.show();
- }
- };
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+ EditText editTextFilename = dialog.findViewById(R.id.edit_text_file_name);
+ if(editTextFilename != null) {
+ editTextFilename.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- private TextWatcher mMaxSizeTextWatcher = new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
+ }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
+ }
- @Override
- public void afterTextChanged(Editable s) {
- if (s != null && !s.toString().trim().isEmpty()) {
- if (Integer.valueOf(s.toString()) < UCrop.MIN_SIZE) {
- Toast.makeText(SampleActivity.this, String.format(getString(R.string.format_max_cropped_image_size), UCrop.MIN_SIZE), Toast.LENGTH_SHORT).show();
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s != null && !s.toString().trim().isEmpty() && s.toString().trim().length() >= 3) {
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
+ } else {
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+ }
}
- }
+ });
}
- };
+
+ if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
+ RadioButton externalDcim = dialog.findViewById(R.id.radio_external_storage_dcim);
+ RadioButton externalPictures = dialog.findViewById(R.id.radio_external_storage_pictures);
+ RadioButton appDcim = dialog.findViewById(R.id.radio_app_external_storage_dcim);
+ externalDcim.setEnabled(false);
+ externalPictures.setEnabled(false);
+ appDcim.setChecked(true);
+ }
+ }
private void pickFromGallery() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
@@ -259,7 +482,11 @@ private void startCrop(@NonNull Uri uri) {
break;
}
- UCrop uCrop = UCrop.of(uri, Uri.fromFile(new File(getCacheDir(), destinationFileName)));
+ if (destinationUri == null) {
+ destinationUri = Uri.fromFile(new File(getCacheDir(), destinationFileName));
+ }
+
+ UCrop uCrop = UCrop.of(uri, destinationUri);
uCrop = basisConfig(uCrop);
uCrop = advancedConfig(uCrop);
@@ -378,12 +605,14 @@ Tune everything (ノ◕ヮ◕)ノ*:・゚✧
options.setActiveControlsWidgetColor(ContextCompat.getColor(this, R.color.your_color_res));
// Aspect ratio options
- options.setAspectRatioOptions(1,
+ options.setAspectRatioOptions(2,
new AspectRatio("WOW", 1, 2),
new AspectRatio("MUCH", 3, 4),
new AspectRatio("RATIO", CropImageView.DEFAULT_ASPECT_RATIO, CropImageView.DEFAULT_ASPECT_RATIO),
new AspectRatio("SO", 16, 9),
new AspectRatio("ASPECT", 1, 1));
+ options.withAspectRatio(CropImageView.DEFAULT_ASPECT_RATIO, CropImageView.DEFAULT_ASPECT_RATIO);
+ options.useSourceImageAspectRatio();
*/
@@ -526,7 +755,7 @@ public boolean onCreateOptionsMenu(final Menu menu) {
}
MenuItem menuItemCrop = menu.findItem(R.id.menu_crop);
- Drawable menuItemCropIcon = ContextCompat.getDrawable(this, mToolbarCropDrawable);
+ Drawable menuItemCropIcon = ContextCompat.getDrawable(this, mToolbarCropDrawable == 0 ? R.drawable.ucrop_ic_done : mToolbarCropDrawable);
if (menuItemCropIcon != null) {
menuItemCropIcon.mutate();
menuItemCropIcon.setColorFilter(mToolbarWidgetColor, PorterDuff.Mode.SRC_ATOP);
@@ -546,7 +775,7 @@ public boolean onPrepareOptionsMenu(Menu menu) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_crop) {
- if (fragment.isAdded())
+ if (fragment != null && fragment.isAdded())
fragment.cropAndSaveImage();
} else if (item.getItemId() == android.R.id.home) {
removeFragmentFromScreen();
diff --git a/sample/src/main/java/com/yalantis/ucrop/sample/SampleApp.java b/sample/src/main/java/com/yalantis/ucrop/sample/SampleApp.java
new file mode 100644
index 000000000..80e746cff
--- /dev/null
+++ b/sample/src/main/java/com/yalantis/ucrop/sample/SampleApp.java
@@ -0,0 +1,33 @@
+package com.yalantis.ucrop.sample;
+
+import android.app.Application;
+
+import com.yalantis.ucrop.UCropInitializer;
+
+import java.util.Collections;
+
+import okhttp3.ConnectionSpec;
+import okhttp3.OkHttpClient;
+
+public class SampleApp extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ setUcropHttpClient();
+ }
+
+ private void setUcropHttpClient() {
+ ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+ .allEnabledCipherSuites()
+ .allEnabledTlsVersions()
+ .build();
+
+ OkHttpClient client = new OkHttpClient.Builder()
+ .connectionSpecs(Collections.singletonList(cs))
+ .build();
+
+ new UCropInitializer()
+ .setOkHttpClient(client);
+ }
+}
diff --git a/sample/src/main/res/drawable/bg_button.xml b/sample/src/main/res/drawable/bg_button.xml
new file mode 100644
index 000000000..d35c3fcc0
--- /dev/null
+++ b/sample/src/main/res/drawable/bg_button.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/drawable/bg_text_border.xml b/sample/src/main/res/drawable/bg_text_border.xml
new file mode 100644
index 000000000..842888763
--- /dev/null
+++ b/sample/src/main/res/drawable/bg_text_border.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_result.xml b/sample/src/main/res/layout/activity_result.xml
index 27d854983..c033f67b5 100755
--- a/sample/src/main/res/layout/activity_result.xml
+++ b/sample/src/main/res/layout/activity_result.xml
@@ -15,6 +15,16 @@
app:title="@string/format_crop_result_d_d"
app:titleTextColor="@android:color/white"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/include_settings.xml b/sample/src/main/res/layout/include_settings.xml
index abdaae8bf..c5340e792 100644
--- a/sample/src/main/res/layout/include_settings.xml
+++ b/sample/src/main/res/layout/include_settings.xml
@@ -55,6 +55,85 @@
android:textColor="@android:color/white"
android:textStyle="bold" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -180,6 +260,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:maxLength="9"
android:gravity="center"
android:hint="@string/label_height"
android:inputType="number" />
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
index 4f44e31ee..9291d0d0b 100644
--- a/sample/src/main/res/values/colors.xml
+++ b/sample/src/main/res/values/colors.xml
@@ -3,6 +3,7 @@
#FF6E40
#CC5833
#FF6E40
+ #80FF6E40
#03A9F4
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
index c77ebb6b4..0b6ceeed0 100644
--- a/sample/src/main/res/values/strings.xml
+++ b/sample/src/main/res/values/strings.xml
@@ -33,6 +33,7 @@
Quality: %d
%1$dx%2$d px
Max cropped image size cannot be less then %d
+ Too big resolution
Permission needed
Storage read permission is needed to pick files.
@@ -41,8 +42,30 @@
Cannot retrieve selected image
Cannot retrieve cropped image
Unexpected error
+ Your image is already saved
com.yalantis.ucrop.provider
ucrop_chanel
ucrop result image
+ Choose File Destination
+ No application found on device to open view
+
+
+ Choose the Destination Directory
+ Shared Storage DCIM
+ Shared Storage Pictures
+ App External Storage DCIM
+ App External Storage Pictures
+ File name
+ @string/file_name
+
+ Tmp File destination
+ Choose destination
+ Use document provider for creating or choosing the destination file
+ Use a file provider for creating the file destination URI
+
+
+ Warning: On Android versions lower then Lollipop, if the output file URI has a \"content\"
+ schema, it is not possible to preserve the Exif info!
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 000000000..4463e264d
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ucrop/build.gradle b/ucrop/build.gradle
index c29656f81..88782b79d 100644
--- a/ucrop/build.gradle
+++ b/ucrop/build.gradle
@@ -2,14 +2,14 @@ apply plugin: 'com.android.library'
apply from: '../mavenpush.gradle'
android {
- compileSdkVersion 28
- buildToolsVersion '28.0.3'
+ compileSdkVersion 31
+ buildToolsVersion '30.0.2'
defaultConfig {
minSdkVersion 14
- targetSdkVersion 28
- versionCode 24
- versionName "2.2.2-non-native"
+ targetSdkVersion 31
+ versionCode 26
+ versionName "2.2.8-native"
vectorDrawables.useSupportLibrary = true
}
@@ -35,5 +35,6 @@ dependencies {
implementation "androidx.appcompat:appcompat:${androidx_appcompat_version}"
implementation "androidx.exifinterface:exifinterface:${androidx_exifinterface_version}"
implementation "androidx.transition:transition:${androidx_transition_version}"
- implementation "com.squareup.okhttp3:okhttp:3.12.1"
+ // OkHttp3 versions above 3.12.x don't support pre-Lollipop Android versions (API 21)
+ implementation "com.squareup.okhttp3:okhttp:3.12.13"
}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/OkHttpClientStore.java b/ucrop/src/main/java/com/yalantis/ucrop/OkHttpClientStore.java
new file mode 100644
index 000000000..8b01a8025
--- /dev/null
+++ b/ucrop/src/main/java/com/yalantis/ucrop/OkHttpClientStore.java
@@ -0,0 +1,35 @@
+package com.yalantis.ucrop;
+
+import androidx.annotation.NonNull;
+
+import okhttp3.OkHttpClient;
+
+public class OkHttpClientStore {
+
+ private OkHttpClientStore() {}
+
+ public final static OkHttpClientStore INSTANCE = new OkHttpClientStore();
+
+ private OkHttpClient client;
+
+ /**
+ * @return stored OkHttpClient if it was already set,
+ * or just an instance created via empty constructor
+ * and store it
+ */
+ @NonNull
+ public OkHttpClient getClient() {
+ if (client == null) {
+ client = new OkHttpClient();
+ }
+ return client;
+ }
+
+ /**
+ * @param client OkHttpClient for downloading bitmap form remote Uri,
+ * it may contain any preferences you need
+ */
+ void setClient(@NonNull OkHttpClient client) {
+ this.client = client;
+ }
+}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java b/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java
index 878191c1b..0b6dce23a 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java
@@ -1,5 +1,6 @@
package com.yalantis.ucrop;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -123,7 +124,7 @@ public UCrop withOptions(@NonNull Options options) {
*
* @param activity Activity to receive result
*/
- public void start(@NonNull AppCompatActivity activity) {
+ public void start(@NonNull Activity activity) {
start(activity, REQUEST_CROP);
}
@@ -133,7 +134,7 @@ public void start(@NonNull AppCompatActivity activity) {
* @param activity Activity to receive result
* @param requestCode requestCode for result
*/
- public void start(@NonNull AppCompatActivity activity, int requestCode) {
+ public void start(@NonNull Activity activity, int requestCode) {
activity.startActivityForResult(getIntent(activity), requestCode);
}
@@ -216,7 +217,7 @@ public static int getOutputImageHeight(@NonNull Intent intent) {
* @return aspect ratio as a floating point value (x:y) - so it will be 1 for 1:1 or 4/3 for 4:3
*/
public static float getOutputCropAspectRatio(@NonNull Intent intent) {
- return intent.getParcelableExtra(EXTRA_OUTPUT_CROP_ASPECT_RATIO);
+ return intent.getFloatExtra(EXTRA_OUTPUT_CROP_ASPECT_RATIO, 0f);
}
/**
@@ -230,7 +231,6 @@ public static Throwable getError(@NonNull Intent result) {
return (Throwable) result.getSerializableExtra(EXTRA_ERROR);
}
-
/**
* Class that helps to setup advanced configs that are not commonly used.
* Use it with method {@link #withOptions(Options)}
@@ -487,9 +487,9 @@ public void setFreeStyleCropEnabled(boolean enabled) {
* @param aspectRatio - list of aspect ratio options that are available to user
*/
public void setAspectRatioOptions(int selectedByDefault, AspectRatio... aspectRatio) {
- if (selectedByDefault > aspectRatio.length) {
+ if (selectedByDefault >= aspectRatio.length) {
throw new IllegalArgumentException(String.format(Locale.US,
- "Index [selectedByDefault = %d] cannot be higher than aspect ratio options count [count = %d].",
+ "Index [selectedByDefault = %d] (0-based) cannot be higher or equal than aspect ratio options count [count = %d].",
selectedByDefault, aspectRatio.length));
}
mOptionBundle.putInt(EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, selectedByDefault);
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java b/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java
index fae5dc9ea..96c4b7bd3 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java
@@ -255,20 +255,21 @@ private void processOptions(@NonNull Intent intent) {
mOverlayView.setCropGridStrokeWidth(intent.getIntExtra(UCrop.Options.EXTRA_CROP_GRID_STROKE_WIDTH, getResources().getDimensionPixelSize(R.dimen.ucrop_default_crop_grid_stoke_width)));
// Aspect ratio options
- float aspectRatioX = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_X, 0);
- float aspectRatioY = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_Y, 0);
+ float aspectRatioX = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_X, -1);
+ float aspectRatioY = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_Y, -1);
int aspectRationSelectedByDefault = intent.getIntExtra(UCrop.Options.EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, 0);
ArrayList aspectRatioList = intent.getParcelableArrayListExtra(UCrop.Options.EXTRA_ASPECT_RATIO_OPTIONS);
- if (aspectRatioX > 0 && aspectRatioY > 0) {
+ if (aspectRatioX >= 0 && aspectRatioY >= 0) {
if (mWrapperStateAspectRatio != null) {
mWrapperStateAspectRatio.setVisibility(View.GONE);
}
- mGestureCropImageView.setTargetAspectRatio(aspectRatioX / aspectRatioY);
+ float targetAspectRatio = aspectRatioX / aspectRatioY;
+ mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else if (aspectRatioList != null && aspectRationSelectedByDefault < aspectRatioList.size()) {
- mGestureCropImageView.setTargetAspectRatio(aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioX() /
- aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioY());
+ float targetAspectRatio = aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioX() / aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioY();
+ mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else {
mGestureCropImageView.setTargetAspectRatio(CropImageView.SOURCE_IMAGE_ASPECT_RATIO);
}
@@ -305,7 +306,6 @@ private void setupViews(@NonNull Intent intent) {
ViewGroup viewGroup = findViewById(R.id.ucrop_photobox);
ViewGroup wrapper = viewGroup.findViewById(R.id.controls_wrapper);
wrapper.setVisibility(View.VISIBLE);
- wrapper.setBackgroundColor(mRootViewBackgroundColor);
LayoutInflater.from(this).inflate(R.layout.ucrop_controls, wrapper, true);
mControlsTransition = new AutoTransition();
@@ -367,6 +367,11 @@ private void initiateRootViews() {
((ImageView) findViewById(R.id.image_view_logo)).setColorFilter(mLogoColor, PorterDuff.Mode.SRC_ATOP);
findViewById(R.id.ucrop_frame).setBackgroundColor(mRootViewBackgroundColor);
+ if (!mShowBottomControls) {
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) findViewById(R.id.ucrop_frame).getLayoutParams();
+ params.bottomMargin = 0;
+ findViewById(R.id.ucrop_frame).requestLayout();
+ }
}
private TransformImageView.TransformImageListener mImageListener = new TransformImageView.TransformImageListener() {
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java b/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java
index 44545b609..ab7950333 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java
@@ -149,7 +149,6 @@ public void setupViews(View view, Bundle args) {
ViewGroup wrapper = view.findViewById(R.id.controls_wrapper);
wrapper.setVisibility(View.VISIBLE);
- wrapper.setBackgroundColor(mRootViewBackgroundColor);
LayoutInflater.from(getContext()).inflate(R.layout.ucrop_controls, wrapper, true);
mControlsTransition = new AutoTransition();
@@ -170,6 +169,10 @@ public void setupViews(View view, Bundle args) {
setupRotateWidget(view);
setupScaleWidget(view);
setupStatesWrapper(view);
+ } else {
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.findViewById(R.id.ucrop_frame).getLayoutParams();
+ params.bottomMargin = 0;
+ view.findViewById(R.id.ucrop_frame).requestLayout();
}
}
@@ -233,20 +236,21 @@ private void processOptions(@NonNull Bundle bundle) {
mOverlayView.setCropGridStrokeWidth(bundle.getInt(UCrop.Options.EXTRA_CROP_GRID_STROKE_WIDTH, getResources().getDimensionPixelSize(R.dimen.ucrop_default_crop_grid_stoke_width)));
// Aspect ratio options
- float aspectRatioX = bundle.getFloat(UCrop.EXTRA_ASPECT_RATIO_X, 0);
- float aspectRatioY = bundle.getFloat(UCrop.EXTRA_ASPECT_RATIO_Y, 0);
+ float aspectRatioX = bundle.getFloat(UCrop.EXTRA_ASPECT_RATIO_X, -1);
+ float aspectRatioY = bundle.getFloat(UCrop.EXTRA_ASPECT_RATIO_Y, -1);
int aspectRationSelectedByDefault = bundle.getInt(UCrop.Options.EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, 0);
ArrayList aspectRatioList = bundle.getParcelableArrayList(UCrop.Options.EXTRA_ASPECT_RATIO_OPTIONS);
- if (aspectRatioX > 0 && aspectRatioY > 0) {
+ if (aspectRatioX >= 0 && aspectRatioY >= 0) {
if (mWrapperStateAspectRatio != null) {
mWrapperStateAspectRatio.setVisibility(View.GONE);
}
- mGestureCropImageView.setTargetAspectRatio(aspectRatioX / aspectRatioY);
+ float targetAspectRatio = aspectRatioX / aspectRatioY;
+ mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else if (aspectRatioList != null && aspectRationSelectedByDefault < aspectRatioList.size()) {
- mGestureCropImageView.setTargetAspectRatio(aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioX() /
- aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioY());
+ float targetAspectRatio = aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioX() / aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioY();
+ mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else {
mGestureCropImageView.setTargetAspectRatio(CropImageView.SOURCE_IMAGE_ASPECT_RATIO);
}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/UCropInitializer.java b/ucrop/src/main/java/com/yalantis/ucrop/UCropInitializer.java
new file mode 100644
index 000000000..616bc75bf
--- /dev/null
+++ b/ucrop/src/main/java/com/yalantis/ucrop/UCropInitializer.java
@@ -0,0 +1,18 @@
+package com.yalantis.ucrop;
+
+import androidx.annotation.NonNull;
+
+import okhttp3.OkHttpClient;
+
+public class UCropInitializer {
+
+ /**
+ * @param client OkHttpClient for downloading bitmap form remote Uri,
+ * it may contain any preferences you need
+ */
+ public UCropInitializer setOkHttpClient(@NonNull OkHttpClient client) {
+ OkHttpClientStore.INSTANCE.setClient(client);
+ return this;
+ }
+
+}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapLoadCallback.java b/ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapLoadCallback.java
index 91815bd0f..2f0966640 100755
--- a/ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapLoadCallback.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapLoadCallback.java
@@ -1,6 +1,7 @@
package com.yalantis.ucrop.callback;
import android.graphics.Bitmap;
+import android.net.Uri;
import com.yalantis.ucrop.model.ExifInfo;
@@ -9,7 +10,7 @@
public interface BitmapLoadCallback {
- void onBitmapLoaded(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo, @NonNull String imageInputPath, @Nullable String imageOutputPath);
+ void onBitmapLoaded(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo, @NonNull Uri imageInputUri, @Nullable Uri imageOutputUri);
void onFailure(@NonNull Exception bitmapWorkerException);
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java b/ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java
index 17d8a7e72..543417e9a 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java
@@ -1,6 +1,7 @@
package com.yalantis.ucrop.model;
import android.graphics.Bitmap;
+import android.net.Uri;
/**
* Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.
@@ -14,6 +15,8 @@ public class CropParameters {
private String mImageInputPath, mImageOutputPath;
private ExifInfo mExifInfo;
+ private Uri mContentImageInputUri, mContentImageOutputUri;
+
public CropParameters(int maxResultImageSizeX, int maxResultImageSizeY,
Bitmap.CompressFormat compressFormat, int compressQuality,
@@ -55,4 +58,19 @@ public ExifInfo getExifInfo() {
return mExifInfo;
}
+ public Uri getContentImageInputUri() {
+ return mContentImageInputUri;
+ }
+
+ public void setContentImageInputUri(Uri mContentImageInputUri) {
+ this.mContentImageInputUri = mContentImageInputUri;
+ }
+
+ public Uri getContentImageOutputUri() {
+ return mContentImageOutputUri;
+ }
+
+ public void setContentImageOutputUri(Uri mContentImageOutputUri) {
+ this.mContentImageOutputUri = mContentImageOutputUri;
+ }
}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java b/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java
index 3abbc35d6..c2f34d511 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java
@@ -6,8 +6,13 @@
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.exifinterface.media.ExifInterface;
+
import com.yalantis.ucrop.callback.BitmapCropCallback;
import com.yalantis.ucrop.model.CropParameters;
import com.yalantis.ucrop.model.ExifInfo;
@@ -16,16 +21,12 @@
import com.yalantis.ucrop.util.FileUtils;
import com.yalantis.ucrop.util.ImageHeaderParser;
+import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.exifinterface.media.ExifInterface;
-
/**
* Crops part of image that fills the crop bounds.
*
@@ -37,6 +38,8 @@ public class BitmapCropTask extends AsyncTask {
private static final String TAG = "BitmapCropTask";
+ private static final String CONTENT_SCHEME = "content";
+
private final WeakReference mContext;
private Bitmap mViewBitmap;
@@ -50,6 +53,7 @@ public class BitmapCropTask extends AsyncTask {
private final Bitmap.CompressFormat mCompressFormat;
private final int mCompressQuality;
private final String mImageInputPath, mImageOutputPath;
+ private final Uri mImageInputUri, mImageOutputUri;
private final ExifInfo mExifInfo;
private final BitmapCropCallback mCropCallback;
@@ -75,6 +79,8 @@ public BitmapCropTask(@NonNull Context context, @Nullable Bitmap viewBitmap, @No
mImageInputPath = cropParameters.getImageInputPath();
mImageOutputPath = cropParameters.getImageOutputPath();
+ mImageInputUri = cropParameters.getContentImageInputUri();
+ mImageOutputUri = cropParameters.getContentImageOutputUri();
mExifInfo = cropParameters.getExifInfo();
mCropCallback = cropCallback;
@@ -91,6 +97,9 @@ protected Throwable doInBackground(Void... params) {
return new NullPointerException("CurrentImageRect is empty");
}
+ if (mImageOutputUri == null) {
+ return new NullPointerException("ImageOutputUri is null");
+ }
try {
crop();
@@ -102,8 +111,12 @@ protected Throwable doInBackground(Void... params) {
return null;
}
-
private boolean crop() throws IOException {
+ Context context = mContext.get();
+ if (context == null) {
+ return false;
+ }
+
// Downsize if needed
if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) {
float cropWidth = mCropRect.width() / mCurrentScale;
@@ -149,31 +162,67 @@ private boolean crop() throws IOException {
Log.i(TAG, "Should crop: " + shouldCrop);
if (shouldCrop) {
- ExifInterface originalExif = new ExifInterface(mImageInputPath);
saveImage(Bitmap.createBitmap(mViewBitmap, cropOffsetX, cropOffsetY, mCroppedImageWidth, mCroppedImageHeight));
if (mCompressFormat.equals(Bitmap.CompressFormat.JPEG)) {
- ImageHeaderParser.copyExif(originalExif, mCroppedImageWidth, mCroppedImageHeight, mImageOutputPath);
+ copyExifForOutputFile(context);
}
return true;
} else {
- FileUtils.copyFile(mImageInputPath, mImageOutputPath);
+ FileUtils.copyFile(context ,mImageInputUri, mImageOutputUri);
return false;
}
}
- private void saveImage(@NonNull Bitmap croppedBitmap) throws FileNotFoundException {
+ private void copyExifForOutputFile(Context context) throws IOException {
+ boolean hasImageInputUriContentSchema = BitmapLoadUtils.hasContentScheme(mImageInputUri);
+ boolean hasImageOutputUriContentSchema = BitmapLoadUtils.hasContentScheme(mImageOutputUri);
+ /*
+ * ImageHeaderParser.copyExif with output uri as a parameter
+ * uses ExifInterface constructor with FileDescriptor param for overriding output file exif info,
+ * which doesn't support ExitInterface.saveAttributes call for SDK lower than 21.
+ *
+ * See documentation for ImageHeaderParser.copyExif and ExifInterface.saveAttributes implementation.
+ */
+ if (hasImageInputUriContentSchema && hasImageOutputUriContentSchema) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ImageHeaderParser.copyExif(context, mCroppedImageWidth, mCroppedImageHeight, mImageInputUri, mImageOutputUri);
+ } else {
+ Log.e(TAG, "It is not possible to write exif info into file represented by \"content\" Uri if Android < LOLLIPOP");
+ }
+ } else if (hasImageInputUriContentSchema) {
+ ImageHeaderParser.copyExif(context, mCroppedImageWidth, mCroppedImageHeight, mImageInputUri, mImageOutputPath);
+ } else if (hasImageOutputUriContentSchema) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ExifInterface originalExif = new ExifInterface(mImageInputPath);
+ ImageHeaderParser.copyExif(context, originalExif, mCroppedImageWidth, mCroppedImageHeight, mImageOutputUri);
+ } else {
+ Log.e(TAG, "It is not possible to write exif info into file represented by \"content\" Uri if Android < LOLLIPOP");
+ }
+ } else {
+ ExifInterface originalExif = new ExifInterface(mImageInputPath);
+ ImageHeaderParser.copyExif(originalExif, mCroppedImageWidth, mCroppedImageHeight, mImageOutputPath);
+ }
+ }
+
+ private void saveImage(@NonNull Bitmap croppedBitmap) {
Context context = mContext.get();
if (context == null) {
return;
}
OutputStream outputStream = null;
+ ByteArrayOutputStream outStream = null;
try {
- outputStream = context.getContentResolver().openOutputStream(Uri.fromFile(new File(mImageOutputPath)));
- croppedBitmap.compress(mCompressFormat, mCompressQuality, outputStream);
+ outputStream = context.getContentResolver().openOutputStream(mImageOutputUri);
+ outStream = new ByteArrayOutputStream();
+ croppedBitmap.compress(mCompressFormat, mCompressQuality, outStream);
+ outputStream.write(outStream.toByteArray());
croppedBitmap.recycle();
+ } catch (IOException exc) {
+ Log.e(TAG, exc.getLocalizedMessage());
} finally {
BitmapLoadUtils.close(outputStream);
+ BitmapLoadUtils.close(outStream);
}
}
@@ -200,7 +249,13 @@ private boolean shouldCrop(int width, int height) {
protected void onPostExecute(@Nullable Throwable t) {
if (mCropCallback != null) {
if (t == null) {
- Uri uri = Uri.fromFile(new File(mImageOutputPath));
+ Uri uri;
+
+ if (BitmapLoadUtils.hasContentScheme(mImageOutputUri)) {
+ uri = mImageOutputUri;
+ } else {
+ uri = Uri.fromFile(new File(mImageOutputPath));
+ }
mCropCallback.onBitmapCropped(uri, cropOffsetX, cropOffsetY, mCroppedImageWidth, mCroppedImageHeight);
} else {
mCropCallback.onCropFailure(t);
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java b/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java
index 61235a158..fa7adaef3 100755
--- a/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java
@@ -11,15 +11,15 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.yalantis.ucrop.OkHttpClientStore;
import com.yalantis.ucrop.callback.BitmapLoadCallback;
import com.yalantis.ucrop.model.ExifInfo;
import com.yalantis.ucrop.util.BitmapLoadUtils;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.ref.WeakReference;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -37,7 +37,9 @@ public class BitmapLoadTask extends AsyncTask mContext;
private Uri mInputUri;
private Uri mOutputUri;
private final int mRequiredWidth;
@@ -66,7 +68,7 @@ public BitmapLoadTask(@NonNull Context context,
@NonNull Uri inputUri, @Nullable Uri outputUri,
int requiredWidth, int requiredHeight,
BitmapLoadCallback loadCallback) {
- mContext = context;
+ mContext = new WeakReference<>(context);
mInputUri = inputUri;
mOutputUri = outputUri;
mRequiredWidth = requiredWidth;
@@ -77,6 +79,11 @@ public BitmapLoadTask(@NonNull Context context,
@Override
@NonNull
protected BitmapWorkerResult doInBackground(Void... params) {
+ Context context = mContext.get();
+ if (context == null) {
+ return new BitmapWorkerResult(new NullPointerException("context is null"));
+ }
+
if (mInputUri == null) {
return new BitmapWorkerResult(new NullPointerException("Input Uri cannot be null"));
}
@@ -97,7 +104,7 @@ protected BitmapWorkerResult doInBackground(Void... params) {
boolean decodeAttemptSuccess = false;
while (!decodeAttemptSuccess) {
try {
- InputStream stream = mContext.getContentResolver().openInputStream(mInputUri);
+ InputStream stream = context.getContentResolver().openInputStream(mInputUri);
try {
decodeSampledBitmap = BitmapFactory.decodeStream(stream, null, options);
if (options.outWidth == -1 || options.outHeight == -1) {
@@ -106,6 +113,7 @@ protected BitmapWorkerResult doInBackground(Void... params) {
} finally {
BitmapLoadUtils.close(stream);
}
+ if (checkSize(decodeSampledBitmap, options)) continue;
decodeAttemptSuccess = true;
} catch (OutOfMemoryError error) {
Log.e(TAG, "doInBackground: BitmapFactory.decodeFileDescriptor: ", error);
@@ -120,7 +128,7 @@ protected BitmapWorkerResult doInBackground(Void... params) {
return new BitmapWorkerResult(new IllegalArgumentException("Bitmap could not be decoded from the Uri: [" + mInputUri + "]"));
}
- int exifOrientation = BitmapLoadUtils.getExifOrientation(mContext, mInputUri);
+ int exifOrientation = BitmapLoadUtils.getExifOrientation(context, mInputUri);
int exifDegrees = BitmapLoadUtils.exifToDegrees(exifOrientation);
int exifTranslation = BitmapLoadUtils.exifToTranslation(exifOrientation);
@@ -150,50 +158,12 @@ private void processInputUri() throws NullPointerException, IOException {
Log.e(TAG, "Downloading failed", e);
throw e;
}
- } else if ("content".equals(inputUriScheme)) {
- try {
- copyFile(mInputUri, mOutputUri);
- } catch (NullPointerException | IOException e) {
- Log.e(TAG, "Copying failed", e);
- throw e;
- }
- } else if (!"file".equals(inputUriScheme)) {
+ } else if (!"file".equals(inputUriScheme) && !"content".equals(inputUriScheme)) {
Log.e(TAG, "Invalid Uri scheme " + inputUriScheme);
throw new IllegalArgumentException("Invalid Uri scheme" + inputUriScheme);
}
}
- private void copyFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException {
- Log.d(TAG, "copyFile");
-
- if (outputUri == null) {
- throw new NullPointerException("Output Uri is null - cannot copy image");
- }
-
- InputStream inputStream = null;
- OutputStream outputStream = null;
- try {
- inputStream = mContext.getContentResolver().openInputStream(inputUri);
- outputStream = new FileOutputStream(new File(outputUri.getPath()));
- if (inputStream == null) {
- throw new NullPointerException("InputStream for given input Uri is null");
- }
-
- byte buffer[] = new byte[1024];
- int length;
- while ((length = inputStream.read(buffer)) > 0) {
- outputStream.write(buffer, 0, length);
- }
- } finally {
- BitmapLoadUtils.close(outputStream);
- BitmapLoadUtils.close(inputStream);
-
- // swap uris, because input image was copied to the output destination
- // (cropped image will override it later)
- mInputUri = mOutputUri;
- }
- }
-
private void downloadFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException {
Log.d(TAG, "downloadFile");
@@ -201,19 +171,24 @@ private void downloadFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws
throw new NullPointerException("Output Uri is null - cannot download image");
}
- OkHttpClient client = new OkHttpClient();
+ Context context = mContext.get();
+ if (context == null) {
+ throw new NullPointerException("Context is null");
+ }
+
+ OkHttpClient client = OkHttpClientStore.INSTANCE.getClient();
BufferedSource source = null;
Sink sink = null;
Response response = null;
try {
Request request = new Request.Builder()
- .url(inputUri.toString())
- .build();
+ .url(inputUri.toString())
+ .build();
response = client.newCall(request).execute();
source = response.body().source();
- OutputStream outputStream = mContext.getContentResolver().openOutputStream(outputUri);
+ OutputStream outputStream = context.getContentResolver().openOutputStream(outputUri);
if (outputStream != null) {
sink = Okio.sink(outputStream);
source.readAll(sink);
@@ -237,10 +212,18 @@ private void downloadFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws
@Override
protected void onPostExecute(@NonNull BitmapWorkerResult result) {
if (result.mBitmapWorkerException == null) {
- mBitmapLoadCallback.onBitmapLoaded(result.mBitmapResult, result.mExifInfo, mInputUri.getPath(), (mOutputUri == null) ? null : mOutputUri.getPath());
+ mBitmapLoadCallback.onBitmapLoaded(result.mBitmapResult, result.mExifInfo, mInputUri, mOutputUri);
} else {
mBitmapLoadCallback.onFailure(result.mBitmapWorkerException);
}
}
+ private boolean checkSize(Bitmap bitmap, BitmapFactory.Options options) {
+ int bitmapSize = bitmap != null ? bitmap.getByteCount() : 0;
+ if (bitmapSize > MAX_BITMAP_SIZE) {
+ options.inSampleSize *= 2;
+ return true;
+ }
+ return false;
+ }
}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/util/BitmapLoadUtils.java b/ucrop/src/main/java/com/yalantis/ucrop/util/BitmapLoadUtils.java
index 467ad43a4..136bc145a 100755
--- a/ucrop/src/main/java/com/yalantis/ucrop/util/BitmapLoadUtils.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/util/BitmapLoadUtils.java
@@ -12,6 +12,10 @@
import android.view.Display;
import android.view.WindowManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.exifinterface.media.ExifInterface;
+
import com.yalantis.ucrop.callback.BitmapLoadCallback;
import com.yalantis.ucrop.task.BitmapLoadTask;
@@ -19,15 +23,13 @@
import java.io.IOException;
import java.io.InputStream;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.exifinterface.media.ExifInterface;
-
/**
* Created by Oleksii Shliama (https://github.com/shliama).
*/
public class BitmapLoadUtils {
+ private static final String CONTENT_SCHEME = "content";
+
private static final String TAG = "BitmapLoadUtils";
public static void decodeBitmapInBackground(@NonNull Context context,
@@ -171,4 +173,8 @@ public static void close(@Nullable Closeable c) {
}
}
+ public static boolean hasContentScheme(Uri uri) {
+ return uri != null && CONTENT_SCHEME.equals(uri.getScheme());
+ }
+
}
\ No newline at end of file
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/util/FileUtils.java b/ucrop/src/main/java/com/yalantis/ucrop/util/FileUtils.java
index 913bf8c4e..29199c63e 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/util/FileUtils.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/util/FileUtils.java
@@ -32,6 +32,8 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.Locale;
@@ -215,6 +217,9 @@ else if ("file".equalsIgnoreCase(uri.getScheme())) {
* In the event that the paths are the same, trying to copy one file to the other
* will cause both files to become null.
* Simply skipping this step if the paths are identical.
+ *
+ * @param pathFrom Represents the source file
+ * @param pathTo Represents the destination file
*/
public static void copyFile(@NonNull String pathFrom, @NonNull String pathTo) throws IOException {
if (pathFrom.equalsIgnoreCase(pathTo)) {
@@ -227,11 +232,45 @@ public static void copyFile(@NonNull String pathFrom, @NonNull String pathTo) th
inputChannel = new FileInputStream(new File(pathFrom)).getChannel();
outputChannel = new FileOutputStream(new File(pathTo)).getChannel();
inputChannel.transferTo(0, inputChannel.size(), outputChannel);
- inputChannel.close();
} finally {
if (inputChannel != null) inputChannel.close();
if (outputChannel != null) outputChannel.close();
}
}
+ /**
+ * Copies one file into the other with the given Uris.
+ * In the event that the Uris are the same, trying to copy one file to the other
+ * will cause both files to become null.
+ * Simply skipping this step if the paths are identical.
+ *
+ * @param context The context from which to require the {@link android.content.ContentResolver}
+ * @param uriFrom Represents the source file
+ * @param uriTo Represents the destination file
+ */
+ public static void copyFile(@NonNull Context context, @NonNull Uri uriFrom, @NonNull Uri uriTo) throws IOException {
+ if (uriFrom.equals(uriTo)) {
+ return;
+ }
+
+ InputStream isFrom = null;
+ OutputStream osTo = null;
+ try {
+ isFrom = context.getContentResolver().openInputStream(uriFrom);
+ osTo = context.getContentResolver().openOutputStream(uriTo);
+
+ if (isFrom instanceof FileInputStream && osTo instanceof FileOutputStream) {
+ FileChannel inputChannel = ((FileInputStream)isFrom).getChannel();
+ FileChannel outputChannel = ((FileOutputStream)osTo).getChannel();
+ inputChannel.transferTo(0, inputChannel.size(), outputChannel);
+ } else {
+ throw new IllegalArgumentException("The input or output URI don't represent a file. " +
+ "uCrop requires then to represent files in order to work properly.");
+ }
+ } finally {
+ if (isFrom != null) isFrom.close();
+ if (osTo != null) osTo.close();
+ }
+ }
+
}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/util/ImageHeaderParser.java b/ucrop/src/main/java/com/yalantis/ucrop/util/ImageHeaderParser.java
index ebde7c5eb..ba328407e 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/util/ImageHeaderParser.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/util/ImageHeaderParser.java
@@ -30,15 +30,21 @@
package com.yalantis.ucrop.util;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
+import androidx.annotation.RequiresApi;
import androidx.exifinterface.media.ExifInterface;
/**
@@ -376,7 +382,169 @@ public int read(byte[] buffer, int byteCount) throws IOException {
}
}
+ /**
+ * Copy exif information represented by originalExif into the file represented by imageOutputPath.
+ *
+ * @param originalExif The exif info from the original input file
+ * @param width output image new width
+ * @param height output image new height
+ * @param imageOutputPath The path to the output file
+ */
public static void copyExif(ExifInterface originalExif, int width, int height, String imageOutputPath) {
+
+ try {
+ ExifInterface newExif = new ExifInterface(imageOutputPath);
+
+ copyExifAttributes(originalExif, newExif, width, height);
+
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage());
+ }
+ }
+
+ /**
+ * Copy exif information from the file represented by imageInputUri into the file represented by imageOutputPath and
+ * overwrites it's width and height with the given ones.
+ *
+ * @param context The context from which to obtain a content resolver
+ * @param width output image new width
+ * @param height output image new height
+ * @param imageInputUri The {@link Uri} that represents the input file
+ * @param imageOutputPath The path to the output file
+ */
+ public static void copyExif(Context context, int width, int height, Uri imageInputUri, String imageOutputPath) {
+ if(context == null) {
+ Log.d(TAG, "context is null");
+ return;
+ }
+
+ InputStream ins = null;
+ try {
+ ins = context.getContentResolver().openInputStream(imageInputUri);
+ ExifInterface originalExif = new ExifInterface(ins);
+
+ ExifInterface newExif = new ExifInterface(imageOutputPath);
+
+ copyExifAttributes(originalExif, newExif, width, height);
+
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage(), e);
+ } finally {
+ if (ins != null) {
+ try {
+ ins.close();
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Copy exif information from the file represented by imageInputUri into the file represented by imageOutputUri and
+ * overwrites it's width and height with the given ones.
+ * This is done by {@link ExifInterface} through a seekable {@link FileDescriptor} and this is only possible
+ * starting on Lollipop version of Android.
+ *
+ * @param context The context from which to obtain a content resolver
+ * @param width output image new width
+ * @param height output image new height
+ * @param imageInputUri The {@link Uri} that represents the input file
+ * @param imageOutputUri The {@link Uri} that represents the output file
+ */
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void copyExif(Context context, int width, int height, Uri imageInputUri, Uri imageOutputUri) {
+ if(context == null) {
+ Log.d(TAG, "context is null");
+ return;
+ }
+
+ InputStream ins = null;
+ ParcelFileDescriptor outFd = null;
+ try {
+ ins = context.getContentResolver().openInputStream(imageInputUri);
+ ExifInterface originalExif = new ExifInterface(ins);
+
+ outFd = context.getContentResolver().openFileDescriptor(imageOutputUri, "rw");
+ ExifInterface newExif = new ExifInterface(outFd.getFileDescriptor());
+ copyExifAttributes(originalExif, newExif, width, height);
+
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage(), e);
+ } finally {
+ if (ins != null) {
+ try {
+ ins.close();
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage(), e);
+ }
+ }
+ if (outFd != null) {
+ try {
+ outFd.close();
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage(), e);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Copy exif information represented by originalExif into the file represented by imageOutputUri and overwrites it's
+ * width and height with the given ones.
+ * This is done by {@link ExifInterface} through a seekable {@link FileDescriptor} and this is only possible
+ * starting on Lollipop version of Android.
+ *
+ * @param context The context from which to obtain a content resolver
+ * @param originalExif The exif info from the original input file
+ * @param width output image new width
+ * @param height output image new height
+ * @param imageOutputUri The {@link Uri} that represents the output file
+ */
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void copyExif(Context context, ExifInterface originalExif, int width, int height, Uri imageOutputUri) {
+
+ if(context == null) {
+ Log.d(TAG, "context is null");
+ return;
+ }
+
+ ParcelFileDescriptor outFd = null;
+ try {
+
+ // In order to the ExifInterface be able to validate JPEG info from the file, the FileDescriptor must to be
+ // opened en "rw" (read and write) mode
+ outFd = context.getContentResolver().openFileDescriptor(imageOutputUri, "rw");
+ ExifInterface newExif = new ExifInterface(outFd.getFileDescriptor());
+
+ copyExifAttributes(originalExif, newExif, width, height);
+
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage());
+ } finally {
+ if (outFd != null) {
+ try {
+ outFd.close();
+ } catch (IOException e) {
+ Log.d(TAG, e.getMessage(), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Copy Exif attributes from the originalExif to the newExif and overwrites it's width and height with the given ones.
+ *
+ * @param originalExif Original exif information
+ * @param newExif New exif information
+ * @param width Width for overwriting into the newExif
+ * @param height Height for overwriting into the newExif
+ * @throws IOException If it occurs some IO error while trying to save the new exif info.
+ */
+ private static void copyExifAttributes(ExifInterface originalExif, ExifInterface newExif, int width, int height) throws IOException {
+
String[] attributes = new String[]{
ExifInterface.TAG_F_NUMBER,
ExifInterface.TAG_DATETIME,
@@ -402,24 +570,18 @@ public static void copyExif(ExifInterface originalExif, int width, int height, S
ExifInterface.TAG_WHITE_BALANCE
};
- try {
- ExifInterface newExif = new ExifInterface(imageOutputPath);
- String value;
- for (String attribute : attributes) {
- value = originalExif.getAttribute(attribute);
- if (!TextUtils.isEmpty(value)) {
- newExif.setAttribute(attribute, value);
- }
+ String value;
+ for (String attribute : attributes) {
+ value = originalExif.getAttribute(attribute);
+ if (!TextUtils.isEmpty(value)) {
+ newExif.setAttribute(attribute, value);
}
- newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width));
- newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height));
- newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "0");
-
- newExif.saveAttributes();
-
- } catch (IOException e) {
- Log.d(TAG, e.getMessage());
}
+ newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width));
+ newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height));
+ newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "0");
+
+ newExif.saveAttributes();
}
}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/view/CropImageView.java b/ucrop/src/main/java/com/yalantis/ucrop/view/CropImageView.java
index 2edfd61da..31504aa0d 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/view/CropImageView.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/view/CropImageView.java
@@ -84,6 +84,9 @@ public void cropAndSaveImage(@NonNull Bitmap.CompressFormat compressFormat, int
compressFormat, compressQuality,
getImageInputPath(), getImageOutputPath(), getExifInfo());
+ cropParameters.setContentImageInputUri(getImageInputUri());
+ cropParameters.setContentImageOutputUri(getImageOutputUri());
+
new BitmapCropTask(getContext(), getViewBitmap(), imageState, cropParameters, cropCallback)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/view/GestureCropImageView.java b/ucrop/src/main/java/com/yalantis/ucrop/view/GestureCropImageView.java
index b0f3cff93..e3644e184 100644
--- a/ucrop/src/main/java/com/yalantis/ucrop/view/GestureCropImageView.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/view/GestureCropImageView.java
@@ -21,7 +21,7 @@ public class GestureCropImageView extends CropImageView {
private float mMidPntX, mMidPntY;
- private boolean mIsRotateEnabled = true, mIsScaleEnabled = true;
+ private boolean mIsRotateEnabled = true, mIsScaleEnabled = true, mIsGestureEnabled = true;
private int mDoubleTapScaleSteps = 5;
public GestureCropImageView(Context context) {
@@ -52,6 +52,14 @@ public boolean isRotateEnabled() {
return mIsRotateEnabled;
}
+ public void setGestureEnabled(boolean gestureEnabled) {
+ mIsGestureEnabled = gestureEnabled;
+ }
+
+ public boolean isGestureEnabled() {
+ return mIsGestureEnabled;
+ }
+
public void setDoubleTapScaleSteps(int doubleTapScaleSteps) {
mDoubleTapScaleSteps = doubleTapScaleSteps;
}
@@ -77,7 +85,9 @@ public boolean onTouchEvent(MotionEvent event) {
mMidPntY = (event.getY(0) + event.getY(1)) / 2;
}
- mGestureDetector.onTouchEvent(event);
+ if (mIsGestureEnabled) {
+ mGestureDetector.onTouchEvent(event);
+ }
if (mIsScaleEnabled) {
mScaleDetector.onTouchEvent(event);
diff --git a/ucrop/src/main/java/com/yalantis/ucrop/view/TransformImageView.java b/ucrop/src/main/java/com/yalantis/ucrop/view/TransformImageView.java
index 91d348b2f..5859f41cf 100755
--- a/ucrop/src/main/java/com/yalantis/ucrop/view/TransformImageView.java
+++ b/ucrop/src/main/java/com/yalantis/ucrop/view/TransformImageView.java
@@ -53,6 +53,7 @@ public class TransformImageView extends AppCompatImageView {
private int mMaxBitmapSize = 0;
private String mImageInputPath, mImageOutputPath;
+ private Uri mImageInputUri, mImageOutputUri;
private ExifInfo mExifInfo;
/**
@@ -126,6 +127,14 @@ public String getImageOutputPath() {
return mImageOutputPath;
}
+ public Uri getImageInputUri() {
+ return mImageInputUri;
+ }
+
+ public Uri getImageOutputUri() {
+ return mImageOutputUri;
+ }
+
public ExifInfo getExifInfo() {
return mExifInfo;
}
@@ -143,9 +152,11 @@ public void setImageUri(@NonNull Uri imageUri, @Nullable Uri outputUri) throws E
new BitmapLoadCallback() {
@Override
- public void onBitmapLoaded(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo, @NonNull String imageInputPath, @Nullable String imageOutputPath) {
- mImageInputPath = imageInputPath;
- mImageOutputPath = imageOutputPath;
+ public void onBitmapLoaded(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo, @NonNull Uri imageInputUri, @Nullable Uri imageOutputUri) {
+ mImageInputUri = imageInputUri;
+ mImageOutputUri = imageOutputUri;
+ mImageInputPath = imageInputUri.getPath();
+ mImageOutputPath = imageOutputUri != null ? imageOutputUri.getPath() : null;
mExifInfo = exifInfo;
mBitmapDecoded = true;
diff --git a/ucrop/src/main/res/drawable/ucrop_ic_rotate.xml b/ucrop/src/main/res/drawable/ucrop_ic_rotate.xml
index 509d7b156..2c08c2285 100644
--- a/ucrop/src/main/res/drawable/ucrop_ic_rotate.xml
+++ b/ucrop/src/main/res/drawable/ucrop_ic_rotate.xml
@@ -7,5 +7,5 @@
+ android:pathData="M 19.761,5.566 C 15.791825,0.49517195 7.1049248,0.96194642 3.7237612,6.4638227 -0.33837992,12.033745 3.3097107,20.843671 10.121424,21.908979 c 4.572019,1.06054 9.956288,-1.892196 11.2181,-6.246218 C 20.613349,15.378841 19.887175,15.09492 19.161,14.811 17.787681,18.760509 12.866024,20.703386 9.1288906,19.047063 5.9906393,17.775571 3.9999147,14.236171 4.5746701,10.893626 5.0470831,7.426033 8.1929546,4.5782223 11.692652,4.4629013 13.866935,4.33846 16.076584,5.2031434 17.571,6.791 c -0.89,0.4976667 -1.78,0.9953333 -2.67,1.493 2.321563,1.3826149 4.642896,2.765615 6.964,4.149 0.03752,-2.7019929 0.07452,-5.4039929 0.111,-8.106 -0.738333,0.413 -1.476667,0.826 -2.215,1.239 z" />
diff --git a/ucrop/src/main/res/drawable/ucrop_ic_rotate_unselected.xml b/ucrop/src/main/res/drawable/ucrop_ic_rotate_unselected.xml
index 55f7a3ff9..841dc96ea 100644
--- a/ucrop/src/main/res/drawable/ucrop_ic_rotate_unselected.xml
+++ b/ucrop/src/main/res/drawable/ucrop_ic_rotate_unselected.xml
@@ -7,5 +7,5 @@
+ android:pathData="M 19.761,5.566 C 15.791825,0.49517195 7.1049248,0.96194642 3.7237612,6.4638227 -0.33837992,12.033745 3.3097107,20.843671 10.121424,21.908979 c 4.572019,1.06054 9.956288,-1.892196 11.2181,-6.246218 C 20.613349,15.378841 19.887175,15.09492 19.161,14.811 17.787681,18.760509 12.866024,20.703386 9.1288906,19.047063 5.9906393,17.775571 3.9999147,14.236171 4.5746701,10.893626 5.0470831,7.426033 8.1929546,4.5782223 11.692652,4.4629013 13.866935,4.33846 16.076584,5.2031434 17.571,6.791 c -0.89,0.4976667 -1.78,0.9953333 -2.67,1.493 2.321563,1.3826149 4.642896,2.765615 6.964,4.149 0.03752,-2.7019929 0.07452,-5.4039929 0.111,-8.106 -0.738333,0.413 -1.476667,0.826 -2.215,1.239 z" />
diff --git a/ucrop/src/main/res/layout/ucrop_activity_photobox.xml b/ucrop/src/main/res/layout/ucrop_activity_photobox.xml
index 0ed5f6d82..57a5a3b13 100644
--- a/ucrop/src/main/res/layout/ucrop_activity_photobox.xml
+++ b/ucrop/src/main/res/layout/ucrop_activity_photobox.xml
@@ -28,7 +28,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/controls_wrapper"
- android:layout_below="@+id/toolbar">
+ android:layout_below="@+id/toolbar"
+ android:layout_marginBottom="-12dp">
@@ -52,7 +53,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
- android:background="@android:color/black"
android:visibility="gone" />
diff --git a/ucrop/src/main/res/layout/ucrop_fragment_photobox.xml b/ucrop/src/main/res/layout/ucrop_fragment_photobox.xml
index a2ff0467a..34594546c 100644
--- a/ucrop/src/main/res/layout/ucrop_fragment_photobox.xml
+++ b/ucrop/src/main/res/layout/ucrop_fragment_photobox.xml
@@ -10,6 +10,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/controls_wrapper"
+ android:layout_marginBottom="-12dp"
android:background="@color/ucrop_color_crop_background">
diff --git a/ucrop/src/main/res/values-de/strings.xml b/ucrop/src/main/res/values-de/strings.xml
index 481d25a57..8d81cea75 100644
--- a/ucrop/src/main/res/values-de/strings.xml
+++ b/ucrop/src/main/res/values-de/strings.xml
@@ -3,4 +3,8 @@
Foto editieren
Original
Zuschneiden
+
+ Rotieren
+ Skalieren
+ Zuschneiden
\ No newline at end of file
diff --git a/ucrop/src/main/res/values-fa/strings.xml b/ucrop/src/main/res/values-fa/strings.xml
new file mode 100644
index 000000000..f0c81a008
--- /dev/null
+++ b/ucrop/src/main/res/values-fa/strings.xml
@@ -0,0 +1,13 @@
+
+
+
+ اصلی
+ ویرایش عکس
+
+ برش
+
+ چرخش
+ اندازه
+ برش
+
+
diff --git a/ucrop/src/main/res/values-in/strings.xml b/ucrop/src/main/res/values-in/strings.xml
new file mode 100644
index 000000000..eddf03209
--- /dev/null
+++ b/ucrop/src/main/res/values-in/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Asli
+ Edit Foto
+
+ Pangkas
+
+ Memutar
+ Skala
+ Pangkas
+
+
diff --git a/ucrop/src/main/res/values-nl/strings.xml b/ucrop/src/main/res/values-nl/strings.xml
index 4570a62f8..a9ca77d13 100644
--- a/ucrop/src/main/res/values-nl/strings.xml
+++ b/ucrop/src/main/res/values-nl/strings.xml
@@ -2,4 +2,8 @@
Origineel
Foto bewerken
Bijsnijden
+
+ Draaien
+ Schalen
+ Bijsnijden
diff --git a/ucrop/src/main/res/values-pt/strings.xml b/ucrop/src/main/res/values-pt/strings.xml
new file mode 100644
index 000000000..1e98d1d0c
--- /dev/null
+++ b/ucrop/src/main/res/values-pt/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Original
+ Editar Foto
+
+ Cortar
+
+ Girar
+ Tamanho
+ Cortar
+
+
diff --git a/ucrop/src/main/res/values-ru/strings.xml b/ucrop/src/main/res/values-ru/strings.xml
new file mode 100644
index 000000000..7973c3c92
--- /dev/null
+++ b/ucrop/src/main/res/values-ru/strings.xml
@@ -0,0 +1,9 @@
+
+
+ Оригинал
+ Повернуть
+ Масштабировать
+ Обрезать
+ Обрезать
+ Редактировать фото
+
diff --git a/ucrop/src/main/res/values-th/strings.xml b/ucrop/src/main/res/values-th/strings.xml
new file mode 100644
index 000000000..add5b5ebe
--- /dev/null
+++ b/ucrop/src/main/res/values-th/strings.xml
@@ -0,0 +1,12 @@
+
+
+ เป็นต้นฉบับ
+ แก้ไขรูปภาพ
+
+ พืชผล
+
+ หมุน
+ ขนาด
+ พืชผล
+
+
diff --git a/ucrop/src/main/res/values-tr/strings.xml b/ucrop/src/main/res/values-tr/strings.xml
new file mode 100644
index 000000000..097261675
--- /dev/null
+++ b/ucrop/src/main/res/values-tr/strings.xml
@@ -0,0 +1,12 @@
+
+
+ Orijinal
+ Fotoğrafı Düzenle
+
+ Kırp
+
+ Döndürme
+ Ölçek
+ Kırp
+
+
diff --git a/ucrop/src/main/res/values-zh-rTW/strings.xml b/ucrop/src/main/res/values-zh-rTW/strings.xml
index f0abcc71e..c55eb6294 100644
--- a/ucrop/src/main/res/values-zh-rTW/strings.xml
+++ b/ucrop/src/main/res/values-zh-rTW/strings.xml
@@ -7,5 +7,8 @@
必須指定輸入以及輸出的 Uri
在你的 App 內覆寫顏色資源檔 (ucrop_color_toolbar_widget) 使 5.0 以前裝置正常運作
+ 旋轉
+ 縮放
+ 裁切
diff --git a/ucrop/src/main/res/values-zh/strings.xml b/ucrop/src/main/res/values-zh/strings.xml
index fb39928db..100ef14ba 100644
--- a/ucrop/src/main/res/values-zh/strings.xml
+++ b/ucrop/src/main/res/values-zh/strings.xml
@@ -4,5 +4,8 @@
裁剪
裁剪
+ 旋转
+ 缩放
+ 裁剪