uCrop icon indicating copy to clipboard operation
uCrop copied to clipboard

Crash during crop bitmap creation (x + width must be <= bitmap.width())

Open gavriil opened this issue 7 years ago • 7 comments

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.
android versions

gavriil avatar Apr 16 '18 23:04 gavriil

@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 :)

Legementarion avatar Apr 17 '18 11:04 Legementarion

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.

Example

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)

nikartx avatar May 14 '18 00:05 nikartx

@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 avatar Mar 26 '19 16:03 p1nkydev

@p1nkydev thanks a lot for your help!

gavriil avatar Mar 26 '19 16:03 gavriil

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

mhelder avatar Oct 30 '20 08:10 mhelder

Any update on this issue?

mega-ht avatar Jul 07 '25 04:07 mega-ht

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

gnau-mys avatar Dec 02 '25 13:12 gnau-mys