I tried to replace the legacy 2D rendering code of WordMix, which uses the native Android canvas methods, with an OpenGL renderer to allow for fancy effects and animations.
simple texture with full bleed image, no border
Because the tiles are simple rectangles with round corners, I created a texture with gimp and rendered a quad in OpenGL. The texture had no mipmaps and was filtered linear for both, minimizing and magnifying. When rotating that quad, I got the typical “staircase” lines, because I did not use anti-aliasing / multisampling. The result looks rather horrible:
no multisampling, no mipmaps, simple texture result in typical “staircase” borders
You can see two effects, one if it being the clear staircase borders, where the texture is not linear filtered, and you see the round corners of the texture with a grayish border, I’ll explain in the next paragraphs.
Multisampling emulation to remove “staircase”
So how to achieve multisampling in OpenGL ES 1.1? The answer I found is quite simple and easy on the hardware: use a texture with a transparent border and linear texture interpolation will do the rest. So I modified the texture to include a transparent border and rendered the quads slightly bigger to fill the same amount of pixels.
texture image with transparent border
The result looked better but I was not satisfied with the borders. I saw the interpolations but there is still a very visible “staircase”. Plus it seems, that the borders are blended with a black color, which can be seen on the overlapping tiles:
no mipmaps, texture with transparent border, still visible “staircase” and dark colored border
This is in fact due to my texture, which had the transparent pixels assigned the color black. The OpenGL interpolation would just average two neighbour pixels, which would calculate like
(argb(1, 1, 1, 1) + argb(0, 0, 0, 0)) / 2 = argb(0.5, 0.5, 0.5, 0.5)
which is a semi transparent gray color tone.
Monkeying with gimp for transparent pixel
So how to create a texture, where the transparent pixels have the color white? Gimp seemed to screw up the color of transparent pixels even though when exporting my work as png file, it offers to keep the color of transparent pixels.
The trick: combine all visible planes, create an alpha channel and change the color layer. If you have uncombined planes, the result is unpredictable and the colors are screwed up.
So now I had a texture with a white but fully transparent border (value 0x00FFFFFF) and I’d expect the calculation to be
(argb(1, 1, 1, 1) + argb(0, 1, 1, 1)) / 2 = argb(0.5, 1, 1, 1)
But I still got the same result:
texture with transparent + white border: still black border in Android
Bitmaps with transparent pixels in Android
So why is my border still black, while the texture has white transparent regions? I checked the loaded Bitmap with this code after loading the png resource:
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.stone);
Log.d("texture", bmp.getPixel(0, 0)); /* result: 0 */
Why is the result 0?? I’d expect a 0x00FFFFFF, but either Androids Bitmap loader premultiplies the alpha or recompresses the image file on compile, although I did place the image in the res/drawable-nodpi folder.
But apparently Bitmap and Canvas throw away all color information, when drawing with an alpha value of 0. This results in a fully transparent, but black canvas:
canvas.drawColor(Color.argb(0, 255, 255, 255), Mode.SRC);
Log.d("texture", bmp.getPixel(0, 0)); /* result: 0 */
while the following results in a white canvas, which is almost transparent (1/256):
canvas.drawColor(Color.argb(1, 255, 255, 255), Mode.SRC);
Log.d("texture", bmp.getPixel(0, 0)); /* result: 0x01FFFFFF */
Good to know, so now I create my texture with a border that is almost transparent, but not completely (alpha value 1/256) and the color white, which should be hardly visible, calculating like:
(argb(1, 1, 1, 1) + argb(0.01, 1, 1, 1)) / 2 = argb(0.505, 1, 1, 1)
I checked with above Log code and indeed got the value 0x01FFFFFF. So at least the Bitmap was loaded correctly now. But I still get a black border and the result looks the same. Why?
Creating OpenGL textures with unmultiplied alpha
I found a post and bug report that apparently the GLUtils.glTexImage2D() screws with the alpha and colors too, creating texture values of 0×01010101, which gets blended with the nearby white pixels on linear filtering. What the…?
The post suggests a workaround to not use GLUtils to load the Bitmap into an OpenGL texture but use the original GL10.glTexImage2D(). While the code in that post is not very efficient, it does result in nice and smooth blended borders. Of course the use of mipmaps helps too to make the texture smooth when minified:
texture with 0x01FFFFFF almost-transparent border and use of original GL10.glTexImage2D method and mipmaps
Several culprits were found to make antialiasing work with an Android App using OpenGL ES 1.1:
- Create textures that have transparent borders, so linear filtering emulated oversampling at polygon borders
- Make sure the transparent border of your texture contains color values, which will “bleed” into the border pixels of the texture.
- If you use mipmaps, make sure you have enough transparent border pixels or set GL_TEXTURE_WRAP to GL_CLAMP.
- Double check result, because gimp does screw up when having multiple layers, that are merged when exporting as png image.
- Androids Bitmap loader and Canvas code seems to zero out the color values when alpha is 0. The workaround to keep the color values on load: Use colored pixels with alpha value of 1 (of 255).
- The GLUtils.glTexImage2D implementation premultiplies alpha values with color values, resulting in very dark color, instead of the white I wanted. Use the GL10.glTexImage2D directly (example in this post).
Using mipmaps and adding a nice shadow texture results in a screen, that looks very similar to the original, but which is much faster:
final result with all workarounds, mipmaps and shadows