burhanrashid52 / PhotoEditor

A Photo Editor library with simple, easy support for image editing using paints,text,filters,emoji and Sticker like stories.
MIT License
4.13k stars 991 forks source link

saveAsBitmap with error #374

Closed anderbytes closed 1 year ago

anderbytes commented 3 years ago

I'm trying to execute saveAsBitmap but I often get this error:

E/ImageFilterView: onDrawFrame: android.graphics.Bitmap@de42880 E/PhotoEditorView: saveFilter: android.graphics.Bitmap@de42880 D/PhotoEditorView: onBitmapLoaded() called with: sourceBitmap = [android.graphics.Bitmap@de42880] onBitmapLoaded() called with: sourceBitmap = [android.graphics.Bitmap@de42880] E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1 Process: com.company.myapp, PID: 24958 java.lang.RuntimeException: An error occurred while executing doInBackground() at android.os.AsyncTask$3.done(AsyncTask.java:353) at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383) at java.util.concurrent.FutureTask.setException(FutureTask.java:252) at java.util.concurrent.FutureTask.run(FutureTask.java:271) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) at java.lang.Thread.run(Thread.java:764) Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 0 at java.util.ArrayList.get(ArrayList.java:437) at android.view.ViewGroup.getAndVerifyPreorderedView(ViewGroup.java:3530) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4001) at android.view.View.draw(View.java:19003) at android.view.ViewGroup.drawChild(ViewGroup.java:4218) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4004) at android.view.View.draw(View.java:19138) at ja.burhanrashid52.photoeditor.PhotoSaverTask.captureView(PhotoSaverTask.java:161) at ja.burhanrashid52.photoeditor.PhotoSaverTask.buildBitmap(PhotoSaverTask.java:104) at ja.burhanrashid52.photoeditor.PhotoSaverTask.saveImageAsBitmap(PhotoSaverTask.java:76) at ja.burhanrashid52.photoeditor.PhotoSaverTask.doInBackground(PhotoSaverTask.java:68) at ja.burhanrashid52.photoeditor.PhotoSaverTask.doInBackground(PhotoSaverTask.java:23) at android.os.AsyncTask$2.call(AsyncTask.java:333) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)  at java.lang.Thread.run(Thread.java:764) 

my code is as simple as:

val onSaveBMP: OnSaveBitmap = object: OnSaveBitmap {
                    override fun onBitmapReady(saveBitmap: Bitmap?) { }
                    override fun onFailure(e: Exception?) { }
                }

                mPhoto.saveAsBitmap(onSaveBMP)

really don't know what is happenin. At first I thought it was something at the moment I tried to use the saveBitmap object, but no... even this code above is causing the crash.

very weird. It is as if the Bitmap can't be generated for some reason.

rtz333 commented 3 years ago

i face this error too even i used save as file

anderbytes commented 3 years ago

@burhanrashid52 please any ideas on that? I saw that 1.1.0 version addressed a similar error.

By the way... I use 1 base activity and several Fragments+Jetpack in my app , and not several activities.

burhanrashid52 commented 3 years ago

Honestly. I don't why it caused it. I was not able to reproduce it. Someone raised a PR with the fix and merge it.

anderbytes commented 3 years ago

I will be trying to debug this with more details. But @burhanrashid52 could you please explain a little about the logic of how saveAsBitmap works? As far as I understood, the "PhotoSaverTask" class organizes the steps, and tries to collect the active views in the editor and does something to them.

at ja.burhanrashid52.photoeditor.PhotoSaverTask.captureView(PhotoSaverTask.java:161) at ja.burhanrashid52.photoeditor.PhotoSaverTask.buildBitmap(PhotoSaverTask.java:104) at ja.burhanrashid52.photoeditor.PhotoSaverTask.saveImageAsBitmap(PhotoSaverTask.java:76)

The error itself occurs as seen below, maybe somethin related to this "gathering" of Views to build the Bitmap

Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 0 at java.util.ArrayList.get(ArrayList.java:437) at android.view.ViewGroup.getAndVerifyPreorderedView(ViewGroup.java:3530)

if saveAsBitmap is also used inside saveAsFile, that one also should suffer from the issue.

anderbytes commented 3 years ago

Just sent a PR to try to resolve this mysterious issue. https://github.com/burhanrashid52/PhotoEditor/pull/377

It is explained on the PR, but briefly... it seems that on PhotoSaverTask, if the createBitmap method (which probably is async inside) is not finished before the draw method just ahed, it just sends the blank bitmap object inside the canvas, and we don't want that.

So I added (and tested) a simple timer (in milliseconds) right after the createBitmap (also brought it more far away from the draw). Every test I did with 1000ms resulted in no more crashes.

IF somehow @burhanrashid52 could think of any other solution that guarantees that createBitmap is finished before trying to use the bitmap to be used in Canvas, this delay timer could be lifted out.

Anyway... the default delay is zero, so probably no harm done even for any other users of the editor.

anderbytes commented 3 years ago

Changed the solution with a new PR, now insted of a delay, the draw command retries until the Bitmap is created and it succeeds.

anderbytes commented 3 years ago

@burhanrashid52 ?

RGdevz commented 3 years ago

I was facing the same issue, what I did is got rid of the async task and run everything on main thread (I dont use the save to file), now there zero problems. I think you should switch out the asynctask or do the captureView on the main thread

anderbytes commented 3 years ago

I was facing the same issue, what I did is got rid of the async task and run everything on main thread (I dont use the save to file), now there zero problems. I think you should switch out the asynctask or do the captureView on the main thread

How did you got rid of the async task? Because here I don't see a way to do it just "using" the editor.

I don't know if the captureView can be done from without the async because the whole process of creating the bitmap is in there... so it has to be put afterwards in there.

So the dev could remove all from async and do all in main thread... I don't know if that would cause some type of "freezing" on the UI for the time it takes.

the PERFECT way of doing it (we need some Java expert here) would be to instruct the DRAW command of captureView to WAIT for the whole createBitmap command to end, but internally that also seems to have another level of async

I drafted a small "while" on my #377 that would retry if it notices that the Bitmap wasn't available yet. Here in my tests worked perfectly, but unfortunately, it isn't being approved by the automated ui-test of GitHub

RGdevz commented 3 years ago

I was facing the same issue, what I did is got rid of the async task and run everything on main thread (I dont use the save to file), now there zero problems. I think you should switch out the asynctask or do the captureView on the main thread

How did you got rid of the async task? Because here I don't see a way to do it just "using" the editor.

I don't know if the captureView can be done from without the async because the whole process of creating the bitmap is in there... so it has to be put afterwards in there.

So the dev could remove all from async and do all in main thread... I don't know if that would cause some type of "freezing" on the UI for the time it takes.

the PERFECT way of doing it (we need some Java expert here) would be to instruct the DRAW command of captureView to WAIT for the whole createBitmap command to end, but internally that also seems to have another level of async

I drafted a small "while" on my #377 that would retry if it notices that the Bitmap wasn't available yet. Here in my tests worked perfectly, but unfortunately, it isn't being approved by the automated ui-test of GitHub

I just change the whole PhotoSaverTask to a Runnable and its works, been testing it and im not getting any freezes in the ui

anderbytes commented 3 years ago

I was facing the same issue, what I did is got rid of the async task and run everything on main thread (I dont use the save to file), now there zero problems. I think you should switch out the asynctask or do the captureView on the main thread

How did you got rid of the async task? Because here I don't see a way to do it just "using" the editor. I don't know if the captureView can be done from without the async because the whole process of creating the bitmap is in there... so it has to be put afterwards in there. So the dev could remove all from async and do all in main thread... I don't know if that would cause some type of "freezing" on the UI for the time it takes. the PERFECT way of doing it (we need some Java expert here) would be to instruct the DRAW command of captureView to WAIT for the whole createBitmap command to end, but internally that also seems to have another level of async I drafted a small "while" on my #377 that would retry if it notices that the Bitmap wasn't available yet. Here in my tests worked perfectly, but unfortunately, it isn't being approved by the automated ui-test of GitHub

I just change the whole PhotoSaverTask to a Runnable and its works, been testing it and im not getting any freezes in the ui

But doesn't that need a PR? Because the best here would be to only link the online dependency of an official release, not tamper with the code of the editor itself.

anderbytes commented 3 years ago

The tests of #377 still weren't finishing, even after I removed the WHILE of the previous commit, so I ended that PR and will work on another one, that time working on an alternate version of saveBitmap and saveFile that doesn't need an ASYNC extension.

anderbytes commented 3 years ago

New https://github.com/burhanrashid52/PhotoEditor/pull/381 to let the user use a SYNC save instead of the default one, if wanted.

anderbytes commented 3 years ago

@burhanrashid52 , just here to give some little update. I'm still doing some tests here but I think I got exactly the reason of the IndexOutOfBounds on the DoInBackground of the saveBitmap method.

As far as I got, it happens because the view is not yet available to be drawn in a Canvas. So the array that should contain a view, contains nothing. I believe that the "isDirty()" method is the best way to tell if the image is available or still un-updated.

It's ugly, but I'm doing some tests with a busy-wait , a while loop waiting for view.isDirty( ) to be false, and eventually it gets out and saves normally.

I'm not saying it's the best solution. But at least it's not a generic try-catch (that ended up creating some bugs), and still uses the async feature.

anderbytes commented 3 years ago

I've read a lot about wait/notify, but every solution required to code inside the implementation of the method that KNOWS when the flag is changed, so INSIDE the View class (that I don't recommend to mess with)

So I'm not seeing, for now, another solution other than a busy-wait. There could be a 100ms interval between each check, that would be enough to relieve the CPU a lot

akshayherle11 commented 3 years ago

I tried to find out the solution for it, The error is caused because of the captureView in the PhotoSaverTask because it it is not running on the Main UI thread . Error is caused when added view to the PhotoEditorView is out of the boundary of it .

Solution is to run the function on main UI thread even if the view is out of boundary it works

photoEditor.clearHelperBox(); photoEditorView.getDrawingView().destroyDrawingCache(); String rootpath = Environment.getExternalStorageDirectory().toString(); File dir = new File(rootpath + "/folder"); dir.mkdirs(); String savedp = "/aksahy.jpg"; File filedp = new File(dir, savedp);

    Bitmap bitmap = Bitmap.createBitmap(
            photoEditorView.getWidth(),
            photoEditorView.getHeight(),
            Bitmap.Config.ARGB_8888
    );
    Canvas canvas = new Canvas(bitmap);
    photoEditorView.draw(canvas);

    try {
        FileOutputStream out = new FileOutputStream(filedp, false);
        bitmap.compress(Bitmap.CompressFormat.PNG,100, out);
        out.flush();
        out.close();
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(filedp)));
        Toast.makeText(EditorActivity.this, "Image Saved !\n" + filedp.getPath(), Toast.LENGTH_SHORT).show();

    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(EditorActivity.this, "Failed", Toast.LENGTH_SHORT).show();

    }   

could be done like this error free

anderbytes commented 3 years ago

Not exactly. Maybe you found another reason for the same issue.

But the one I tracked pointed me that the IndexOutOfBounds exception is generated inside the photoEditorView.draw(canvas) inside your code above.

I have been having this issue even if all the views are inside the boundary. Also... if I TRY to debug the issue and put a breakpoint right before the draw(canvas) in Android Studio, it never crashes, because the pause of the breakpoint gives the necessary time for the view to be redrawn and not be Dirty anymore.

If the bug I experienced were something related to having or not something inside boundary or it being done in back or main UI thread, my busy-wait solution wouldn't work.

github-actions[bot] commented 2 years ago

This issue is stale because it has been open 20 days with no activity. Remove stale label or comment or this will be closed in 4 days.

anderbytes commented 2 years ago

Don't close it

jsericksk commented 2 years ago

Any news about this issue? @anderbytes

anderbytes commented 2 years ago

Unfortunately, no. I have created a branch with the less-wrong solution I could find, as the main dev doesn't seem to be very bothered by the issue.

Even his sample app forces the user to first phisically save the image before sharing it, and I wonder why, probably because he also had issues on sharing the image right out of the screen, as I tracked the issue to be related to the "drawing" being "dirty" (so not available) at the exact moment the bitmap is being generated.

In my branch, I have created a busy-wait loop (500ms , I believe) where I test if the drawing ended it's internal tasks and so stepped out of the "dirty" status.

That way, I could guarantee that the crash never happens.

Em sáb., 9 de out. de 2021 01:12, Ericks @.***> escreveu:

Any news about this issue? @anderbytes https://github.com/anderbytes

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/burhanrashid52/PhotoEditor/issues/374#issuecomment-939221872, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEBYLXFK45ZZW3XLWWMC4HLUF66JBANCNFSM5AJG7QRQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

jsericksk commented 2 years ago

I will test the solution you created. Does your PhotoEditorAlternate repository contain the solution you mention? I did try a little bit of what you suggested with SYNC and ASYNC, but it didn't work here, although I didn't do much testing to check it out either.

A curiosity is that here the problem when saving only happens if I insert an image with addImage(bitmap). Using text only works normally. I haven't tested the other options, but I believe some of them might cause the same problem when saving as the add filter option.

anderbytes commented 2 years ago

Yes, the alternate is the one.

The sync/async suggestion eventually went wrong, really. It only changed the thread where the error appears.

As I never use text-only, I could not test that.

Em sáb., 9 de out. de 2021 10:57, Ericks @.***> escreveu:

I will test the solution you created. Does your PhotoEditorAlternate repository contain the solution you mention? I did try a little bit of what you suggested with SYNC and ASYNC, but it didn't work here, although I didn't do much testing to check it out either.

A curiosity is that here the problem when saving only happens if I insert an image with addImage(bitmap). Using text only works normally. I haven't tested the other options, but I believe some of them might cause the same problem when saving as the add filter option.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/burhanrashid52/PhotoEditor/issues/374#issuecomment-939301649, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEBYLXF7F3O5I2AXFA3PXP3UGBC5NANCNFSM5AJG7QRQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

jsericksk commented 2 years ago

Right. I'll test it later and come back to say if it worked or not. Sounds like an interesting solution to me.

anderbytes commented 2 years ago

Yeah good luck. The way the canvas.draw() command works internally, I believe it should natively wait for the bitmap to be not dirty, or at least have a boolean parameter allowing us to request this behaviour.

But no, I had to check it manually.

Em sáb., 9 de out. de 2021 11:20, Ericks @.***> escreveu:

Right. I'll test it later and come back to say if it worked or not. Sounds like an interesting solution to me.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/burhanrashid52/PhotoEditor/issues/374#issuecomment-939305028, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEBYLXBHU5KT6HL7CSC43HTUGBFSFANCNFSM5AJG7QRQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

jsericksk commented 2 years ago

I have tested it and unfortunately the problem keeps happening. I tried to apply a different timeout, but it didn't work.

anderbytes commented 2 years ago

Wow really? Is the error also an indexoutofbounds exception in the logcat? I hope its not a different issue

Em sáb., 9 de out. de 2021 14:24, Ericks @.***> escreveu:

I have tested it and unfortunately the problem keeps happening. I tried to apply a different timeout, but it didn't work.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/burhanrashid52/PhotoEditor/issues/374#issuecomment-939331277, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEBYLXCGQEAUIRB26JJEW2LUGB3GLANCNFSM5AJG7QRQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

jsericksk commented 2 years ago

Not really. Looks like we're having different problems then. What happens to me is: java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps.

Logcat: Process: com.kproject.memezim, PID: 5372 java.lang.RuntimeException: An error occurred while executing doInBackground() at android.os.AsyncTask$4.done(AsyncTask.java:415) at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383) at java.util.concurrent.FutureTask.setException(FutureTask.java:252) at java.util.concurrent.FutureTask.run(FutureTask.java:271) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923) Caused by: java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps at android.graphics.BaseCanvas.onHwBitmapInSwMode(BaseCanvas.java:632) at android.graphics.BaseCanvas.throwIfHwBitmapInSwMode(BaseCanvas.java:639) at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:73) at android.graphics.BaseCanvas.drawBitmap(BaseCanvas.java:131) at android.graphics.Canvas.drawBitmap(Canvas.java:1648) at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:548) at android.widget.ImageView.onDraw(ImageView.java:1436) at android.view.View.draw(View.java:22350) at android.view.View.draw(View.java:22223) at android.view.ViewGroup.drawChild(ViewGroup.java:4516) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4277) at android.view.View.draw(View.java:22221) at android.view.ViewGroup.drawChild(ViewGroup.java:4516) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4277) at android.view.View.draw(View.java:22221) at android.view.ViewGroup.drawChild(ViewGroup.java:4516) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4277) at android.view.View.draw(View.java:22353) at ja.burhanrashid52.photoeditor.PhotoSaverTask.captureView(PhotoSaverTask.java:181) at ja.burhanrashid52.photoeditor.PhotoSaverTask.buildBitmap(PhotoSaverTask.java:108) at ja.burhanrashid52.photoeditor.PhotoSaverTask.saveImageAsBitmap(PhotoSaverTask.java:80) at ja.burhanrashid52.photoeditor.PhotoSaverTask.doInBackground(PhotoSaverTask.java:72) at ja.burhanrashid52.photoeditor.PhotoSaverTask.doInBackground(PhotoSaverTask.java:27) at android.os.AsyncTask$3.call(AsyncTask.java:394) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)  at java.lang.Thread.run(Thread.java:923)

jsericksk commented 2 years ago

I fix the problem. Well, not directly in the library. I'm using my own way to save the image, capturing the photoEditorView view directly. I saw that many faced the same problem (Software rendering doesn't support hardware bitmaps), so I'll leave here the solution that worked for me. I believe the captureView method in the library should be modified to fix the problem directly, but I haven't tried to convert the code to Java since I'm using Kotlin.

Method to capture view:


    fun captureView(view: View, window: Window, bitmapCallback: (Bitmap) -> Unit) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Above Android O, use PixelCopy
            val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
            val location = IntArray(2)
            view.getLocationInWindow(location)
            PixelCopy.request(
                window,
                Rect(location[0], location[1], location[0] + view.width, location[1] + view.height),
                bitmap,
                {
                    if (it == PixelCopy.SUCCESS) {
                        bitmapCallback.invoke(bitmap)
                    }
                },
                Handler(Looper.getMainLooper())
            )
        } else {
            val bitmap = Bitmap.createBitmap(
                view.width, view.height, Bitmap.Config.RGB_565
            )
            val canvas = Canvas(bitmap)
            view.draw(canvas)
            canvas.setBitmap(null)
            bitmapCallback.invoke(bitmap)
        }
    }

So to save the image:

photoEditor.clearHelperBox()
captureView(photoEditorView, window) { bitmap ->
// Operation to save the image normally with Bitmap
}

Reference: https://stackoverflow.com/questions/58314397/java-lang-illegalstateexception-software-rendering-doesnt-support-hardware-bit

anderbytes commented 2 years ago

Well we really were experiencing different issues, after all. Happy that you found your fix. Mine maybe sometime will come, problably when the native libraries of Android handle better the bitmap inside the canvas method.

anderbytes commented 2 years ago

after the code goes to Kotlin I'll take another shot here on trying to debug a "real trusted" version of a solution.

My latest one (and working so far on my device) includes a busy-wait checking every 100ms if the View is Dirty or not, before being Drawn.

Dirty -> Crash NotDirty -> Pass OK

hahai96 commented 2 years ago

@anderbytes I also get indexoutofbounds error exception, but i follow @jsericksk and everything works fine

sreyans01 commented 2 years ago

This is not fixed yet @burhanrashid52

hoangtuancntt commented 2 years ago

Unfortunately, no. I have created a branch with the less-wrong solution I could find, as the main dev doesn't seem to be very bothered by the issue. Even his sample app forces the user to first phisically save the image before sharing it, and I wonder why, probably because he also had issues on sharing the image right out of the screen, as I tracked the issue to be related to the "drawing" being "dirty" (so not available) at the exact moment the bitmap is being generated. In my branch, I have created a busy-wait loop (500ms , I believe) where I test if the drawing ended it's internal tasks and so stepped out of the "dirty" status. That way, I could guarantee that the crash never happens. Em sáb., 9 de out. de 2021 01:12, Ericks @.***> escreveu: Any news about this issue? @anderbytes https://github.com/anderbytes — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#374 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEBYLXFK45ZZW3XLWWMC4HLUF66JBANCNFSM5AJG7QRQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

I still got this issue when add dirty() :((

idish commented 2 years ago

Experiencing that issue as well

wasky commented 1 year ago

The problem originally reported in this thread (IndexOutOfBoundsException: Index: 1, Size: 0) is caused by calling View.draw() in PhotoSaverTask.captureView() in worker thread instead of the main thread. Android documentation says:

By design, Android View objects are not thread-safe. An app is expected to create, use, and destroy UI objects, all on the main thread. If you try to modify or even reference a UI object in a thread other than the main thread, the result can be exceptions, silent failures, crashes, and other undefined misbehavior.

I don't think there's any reason to call View.draw() in worker thread. I made some performance tests. Execution of the method takes less than 20 ms on the slowest device I have (Huawei Y6) and 10 ms on the fastest one (Motorola Edge 30 Ultra).

There's no single-line fix to this issue because there's also some work that has to be done in worker thread (saving bitmap to file). For that reason I decided to rewrite image saving to use Kotlin coroutines instead of deprecated AsyncTask. To do this in the proper way I had to make some other code refactoring, mostly related to improper nullability handling in Kotlin after conversion from Java (for example OnSaveBitmap.onBitmapReady() might return null bitmap which makes no sense and it's not happening in real world scenario). My proposal is available here: https://github.com/wasky/PhotoEditor/commit/444e0b57b43aab2df99027536698d61ace40f905

For all the above reasons my commit is rather large and touches unrelated to the crash parts of the code. So I decided not to create pull request just yet. Maybe I'll be able to split the commit to multiple smaller commits. First one is already waiting for review (#494).

In the meantime feel free to make any comments directly on the commit in my forked repo. I'm open to any ideas what can be improved in my solution.

I can reproduce the crash this way:

repeat(20) {
    thread {
        buildBitmap()
    }
}

The important part is to have some object with the frame when saving. It's seems like this missing element in the ArrayList is the "close" button on the frame which is removed in ui thread - BoxHelper.clearHelperBox().imgClose.setVisibility(). I believe setVisibility() enqueues work in UI thread to refresh views. At the same time doInBackground() is called and it's causing different threads accessing same view.

burhanrashid52 commented 1 year ago

@wasky Can you create a PR for this? I was thinking to keep the old saving API and mark them as deprecated instead of removing it. It will allow users to migrate this gradually or we can release a new 3.0 version with this breaking change. Thoughts?

wasky commented 1 year ago

I think I will be able to keep old API. I'll update the code and create PR.