6

I have some code using the new Palette class and I'm getting these crash reports on Crashlytics saying that the width and height must be > 0. What is odd is that this is how I call the palette code:

if( bitmap == null || bitmap.getHeight() <= 0 || bitmap.getWidth() <= 0){
   //do something
}else{
   Palette.Builder(bitmap).generate(new Palette.PaletteAsyncListener() {
.....
}

So I am just not sure how it is possible that the bitmap all of the sudden doesn't have the right height or width. I don't know which piece of my code the exception is coming from because the report only include stuff inside the palette class.

Here is the exception:

java.lang.RuntimeException: An error occured while executing doInBackground()
       at android.os.AsyncTask$3.done(AsyncTask.java:300)
       at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
       at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
       at java.util.concurrent.FutureTask.run(FutureTask.java:242)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
       at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.IllegalArgumentException: width and height must be > 0
       at android.graphics.Bitmap.createBitmap(Bitmap.java:815)
       at android.graphics.Bitmap.createBitmap(Bitmap.java:794)
       at android.graphics.Bitmap.createBitmap(Bitmap.java:725)
       at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:601)
       at android.support.v7.graphics.Palette.scaleBitmapDown(Palette.java:282)
       at android.support.v7.graphics.Palette.access$100(Palette.java:67)
       at android.support.v7.graphics.Palette$Builder.generate(Palette.java:557)
       at android.support.v7.graphics.Palette$Builder$1.doInBackground(Palette.java:623)
       at android.support.v7.graphics.Palette$Builder$1.doInBackground(Palette.java:620)
       at android.os.AsyncTask$2.call(AsyncTask.java:288)
       at java.util.concurrent.FutureTask.run(FutureTask.java:237)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
       at java.lang.Thread.run(Thread.java:818)

I am using the Palette class from com.android.support:palette-v7:23+

Any ideas of what might be wrong?

casolorz
  • 8,486
  • 19
  • 93
  • 200
  • are you really sure that the bitmap has something or is even showing? palette must analyze the image and few ms later, you'll get the color. – Mariano Zorrilla Sep 28 '15 at 18:35
  • Well I only get the errors over Crashlytics, never happens to me. But I am checking for null and and size before calling the palette code. – casolorz Sep 28 '15 at 19:17
  • the app never crashes? you could recreate the issue? if not... maybe some users have a low end smartphone and the task took way too much time and palette find null values (crash over palette and not over the bitmap) – Mariano Zorrilla Sep 28 '15 at 19:20
  • The app has never crashed for me with this issue. I'm not sure why the bitmap would become null while the palette is running, wouldn't the palette have a reference to it? maybe the bitmap is being recycled but would the error then be about it being recycled? – casolorz Sep 28 '15 at 19:58
  • I would guess that the error is caused by the code inside the listener. Could you provide more code (where the dots are in the example)? – Nulano Oct 13 '15 at 16:53
  • It's not in the listener, that is the full stacktrace and it never gets back to the listener. – casolorz Oct 13 '15 at 17:32

3 Answers3

0

You're probably trying to access the width and height of the UI element before it's actually been added to the layout.

That's why you've been getting width and height equals 0, since the element isn't actually there.

Use a ViewTreeObserver http://developer.android.com/reference/android/view/ViewTreeObserver.html

    myView.getViewTreeObserver().addOnPreDrawListener(
            new ViewTreeObserver.OnPreDrawListener() {
                public boolean onPreDraw() {

                        int finalHeight = myView.getMeasuredHeight();
                        int finalWidth = myView.getMeasuredWidth();
                        // Do your work here


                    return true;
                }
            });

where the myView would be your bitmap

Prasanth Louis
  • 4,658
  • 2
  • 34
  • 47
  • I'm not trying to access it, the `Palette` class is. And I'm checking for width and height and null prior to calling the `Palette` class. I can't change the `Palette` code, so how can I make sure this doesn't happen? – casolorz Sep 28 '15 at 19:19
  • Call the Palette class inside the the TreeObserver. That way the bitmap is defined before the palette class tries to access it. – Prasanth Louis Sep 28 '15 at 19:24
  • I don't actually have views that these bitmaps are loaded onto before calling palette code. I load them into the views after I know the palette color. This works fine for most of my users, only getting 4 or 5 crashes a day out of 20k daily users. – casolorz Sep 28 '15 at 19:56
0

I don't think your code has any problem. I was looking at the StackTrace that you posted.

at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:601)
at android.support.v7.graphics.Palette.scaleBitmapDown(Palette.java:282)

If you look at the source code of Palette.java, you will find the scaling logic written in method scaleBitmapDown(Bitmap bitmap). If your 'bitmap' is smaller than 100px, then no scaling is done. However if your 'bitmap' is larger than 100px, then Bitmap.createScaledBitmap() is called. Now this method throws the IllegalArgumentException if either width or height < 0;

So the bitmap you are passing does not have a zero width, because if that was the case, you would have never reached the Bitmap.createScaledBitmap() method at all. That being said, I am not sure what the real reason might be. Try going through the source code of the exact Palette.java that you are using.

Henry
  • 17,490
  • 7
  • 63
  • 98
  • The code for Palette that Android Studio gives me when I click on the pasted stacktrace is a bit different but it does have the same checks you are seeing. I guess if something happened to the bitmap between the check and the scaling would be the only possibility. – casolorz Oct 13 '15 at 14:02
  • My guess too. Is the crash occurring in some particular device, on a particular version ? If that's the case, then probably the OS version code has the bug. But in any case, I don't think you do anything about it. – Henry Oct 13 '15 at 14:30
0

The problem is related to a concurrency issue: since you are using the Palette in the 'async' way, when you invoke

Palette.Builder(bitmap).generate(new Palette.PaletteAsyncListener()

your bitmap is valid but when the Palette uses the bitmap in its own async logic, the bitmap may be null or not valid anymore (due to a concurrent manipulation).

There's an easy fix for this issue: since you don't need an high-res image to extract the key colors, just provide a low-res bitmap created from your original one:

if( bitmap == null || bitmap.getHeight() <= 0 || bitmap.getWidth() <= 0){
   //do something
}else{
   //This resized bitmap is just an example: you should keep your bitmap
   //aspect ratio, and keep width and height < 100
   Bitmap resizedBitmap = Bitmap.createScaledBitmap(myBitmap, 100, 100, false); 
   Palette.Builder(resizedBitmap).generate(new Palette.PaletteAsyncListener() {
.....
}
bonnyz
  • 13,458
  • 5
  • 46
  • 70
  • Interesting idea, I'll try that. Thanks. – casolorz Oct 13 '15 at 13:45
  • @mntgoat Don't forget to keep the aspect ratio in your resized bitmap, or you may get wrong key colors (I'm forcing 100x100px in the example) – bonnyz Oct 13 '15 at 13:51
  • Yeah I am. The code I'm actually seeing for the Palette class uses 192 as the minimum. – casolorz Oct 13 '15 at 14:04
  • @mntgoat You should use a size < 100px on both width and height to avoid extra scaling/bitmap creating by the Palette – bonnyz Oct 13 '15 at 14:07
  • I'm going to award you the bounty before it expires but I don't yet know if this is the correct answer. I'll release it to my beta users and if in a few days I don't get any more crashes then I'll mark it as the right answer. – casolorz Oct 13 '15 at 17:34