Quick background info, or 'what the hell is a Stylunk?'
I play Clan Lord, a multiplayer online RPG. Clan Lord provides a lot of ways for players to customize their characters' appearance, including a system for changing shirt and pants colors with an algorithmic dye and bleach system. Experimenting with dyes and bleaches in-game can be prohibitive — each application consumes in-game currency and (often rare) materials. As a result, there's a long tradition of external dye-simulation tools; the earliest was Stylus (named in the 'function-us' pattern of many Clan Lord NPCs), followed by Stylos, the OS X re-imagining of Stylus. Stylos didn't work well on Mac OS X 10.4 Tiger, so in 2005 I built the first version of Stylunk as a replacement.
Since then the number one Stylunk-related request has been for a Windows version, and the number two request is 'hey, add this new shirt color!'. Both of which were difficult enough to fill that they happened never (Windows version) and only intermittently. So I've decided to simplify cross-platform concerns and centralize updating by turning Stylunk into a web app. The beta is right here.
I started playing Clan Lord in 1998 and it had already been in development for a while at that point, so it shouldn't be a surprise that the tech isn't exactly modern. Images in Clan Lord are an run-length-encoded list of indices into a per-image color map, which itself contains indices into the 'Mac standard' 256 color map. That's all pretty integral to Clan Lord, because character customization is implemented as a set of 20 color overrides for the per-image colormap: indices zero and one contain the highlight and shadow colors for the character's hair, for instance.
Images for a particular icon are arranged into a single 16x3 sprite sheet, comprising the 32 standing/walking/running sprites in eight cardinal directions as well as a 'dead' sprite and fifteen 'poses' that players can activate to emote. Here's what the 'Ghorak Zo' sprite sheet looks like:
My first stab at implementing web Stylunk was about what you'd expect: I converted each of the sprite sheets into PNG files and wrote code that draws the selected sprite into a canvas, iterates across the pixels, and replaces the 'default' colors with the mapped values calculated from the current UI selection. Then I created a smaller canvas, copied the frame I needed from the large canvas to the small canvas, and used
toDataURL() to extract something that I could stick into the
background-image of my buttons.
It all worked fairly well, at least at first. On my main machine (a Core i7 Mac Mini) everything was peachy. On my MacBook Air, it was sluggish. On my iPad... well, the less said the better.
My next thought was: I'm doing more work than I need to: there's no point colorizing the entire sprite sheet and then only displaying a single frame of it. So I swapped things around; first extract a small canvas from the larger, then recolor only the smaller canvas. That helped too, but it was still pretty logey on the iPad.
XMLHttpRequest, which simplifies the caching a lot by letting the browser do the work.