Tuesday, September 14, 2010

Aquarium v1.95

  - New Feature: Custom Backgrounds
  - Update: Three-position bubbler

Some people apparently don't like the new bubbler, so now the bubbler button is three-position.  Off, old style, new style.  Simple.

Custom backgrounds, on the other hand, took a while to get working, as there's actually some complexity to it under the hood.  On the surface it looks like a button, you push it, an image browser gets launched, you pick an image, and you're good.  Underneath there's some interesting complexity.  Let's discuss that for a bit.

Firstly, since Aquarium is OpenGL rendered, everything needs to be a power of two.  So, the first step was to load the selected image, determine the best power of two, and resize it.  The logic got a little more complicated though, as I wanted to make sure the size was reasonable and the proportions worked out sanely.  So, the actual logic ended up like this:

1) Determine the longest dimension, X or Y
2) Scale that dimension to the nearest power of two, maxing out at 1024.
3) Determine the amount of scaling it took to get it there
4) Scale the other dimension by the same amount
5) Find the other dimension's nearest power of two

This means you get something more or less proportional, with the longest axis safely capped.  Then you do some browsing around and the entire process gets task-killed.  What's up with that?

Basically, a given Android process isn't allowed to allocate more than 20 meg of memory.  In these heady days of 8 megapixel phone cameras, some of these images are freaking big.  Loading a 8 megapixel image and manipulating it takes up several meg of ram.  On top of that, a year from now we'll probaby be seeing phones with 12 megapixel+ cameras.  On top of that, what if somebody has some gigantic 8000x5000 photoshopped something or other on their SD card, and they select it? 

What to do about that?  BitmapFactory has a subclass called Options, which you can use to, well, set options on bitmaps when loaded.  One of these options (inJustDecodeBounds)  tells the system not to load the actual bitmap, but just load the dimensions.  So, to save loading the entire 8 meg image, I get the starting size, then work out what scaling factor (bitmap supports scaling to half, one quarter, one eighth, one sixteenth, etc) gets me to the 1000-1500 range (preferably) then load the bitmap with that scaling factor.  Sweet, I can now rest easy that I'm not blindly reading 40 meg of image data.

This works pretty good, we're back to being well within our memory budget.  Now the problem becomes the secure digital card, which lots of people store their images on.  The SD card can be pulled from the phone, or unmounted to be read via USB, and on top of that is unmounted at boot time for a while.  Basically, the SD card's presence at any given time isn't trustworthy.  So, you need to make sure that the custom image that's selected actually exists when you're trying to read it, and that you have a reasonable fallback -- I end up just using whichever standard background was most recently selected.  After that I check the status of the custom image's path every time the wallpaper becomes active, so if you put the SD card back in (or it finishes mounting after a reboot), it'll eventually get loaded when the user leaves and comes back to the home screen.

If the SD card is removed and the custom image is already loaded as a texture, then sweet, I keep it around until I'm forced to throw it away (like during an OpenGL context change).  This means you can take the SD card out and your custom image will remain visible until you put it back, almost all the time.

After all this, the thing seems to have gotten sewn up pretty well.  Hopefully if anything apocalyptic happens I'll get an e-mail.  :)


  1. Can I redownload the full version again if I had my phone replaced?

  2. So long as you're using the same Google account, it should remember any previous purchases. Just browse to the listing again and you'll see a button labeled "install" instead of "buy".