# OpenGL gradient banding on Samsung Galaxy S2 Android phone

I've got a live wallpaper out on the market which uses OpenGL to render some basic shapes and a flat plane. The simple lighting creates a gradient effect across the plane, which looks fine on most devices. The Samsung Galaxy S2 series seems to have some trouble rendering the gradient, though, as you can see in this screen shot:

The color banding looks awful, especially compared to this screen shot from an Incredible:

I'm using a 565 EGL config in both cases, so I believe this is just a display issue with the GS2 devices. Can anyone confirm this suspicion?

Is there any solution to the banding?

• Is it OpenGL ES 1.x or 2.x? Are you sure you are getting a 565 context on the Incredible, or are you just asking for it? It’s possible that it decides to give you an 888 context anyway. Finally, you may find this forum thread interesting. – sam hocevar Dec 30 '11 at 0:47
• @Sam I really was getting 565 across devices. +1 for the link, though, as it helped me narrow down the problem. Thanks! – Josh Dec 30 '11 at 17:54

You're getting the banding because of a bad setting for colour bit depth on your surface. Romain Guy and Chet Haase (both Google employees who work on graphics in Android gave a presentation that covers these issues at the San Francisco Android User Group in 2010. The video is available on YouTube here and I've actually made some notes on the stuff they cover (shameless plug) here. Specifically the section that you want starts at about 25 minutes in and lasts for 10 minutes.

Relevant section from my notes:

Supported Bitmap formats are ALPHA_8 (for alpha masks), ARGB_4444 (this isn’t recommended, uses 4 bits for each component and therefore doesn’t look very good), ARGB_8888 (recommended, 32 bit images, allows for transparency), and RGB_565 (doesn’t contain alpha channel, limited precision for colours, faster to draw, uses dithering). JPEG images do not contain transparency and pre-Gingerbread were loaded automatically with RGB_565. This meant that jpg images automatically lost some quality pre-gingerbread. Post Gingerbread all images are loaded by default as ARGB_8888 (and application memory usage limits are increased to compensate). When loading a Bitmap, make sure to specify the format that you want, otherwise it will be loaded as its default and every time the Bitmap is drawn it will need to be converted. For instance, pre-Gingerbread the default bit depth for a Surface is 16 bits, so a 32 bit image will need to be converted before rendering which can be slow. You can control quality of this rendering by enabling/disabling dithering on the Paint object and the Drawable object that the Bitmap’s being used by. Blending should be avoided with the alpha channel. If Android detects an image is completely opaque it can perform a faster rendering pass.

(emphasis mine)

There's both performance and precision loss when loading an image with the wrong bit depths and he gives an example at about 30:00 showing how artefacts and banding can occur and the different options you have to cope with it, as well as the differences in rendering speed for each option.

This is relevant even in OpenGL if you're loading an image with BitmapFactory before putting it into a buffer, you can specify the config you want with BitmapFactory.decodeStream().

• This is interesting but it misses the point. First, no image loading is involved (Josh states that the lighting creates the gradient). Second, there is dithering in the screenshots (I can tell from some of the pixels and from seeing quite a few dithers in my time). – sam hocevar Dec 30 '11 at 0:44
• His answer may very well help me, though =P – notlesh Dec 30 '11 at 1:51
• Sam's right, but thanks for the in-depth answer. I'm well aware of Romain's and Chet's works, but unfortunately I'm not working with bitmaps or jpegs or drawables, etc. – Josh Dec 30 '11 at 17:50

I solved color banding in two phases:

1) When we use the BitmapFactory to decode resources it decodes the resource in RGB565which shows color banding, instead of using ARGB_8888, so I used BitmapFactory.Options for setting the decode options to ARGB_8888

Second problem was whenever I scaled the bitmap it was banded again.

2) This was the tough part and took a lot of searching and finally worked. The method Bitmap.createScaledBitmap for scaling bitmaps also reduced the images to RGB565 format after scaling I got banded images (the old method for solving this was using at least one transparent pixel in a PNG but no other format like JPG or BMP worked) so here I created a method CreateScaledBitmap to scale the bitmap with the original bitmap's configurations in the resulting scale bitmap (actually I copied the method from a post by logicnet.dk and translated in Java)

    BitmapFactory.Options myOptions = new BitmapFactory.Options();
myOptions.inDither = true;
myOptions.inScaled = false;
myOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//important
//myOptions.inDither = false;
myOptions.inPurgeable = true;
Bitmap tempImage =
BitmapFactory.decodeResource(getResources(),R.drawable.defaultart, myOptions);//important

//this is important part new scale method created by someone else
tempImage = CreateScaledBitmap(tempImage,300,300,false);

ImageView v = (ImageView)findViewById(R.id.imageView1);
v.setImageBitmap(tempImage);


// the function

public static Bitmap CreateScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
{
Matrix m = new Matrix();
m.setScale(dstWidth  / (float)src.getWidth(), dstHeight / (float)src.getHeight());
Bitmap result = Bitmap.createBitmap(dstWidth, dstHeight, src.getConfig());
Canvas canvas = new Canvas(result);

Paint paint = new Paint();
paint.setFilterBitmap(filter);
canvas.drawBitmap(src, m, paint);

return result;

}


Please correct me if I am wrong. Also comment if it worked for you. I am so happy I solved it, hope it works for you too.

• Thanks Diljeet, but this problem doesn't have to do with loading and displaying bitmaps. It's a problem with the particular device(s) while displaying anything in a window with a 16-bit color depth. – Josh Apr 26 '12 at 23:55

I've noticed the same thing with a background image in a game I've worked on. On my Samsung Vibrant, it looks as expected, but on my wife's Evo, it has the same banding effect. Both are using 565 and OpenGL ES. Unfortunately, this isn't something I've solved yet (though I haven't spent much time with it).

I've pulled the same image up in the embedded web browser and it looks fine, so I can confirm that it's not merely a display limitation.

Have you tried increasing the precision of variables related to color that you use in your shader(s)?

• No, I haven't tried changing precisions. I'll look into that. – Josh Dec 29 '11 at 19:16
• Let us know if that's helpful, it might solve my problem as well :) – notlesh Dec 29 '11 at 19:45
• I cross-posted this question to SO, some of those answers may help you: stackoverflow.com/questions/8669765/… – Josh Dec 29 '11 at 20:19
• Didn't need to touch the precisions. My fix is going to be to just use an 888 config instead of 565 (which may not be optimal, see my answer). – Josh Dec 30 '11 at 17:52

Looks like it really is the GS2's display, or more accurately, its dithering algorithm. I tried upping my requested config to RGB888, and this is what I get (from my test user's phone):

So it really seems like the GS2 just does a horrible job of dithering when trying to map colors in an 888 space to a 565 config.

Now I'm not sure if I want to up the config to 888 across all devices (better quality but a performance hit), or only on devices which I know to dither poorly. Hmmm.

• That's pretty annoying. If you do any performance testing (etc) I'd love to see what you find. Also, I wonder if you could do a quick test (create 565 config, draw something, grab color buffer and examine it) to determine whether a 565 config will suffice or not... – notlesh Dec 30 '11 at 18:04
• @stephelton I'm seeing about a ~5 fps hit on my Incredible, Nexus S handles the higher config a bit better. I'm not sure what you mean about examining the color buffer, what would you expect me to find? – Josh Dec 31 '11 at 1:36
• @Sam Good call, was just waiting a bit to see if anyone else had a better solution. Plus there's a waiting period to accept your own answers. – Josh Dec 31 '11 at 1:37