Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App crashes while adding big size images #1044

Open
shuvashish76 opened this issue Dec 18, 2021 · 4 comments · May be fixed by #1055
Open

App crashes while adding big size images #1044

shuvashish76 opened this issue Dec 18, 2021 · 4 comments · May be fixed by #1055

Comments

@shuvashish76
Copy link

shuvashish76 commented Dec 18, 2021

Describe your issue here.
App crashed when I tried to add 3 images of 7.1MB, 16.1kB, 5.6MB to a single PDF

Steps to reproduce

  1. Home > Images to PDF > Select Images
  2. Pick anyof the following Enhancement options
  • Filter Images
  • Preview PDF
  • Rearrange Images
  1. App crashes

Crash logs

FATAL EXCEPTION: main
Process: swati4star.createpdf, PID: 17464
java.lang.RuntimeException: Canvas: trying to draw too large(147842304bytes) bitmap.
	at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:229)
	at android.view.RecordingCanvas.drawBitmap(RecordingCanvas.java:97)
	at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:529)
	at android.widget.ImageView.onDraw(ImageView.java:1367)
	at android.view.View.draw(View.java:19196)
	at android.view.View.updateDisplayListIfDirty(View.java:18146)
	at android.view.View.draw(View.java:18924)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
	at android.view.View.updateDisplayListIfDirty(View.java:18137)
	at android.view.View.draw(View.java:18924)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
	at android.view.View.updateDisplayListIfDirty(View.java:18137)
	at android.view.View.draw(View.java:18924)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
	at android.view.View.updateDisplayListIfDirty(View.java:18137)
	at android.view.View.draw(View.java:18924)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
	at android.view.View.updateDisplayListIfDirty(View.java:18137)
	at android.view.View.draw(View.java:18924)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
	at android.view.View.updateDisplayListIfDirty(View.java:18137)
	at android.view.View.draw(View.java:18924)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
	at android.view.View.updateDisplayListIfDirty(View.java:18137)
	at android.view.View.draw(View.java:18924)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
	at android.view.View.draw(View.java:19199)
	at com.android.internal.policy.DecorView.draw(DecorView.java:788)
	at android.view.View.updateDisplayListIfDirty(View.java:18146)
	at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:676)
	at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:682)
	at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:790)
	at android.view.ViewRootImpl.draw(ViewRootImpl.java:3006)
	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2810)
	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2363)
	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1396)
	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6773)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
	at android.view.Choreographer.doCallbacks(Choreographer.java:778)
	at android.view.Choreographer.doFrame(Choreographer.java:713)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
	at android.os.Handler.handleCallback(Handler.java:790)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loop(Looper.java:164)
	at android.app.ActivityThread.main(ActivityThread.java:6518)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Would you like to work on the issue?

Sorry, I'm not a dev.

@deepoceansame
Copy link
Contributor

deepoceansame commented Mar 11, 2022

I want to try working on this

@KOISHI-CHEN
Copy link

KOISHI-CHEN commented Apr 18, 2022

@codegsaini Hi, I am a teammate of deepoceansame and now we are working on the same project. I consider that I have found the cause of this problem. When I add some large size png format images and try to pick one of the enhancements which shuvashish76 mentions in the question. The crash happens. A similar situation is also found when I simply convert a large size png image to pdf format. When the program tries to display an in the enhancement views, it just simply loads the pictures in Bitmap format like the code here.

Bitmap roundBitmap = BitmapFactory.decodeResource(mContext.getResources(), imageId);

It decodes a png image to bitmap but a 7.6MB or just 4MB can hold even 30000x 20000 pixels, which requires huge memory.
So when the program tries to load such images, it causes many problems such as

java.lang.RuntimeException: Canvas: trying to draw too large(147842304bytes) bitmap. or     Process: swati4star.createpdf, PID: 5288

or

java.lang.RuntimeException: Could not compile and link shader!

which is a strange error.

And in the convert to pdf part. Maybe it is another bug. When I try to convert a 30000x20000 png image. It will cause this error

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
    Process: swati4star.createpdf, PID: 15738
    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.OutOfMemoryError: Failed to allocate a 1528869312 byte allocation with 25165824 free bytes and 442MB until OOM, target footprint 97675984, growth limit 536870912
        at com.itextpdf.text.pdf.codec.PngImage.decodeIdat(PngImage.java:639)
        at com.itextpdf.text.pdf.codec.PngImage.getImage(PngImage.java:558)
        at com.itextpdf.text.pdf.codec.PngImage.getImage(PngImage.java:230)
        at com.itextpdf.text.pdf.codec.PngImage.getImage(PngImage.java:212)
        at com.itextpdf.text.Image.getInstance(Image.java:286)
        at com.itextpdf.text.Image.getInstance(Image.java:238)
        at com.itextpdf.text.Image.getInstance(Image.java:361)
        at swati4star.createpdf.util.CreatePdf.doInBackground(CreatePdf.java:138)
        at swati4star.createpdf.util.CreatePdf.doInBackground(CreatePdf.java:34)
        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) 
I/Process: Sending signal. PID: 15738 SIG: 9

This is because it invokes the getInstance method of lib itextpdf. Inside this method, especially for png pictures, it will also load the png image and uncompress the png image to bytes which causes some memory errors.

It is a little hard for me to tackle this problem because I am just a noob in android development. I have two ideas to solve it. One is that maybe we can load it as the OS does. Load a compressed picture and when the users want to see some detail, we can display that area dynamically by just loads the pixels in that area. And the second way is that we can just easily ban the pictures that cost too large memory.

I will try the first method, but it is a little hard for me to manage it.

@codegsaini
Copy link
Contributor

but it is a little hard for me to manage it.

@KOISHI-CHEN You can have some help by these references.


Caused by: java.lang.OutOfMemoryError: Failed to allocate a 1528869312 byte allocation with 25165824 free bytes and 442MB until OOM, target footprint 97675984, growth limit 536870912

I came across Android Documentation page which has provided the way to efficiently decode bitmaps.

As the Android documentation says -

To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.

So we need to check if the dimensions of image provided are within the limit of memory. The way to check image dimensions before its memory allocation is also mentioned in documentation -

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;`

Setting inJustDecodeBounds = true and then decodeing large bitmap will give null, but it will set options.outWidth and options.outhieght property. So by this trick we can calculate the actual dimensions of given image.
In the above code we got imageHeight and imageWidth so we can check if the dimension is bigger than what we need to display it in the ImageView. Of course we need to scale down image to smaller dimension if it is as big as 30000x20000.

For example if we only require dimension 780x1200 we can use requiredWidth = 780 and requiredHeight=1200. Now, because 30000x20000 is bigger than required dimension, we can scale it down to display without OutOfMemoryError.

The method for scaling efficiently is provided in Android Documentation -

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

And to calculate the options.InSampleSize which suits best, below method provided in documentation can be used -

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

So, instead of decoding actual image directly by-
Bitmap roundBitmap = BitmapFactory.decodeResource(mContext.getResources(), imageId);
we can get bitmap this way -
Bitmap roundBitmap = decodeSampledBitmapFromResource(mContext.getResources(), imageId, requiredWidth, requiredHeight);

@KOISHI-CHEN
Copy link

KOISHI-CHEN commented Apr 19, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants