Crash during crop bitmap creation (x + width must be <= bitmap.width())
Hi uCrop team!
I have integrated uCrop using following code: val sourceUri = data.data val destinationUri = Uri.fromFile(File(getCacheDir(), "photoFromGallery.jpg")) val ratioX = cameraView.measuredWidth.toFloat() val ratioY = cameraView.measuredHeight.toFloat() val options = UCrop.Options() options.setToolbarColor(ContextCompat.getColor(context, R.color.gallery_primary)) options.setStatusBarColor(ContextCompat.getColor(context, R.color.gallery_dark)) options.setToolbarTitle(context.getString(R.string.gallery_edit_photo)) UCrop.of(sourceUri, destinationUri) .withAspectRatio(ratioX, ratioY) .withMaxResultSize(MAX_PHOTO_SIZE, MAX_PHOTO_SIZE) .withOptions(options) .start(context, this)
It works just fine in most cases. Thank you! But we have found the following crashes in crashlytics (about 30 crashes per week):
Caused by java.lang.IllegalArgumentException: x + width must be <= bitmap.width() at android.graphics.Bitmap.createBitmap(Bitmap.java:826) at android.graphics.Bitmap.createBitmap(Bitmap.java:793) at com.yalantis.ucrop.task.BitmapCropTask.crop(SourceFile:152) at com.yalantis.ucrop.task.BitmapCropTask.doInBackground(SourceFile:95) at com.yalantis.ucrop.task.BitmapCropTask.doInBackground(SourceFile:35) at android.os.AsyncTask$2.call(AsyncTask.java:304) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:762)
Not sure how to reproduce it. Could you, please, fix it? Maybe I can change the client code to avoid this crash?
uCrop version 2.2.1.

@gavriil Hi! Thank you for your issues! As you see, it happens because users try to crop zone much larger then photo. I guess maybe we have some problem with round in BitmapCropTask. This is hard to reproduce, but dont worry, Ucrop has error handling and just write crash to log, instead of app crash user see the toast with error message.
I will try to reproduce and fix it in the next release, if you have some more information, please report it :)
Hi there! Very simple case. This bug arise when image to crop has size less than crop view. And yes it's circle layer. Sometimes after selecting, the image does not auto stretch to the crop layer or can be resizing to less then crop zone.

java.lang.IllegalArgumentException: x + width must be <= bitmap.width()
at android.graphics.Bitmap.createBitmap(Bitmap.java:756)
at android.graphics.Bitmap.createBitmap(Bitmap.java:723)
at com.yalantis.ucrop.task.BitmapCropTask.crop(BitmapCropTask.java:152)
at com.yalantis.ucrop.task.BitmapCropTask.doInBackground(BitmapCropTask.java:95)
at com.yalantis.ucrop.task.BitmapCropTask.doInBackground(BitmapCropTask.java:35)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
@gavriil So the problem is next: We have default scale multiplier to align image inside crop area, if you have image, for example w = 100, h = 1000 and you choose 3:4 aspect ratio, image will be scaled and whole crop are will be cover image, so there will not be any pixels outside of this area. But if you choose 16:9 on current scaling you will find that image cannot be scaled more and of course there would be some pixels outside of crop area so you will see this toast. Solution is next: Ucop has method setMaxScaleMultiplier(multiplier) which allow you to make image more scalable relative to the crop area. Hope that helps!
@p1nkydev thanks a lot for your help!
Apologies in advance for bumping an issue that hasn't had any activity for over a year and a half 😅
I just ran into this issue (from the linked Flutter package that uses uCrop), and I can reliably reproduce it every single time.
it happens because users try to crop zone much larger then photo. I guess maybe we have some problem with round in BitmapCropTask.
You're pretty much spot on. In my reproduction case, this is what happens in BitmapCropTask.crop():
mViewBitmap.width: 3976
mViewBitmap.height: 2652
cropOffsetX: 2256
cropOffsetY: 0
mCroppedImageWidth: 1721
mCroppedImageHeight: 2651
This means that Bitmap.createBitmap is called with cropOffsetX + mCroppedImageWidth > mViewBitmap.width, by 1 pixel. That's exactly what's causing the exception to be thrown in my case.
The root cause of the off-by-one pixel has to do with the rounding that's taking place when calculating the crop offsets and cropped size. It might be enough to remove the rounding, but that may lead to problems in other edge cases, so perhaps a more robust solutions could be to clamp the crop offsets + cropped image size to the size of mViewBitmap?
// Edit: I forgot to mention that this issue only exists on the non-native artifact. Although the same calculations and rounding exists in the native version, it looks like cropCImg() handles the crop area exceeding the bitmap bounds more gracefully.
Above might also be related to https://github.com/Yalantis/uCrop/issues/98
Any update on this issue?
The rounding in the subsequent calculation can cause the result (cropOffsetX + cropOffsetY) to exceed the image limits.
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);
In fact, we should ensure that:
0 <= cropOffsetX <= mCroppedImageWidth
and
0 <= cropOffsetY <= mCroppedImageHeight
The code should be:
cropOffsetX = Math.min(mCroppedImageWidth , Math.max(0, cropOffsetX))
cropOffsetY = Math.min(mCroppedImageHeight , Math.max(0, cropOffsetY))
or use (int) Math.floor() instead of Math.round
But...
Since the call to createBitmap was moved to the native side in version 2.2.11, it's possible that this bug is no longer relevant in 2.2.11... TBC