koral--/android-gif-drawable

Canvas: trying to draw too large(1765130656bytes) bitmap

quekangkang opened this issue · 6 comments

java.lang.RuntimeException: Canvas: trying to draw too large(1765130656bytes) bitmap.
at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:280)
at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:88)
at pl.droidsonroids.gif.GifDrawable.draw(Unknown Source:786)
at android.widget.ImageView.onDraw(ImageView.java:1430)

Hello, I met a crash. Here is a 1683.359771728516M bitmap, it is impossiable. When local gif file is damaged, may cause width and height obtain not correctly!

following is the source code I find.

android-gif-drawable/src/main/c/metadata.c
__unused JNIEXPORT jint JNICALL
Java_pl_droidsonroids_gif_GifInfoHandle_getWidth(__unused JNIEnv *env, jclass __unused class, jlong gifInfo) {
	GifInfo *info = (GifInfo *) (intptr_t) gifInfo;
	if (info == NULL) {
		return 0;
	}
	return (jint) info->gifFilePtr->SWidth;
}

__unused JNIEXPORT jint JNICALL
Java_pl_droidsonroids_gif_GifInfoHandle_getHeight(__unused JNIEnv *env, jclass __unused class, jlong gifInfo) {
	GifInfo *info = (GifInfo *) (intptr_t) gifInfo;
	if (info == NULL) {
		return 0;
	}
	return (jint) info->gifFilePtr->SHeight;
}

it is not occured on my devices but reported by user online.
I will be appreciated if you could check the issue !


Updated:
I find a method to reproduce the crash

  1. find one normal gif file
  2. edit it with notepad++ or other editor can modify it in bytecode.
  3. modify the the sixth and seventh (correspond with width) to "40","40". and eighth and ninth (correspond with height) to "40',"40".
  4. run with a demo like following to load the gif file
        try {
            GifDrawable gifDrawable = new GifDrawable(getResources(), R.mipmap.horrizontal_end_arrow_error);
            int intrinsicWidth = gifDrawable.getIntrinsicWidth();
            int height = gifDrawable.getIntrinsicHeight();
            Log.i(TAG, "width: " + intrinsicWidth + " height: " + height);
            imageView.setImageDrawable(gifDrawable);
        } catch (IOException e) {
            e.printStackTrace();
        }
  1. then the crash reproduced!

I am sorry for cannot upload the damaged gif local file.
refer: https://segmentfault.com/a/1190000020886959

What is your expectation in case of malformed files?

@koral-- Thanks for reply. I have tried create a New Drawable class extends android.graphics.drawable.Drawable and override draw() method,check size before super.draw(), but the draw() method was invoked many times also.

/**
     * bitmap {@link RecordingCanvas#MAX_BITMAP_SIZE}
     */
    public static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024;

    @Override
    public void draw(@NonNull Canvas canvas) {
        int frameByteCount = getFrameByteCount();
        if (frameByteCount > MAX_BITMAP_SIZE) {
            Log.i("GifDrawableSafe", "frameByteCount > MAX_BITMAP_SIZE frameByeCount: " + frameByteCount + " MAX_BITMAP_SIZE: " + MAX_BITMAP_SIZE);
            return;
        }
        super.draw(canvas);
    }

2021-03-15 10:28:24.956 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.041 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.054 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.120 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.137 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.187 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.220 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.270 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.303 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.337 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.387 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600
2021-03-15 10:28:25.420 16893-16893/com.example.test1 I/GifDrawableSafe: frameByteCount > MAX_BITMAP_SIZE frameByeCount: 1082146816 MAX_BITMAP_SIZE: 104857600

Maybe add a check code on constructor method is better. Checking bitmap size before create bitmap or after.

pl.droidsonroids.gif.GifDrawable#GifDrawable(pl.droidsonroids.gif.GifInfoHandle, pl.droidsonroids.gif.GifDrawable, java.util.concurrent.ScheduledThreadPoolExecutor, boolean)

if (this.mNativeInfoHandle.getWidth() * this.mNativeInfoHandle.getHeight() * 4 > MAX_BITMAP_SIZE) { //argb for 4 bytes
     Log.i("GifDrawable", "trying to add too large gif size : width : " + this.mNativeInfoHandle.getWidth() + "height: " + this.mNativeInfoHandle.getHeight() );
     // ... do draw nothing (I am not clearly about the GifDrawable, what I mean is do nothing but not drawing a too big bitmap)
    return;
}
if (oldBitmap == null) {
            this.mBuffer = Bitmap.createBitmap(this.mNativeInfoHandle.getWidth(), this.mNativeInfoHandle.getHeight(), Config.ARGB_8888);
        } else {
            this.mBuffer = oldBitmap;
        }


Why 100 MB as MAX bitmap size?

Why 100 MB as MAX bitmap size?

refer: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/RecordingCanvas.java;l=275?q=RecordingCanvas

Android API hide this constant field.

/** @hide */
    public static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
...
 /** @hide */
    @Override
    protected void throwIfCannotDraw(Bitmap bitmap) {
        super.throwIfCannotDraw(bitmap);
        int bitmapSize = bitmap.getByteCount();
        if (bitmapSize > MAX_BITMAP_SIZE) {
            throw new RuntimeException(
                    "Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
        }
    }

It can be designed to supported setting max value,100 MB is the default value. : )

I don't think that it should be a library responsibility to do checks like that.
One may want to draw on something else than RecordingCanvas or draw in a customized way or maybe don't draw at all but only get some metadata.

OK, I see. I'll do some checks before setImageResource, thanks !