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 c18a2c675..bbb73fd3e 100644 --- a/sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java +++ b/sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java @@ -55,9 +55,9 @@ public class SampleActivity extends BaseActivity implements UCropFragmentCallbac private static final String SAMPLE_CROPPED_IMAGE_NAME = "SampleCropImage"; private RadioGroup mRadioGroupAspectRatio, mRadioGroupCompressionSettings; - private EditText mEditTextMaxWidth, mEditTextMaxHeight; + private EditText mEditTextMinWidth, mEditTextMinHeight, mEditTextMaxWidth, mEditTextMaxHeight; private EditText mEditTextRatioX, mEditTextRatioY; - private CheckBox mCheckBoxMaxSize; + private CheckBox mCheckBoxMinSize, mCheckBoxMaxSize; private SeekBar mSeekBarQuality; private TextView mTextViewQuality; private CheckBox mCheckBoxHideBottomControls; @@ -145,9 +145,12 @@ public void onClick(View v) { settingsView = findViewById(R.id.settings); mRadioGroupAspectRatio = findViewById(R.id.radio_group_aspect_ratio); mRadioGroupCompressionSettings = findViewById(R.id.radio_group_compression_settings); + mCheckBoxMinSize = findViewById(R.id.checkbox_min_size); mCheckBoxMaxSize = findViewById(R.id.checkbox_max_size); mEditTextRatioX = findViewById(R.id.edit_text_ratio_x); mEditTextRatioY = findViewById(R.id.edit_text_ratio_y); + mEditTextMinWidth = findViewById(R.id.edit_text_min_width); + mEditTextMinHeight = findViewById(R.id.edit_text_min_height); mEditTextMaxWidth = findViewById(R.id.edit_text_max_width); mEditTextMaxHeight = findViewById(R.id.edit_text_max_height); mSeekBarQuality = findViewById(R.id.seekbar_quality); @@ -184,6 +187,8 @@ public void onStopTrackingTouch(SeekBar seekBar) { } }); + mEditTextMinHeight.addTextChangedListener(mMinSizeTextWatcher); + mEditTextMinWidth.addTextChangedListener(mMinSizeTextWatcher); mEditTextMaxHeight.addTextChangedListener(mMaxSizeTextWatcher); mEditTextMaxWidth.addTextChangedListener(mMaxSizeTextWatcher); } @@ -206,6 +211,26 @@ public void afterTextChanged(Editable s) { } }; + private TextWatcher mMinSizeTextWatcher = 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_min_cropped_image_size), UCrop.MIN_SIZE), Toast.LENGTH_SHORT).show(); + } + } + } + }; + private TextWatcher mMaxSizeTextWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -301,10 +326,24 @@ private UCrop basisConfig(@NonNull UCrop uCrop) { break; } + if (mCheckBoxMinSize.isChecked()) { + try { + int minWidth = Integer.valueOf(mEditTextMinWidth.getText().toString().trim()); + int minHeight = Integer.valueOf(mEditTextMinHeight.getText().toString().trim()); + + if (minWidth > UCrop.MIN_SIZE && minHeight > UCrop.MIN_SIZE) { + uCrop = uCrop.withMinResultSize(minWidth, minHeight); + } + } catch (NumberFormatException e) { + Log.e(TAG, "Number please", e); + } + } + if (mCheckBoxMaxSize.isChecked()) { try { int maxWidth = Integer.valueOf(mEditTextMaxWidth.getText().toString().trim()); int maxHeight = Integer.valueOf(mEditTextMaxHeight.getText().toString().trim()); + if (maxWidth > UCrop.MIN_SIZE && maxHeight > UCrop.MIN_SIZE) { uCrop = uCrop.withMaxResultSize(maxWidth, maxHeight); } diff --git a/sample/src/main/res/layout/include_settings.xml b/sample/src/main/res/layout/include_settings.xml index abdaae8bf..8a054785a 100644 --- a/sample/src/main/res/layout/include_settings.xml +++ b/sample/src/main/res/layout/include_settings.xml @@ -135,6 +135,57 @@ + + + + + + + + + + + + + + + + Dynamic Image source Square + Min cropped image size Max cropped image size Compression settings UI Width Height + Resize image to min size Resize image to max size Hide bottom UI controls Freestyle crop @@ -32,6 +34,7 @@ Crop Result (%1$dx%2$d) Quality: %d %1$dx%2$d px + Min cropped image size cannot be less then %d Max cropped image size cannot be less then %d Permission needed diff --git a/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java b/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java index d6cd45a10..06d3f7039 100644 --- a/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java +++ b/ucrop/src/main/java/com/yalantis/ucrop/UCrop.java @@ -48,6 +48,8 @@ public class UCrop { public static final String EXTRA_ASPECT_RATIO_X = EXTRA_PREFIX + ".AspectRatioX"; public static final String EXTRA_ASPECT_RATIO_Y = EXTRA_PREFIX + ".AspectRatioY"; + public static final String EXTRA_MIN_SIZE_X = EXTRA_PREFIX + ".MinSizeX"; + public static final String EXTRA_MIN_SIZE_Y = EXTRA_PREFIX + ".MinSizeY"; public static final String EXTRA_MAX_SIZE_X = EXTRA_PREFIX + ".MaxSizeX"; public static final String EXTRA_MAX_SIZE_Y = EXTRA_PREFIX + ".MaxSizeY"; @@ -94,8 +96,30 @@ public UCrop useSourceImageAspectRatio() { return this; } + /** + * Set minimum size for result cropped image. Minimum size cannot be less then {@value MIN_SIZE} + * The priority of this method is less than the {@link #withMaxResultSize(int, int) withMaxResultSize} method. + * + * @param width min cropped image width + * @param height min cropped image height + */ + public UCrop withMinResultSize(@IntRange(from = MIN_SIZE) int width, @IntRange(from = MIN_SIZE) int height) { + if (width < MIN_SIZE) { + width = MIN_SIZE; + } + + if (height < MIN_SIZE) { + height = MIN_SIZE; + } + + mCropOptionsBundle.putInt(EXTRA_MIN_SIZE_X, width); + mCropOptionsBundle.putInt(EXTRA_MIN_SIZE_Y, height); + return this; + } + /** * Set maximum size for result cropped image. Maximum size cannot be less then {@value MIN_SIZE} + * The priority of this method is higher than the {@link #withMinResultSize(int, int) withMinResultSize} method. * * @param width max cropped image width * @param height max cropped image height @@ -545,8 +569,29 @@ public void useSourceImageAspectRatio() { mOptionBundle.putFloat(EXTRA_ASPECT_RATIO_Y, 0); } + /** + * Set minimum size for result cropped image. + * The priority of this method is less than the {@link #withMaxResultSize(int, int) withMaxResultSize} method. + * + * @param width min cropped image width + * @param height min cropped image height + */ + public void withMinResultSize(@IntRange(from = MIN_SIZE) int width, @IntRange(from = MIN_SIZE) int height) { + if (width < MIN_SIZE) { + width = MIN_SIZE; + } + + if (height < MIN_SIZE) { + height = MIN_SIZE; + } + + mOptionBundle.putInt(EXTRA_MIN_SIZE_X, width); + mOptionBundle.putInt(EXTRA_MIN_SIZE_Y, height); + } + /** * Set maximum size for result cropped image. + * The priority of this method is higher than the {@link #withMinResultSize(int, int) withMinResultSize} method. * * @param width max cropped image width * @param height max cropped image height diff --git a/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java b/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java index 36b8b85f7..1189775a4 100644 --- a/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java +++ b/ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java @@ -259,10 +259,19 @@ private void processOptions(@NonNull Intent intent) { mGestureCropImageView.setTargetAspectRatio(CropImageView.SOURCE_IMAGE_ASPECT_RATIO); } + // Result bitmap min size options + int minSizeX = intent.getIntExtra(UCrop.EXTRA_MIN_SIZE_X, 0); + int minSizeY = intent.getIntExtra(UCrop.EXTRA_MIN_SIZE_Y, 0); + // Result bitmap max size options int maxSizeX = intent.getIntExtra(UCrop.EXTRA_MAX_SIZE_X, 0); int maxSizeY = intent.getIntExtra(UCrop.EXTRA_MAX_SIZE_Y, 0); + if (minSizeX > 0 && minSizeY > 0) { + mGestureCropImageView.setMinResultImageSizeX(minSizeX); + mGestureCropImageView.setMinResultImageSizeY(minSizeY); + } + if (maxSizeX > 0 && maxSizeY > 0) { mGestureCropImageView.setMaxResultImageSizeX(maxSizeX); mGestureCropImageView.setMaxResultImageSizeY(maxSizeY); diff --git a/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java b/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java index 2e64450c4..23223927a 100644 --- a/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java +++ b/ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java @@ -232,10 +232,19 @@ private void processOptions(@NonNull Bundle bundle) { mGestureCropImageView.setTargetAspectRatio(CropImageView.SOURCE_IMAGE_ASPECT_RATIO); } + // Result bitmap min size options + int minSizeX = bundle.getInt(UCrop.EXTRA_MIN_SIZE_X, 0); + int minSizeY = bundle.getInt(UCrop.EXTRA_MIN_SIZE_Y, 0); + // Result bitmap max size options int maxSizeX = bundle.getInt(UCrop.EXTRA_MAX_SIZE_X, 0); int maxSizeY = bundle.getInt(UCrop.EXTRA_MAX_SIZE_Y, 0); + if (minSizeX > 0 && minSizeY > 0) { + mGestureCropImageView.setMinResultImageSizeX(minSizeX); + mGestureCropImageView.setMinResultImageSizeY(minSizeY); + } + if (maxSizeX > 0 && maxSizeY > 0) { mGestureCropImageView.setMaxResultImageSizeX(maxSizeX); mGestureCropImageView.setMaxResultImageSizeY(maxSizeY); 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..5dccc1053 100644 --- a/ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java +++ b/ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java @@ -7,7 +7,7 @@ */ public class CropParameters { - private int mMaxResultImageSizeX, mMaxResultImageSizeY; + private int mMinResultImageSizeX, mMinResultImageSizeY, mMaxResultImageSizeX, mMaxResultImageSizeY; private Bitmap.CompressFormat mCompressFormat; private int mCompressQuality; @@ -15,9 +15,12 @@ public class CropParameters { private ExifInfo mExifInfo; - public CropParameters(int maxResultImageSizeX, int maxResultImageSizeY, + public CropParameters(int minResultImageSizeX, int minResultImageSizeY, + int maxResultImageSizeX, int maxResultImageSizeY, Bitmap.CompressFormat compressFormat, int compressQuality, String imageInputPath, String imageOutputPath, ExifInfo exifInfo) { + mMinResultImageSizeX = minResultImageSizeX; + mMinResultImageSizeY = minResultImageSizeY; mMaxResultImageSizeX = maxResultImageSizeX; mMaxResultImageSizeY = maxResultImageSizeY; mCompressFormat = compressFormat; @@ -27,6 +30,14 @@ public CropParameters(int maxResultImageSizeX, int maxResultImageSizeY, mExifInfo = exifInfo; } + public int getMinResultImageSizeX() { + return mMinResultImageSizeX; + } + + public int getMinResultImageSizeY() { + return mMinResultImageSizeY; + } + public int getMaxResultImageSizeX() { return mMaxResultImageSizeX; } 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 1a10b8604..7f2e1ac9e 100644 --- a/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java +++ b/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java @@ -40,8 +40,8 @@ public class BitmapCropTask extends AsyncTask { private final RectF mCropRect; private final RectF mCurrentImageRect; - private float mCurrentScale, mCurrentAngle; - private final int mMaxResultImageSizeX, mMaxResultImageSizeY; + private float mCurrentScale, mCurrentScaleX, mCurrentScaleY, mCurrentAngle; + private final int mMinResultImageSizeX, mMinResultImageSizeY, mMaxResultImageSizeX, mMaxResultImageSizeY; private final Bitmap.CompressFormat mCompressFormat; private final int mCompressQuality; @@ -61,6 +61,9 @@ public BitmapCropTask(@Nullable Bitmap viewBitmap, @NonNull ImageState imageStat mCurrentScale = imageState.getCurrentScale(); mCurrentAngle = imageState.getCurrentAngle(); + + mMinResultImageSizeX = cropParameters.getMinResultImageSizeX(); + mMinResultImageSizeY = cropParameters.getMinResultImageSizeY(); mMaxResultImageSizeX = cropParameters.getMaxResultImageSizeX(); mMaxResultImageSizeY = cropParameters.getMaxResultImageSizeY(); @@ -106,34 +109,55 @@ private float resize() { float scaleX = (swapSides ? options.outHeight : options.outWidth) / (float) mViewBitmap.getWidth(); float scaleY = (swapSides ? options.outWidth : options.outHeight) / (float) mViewBitmap.getHeight(); - float resizeScale = Math.min(scaleX, scaleY); +// float resizeScale = Math.min(scaleX, scaleY); + float resizeScale = 1; + +// mCurrentScale /= resizeScale; + mCurrentScaleX = mCurrentScale / scaleX; + mCurrentScaleY = mCurrentScale / scaleY; + +// resizeScale = 1; + + boolean hasMin = mMinResultImageSizeX > 0 && mMinResultImageSizeY > 0; + boolean hasMax = mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0; - mCurrentScale /= resizeScale; + if (hasMin || hasMax) { + float cropWidth = mCropRect.width() / mCurrentScaleX; + float cropHeight = mCropRect.height() / mCurrentScaleY; - resizeScale = 1; - if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) { - float cropWidth = mCropRect.width() / mCurrentScale; - float cropHeight = mCropRect.height() / mCurrentScale; + boolean scaleChanged = false; - if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) { + if (hasMin && (cropWidth < mMinResultImageSizeX || cropHeight < mMinResultImageSizeY)) { + scaleX = mMinResultImageSizeX / cropWidth; + scaleY = mMinResultImageSizeY / cropHeight; + scaleChanged = true; + } + if (hasMax && (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY)) { scaleX = mMaxResultImageSizeX / cropWidth; scaleY = mMaxResultImageSizeY / cropHeight; - resizeScale = Math.min(scaleX, scaleY); + scaleChanged = true; + } - mCurrentScale /= resizeScale; + if (scaleChanged) { + resizeScale = Math.max(scaleX, scaleY); + +// mCurrentScale /= resizeScale; + mCurrentScaleX /= scaleX; + mCurrentScaleY /= scaleY; } } + return resizeScale; } private boolean crop(float resizeScale) throws IOException { ExifInterface originalExif = new ExifInterface(mImageInputPath); - cropOffsetX = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale); - cropOffsetY = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale); - mCroppedImageWidth = Math.round(mCropRect.width() / mCurrentScale); - mCroppedImageHeight = Math.round(mCropRect.height() / mCurrentScale); + cropOffsetX = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScaleX); + cropOffsetY = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScaleY); + mCroppedImageWidth = Math.round(mCropRect.width() / mCurrentScaleX); + mCroppedImageHeight = Math.round(mCropRect.height() / mCurrentScaleY); boolean shouldCrop = shouldCrop(mCroppedImageWidth, mCroppedImageHeight); Log.i(TAG, "Should crop: " + shouldCrop); 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 9d6dbd411..04c18f7bc 100644 --- a/ucrop/src/main/java/com/yalantis/ucrop/view/CropImageView.java +++ b/ucrop/src/main/java/com/yalantis/ucrop/view/CropImageView.java @@ -49,7 +49,7 @@ public class CropImageView extends TransformImageView { private Runnable mWrapCropBoundsRunnable, mZoomImageToPositionRunnable = null; private float mMaxScale, mMinScale; - private int mMaxResultImageSizeX = 0, mMaxResultImageSizeY = 0; + private int mMinResultImageSizeX = 0, mMinResultImageSizeY = 0, mMaxResultImageSizeX = 0, mMaxResultImageSizeY = 0; private long mImageToWrapCropBoundsAnimDuration = DEFAULT_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION; public CropImageView(Context context) { @@ -78,6 +78,7 @@ public void cropAndSaveImage(@NonNull Bitmap.CompressFormat compressFormat, int getCurrentScale(), getCurrentAngle()); final CropParameters cropParameters = new CropParameters( + mMinResultImageSizeX, mMinResultImageSizeY, mMaxResultImageSizeX, mMaxResultImageSizeY, compressFormat, compressQuality, getImageInputPath(), getImageOutputPath(), getExifInfo()); @@ -154,8 +155,29 @@ public void setCropBoundsChangeListener(@Nullable CropBoundsChangeListener cropB mCropBoundsChangeListener = cropBoundsChangeListener; } + /** + * This method sets minimum width for resulting cropped image + * The priority of this method is less than the {@link #setMaxResultImageSizeX(int) setMaxResultImageSizeX} method. + * + * @param minResultImageSizeX - size in pixels + */ + public void setMinResultImageSizeX(@IntRange(from = 10) int minResultImageSizeX) { + mMinResultImageSizeX = minResultImageSizeX; + } + + /** + * This method sets minimum width for resulting cropped image + * The priority of this method is less than the {@link #setMaxResultImageSizeY(int) setMaxResultImageSizeY} method. + * + * @param minResultImageSizeY - size in pixels + */ + public void setMinResultImageSizeY(@IntRange(from = 10) int minResultImageSizeY) { + mMinResultImageSizeY = minResultImageSizeY; + } + /** * This method sets maximum width for resulting cropped image + * The priority of this method is higher than the {@link #setMinResultImageSizeX(int) setMinResultImageSizeX} method. * * @param maxResultImageSizeX - size in pixels */ @@ -165,6 +187,7 @@ public void setMaxResultImageSizeX(@IntRange(from = 10) int maxResultImageSizeX) /** * This method sets maximum width for resulting cropped image + * The priority of this method is higher than the {@link #setMinResultImageSizeY(int) setMinResultImageSizeY} method. * * @param maxResultImageSizeY - size in pixels */