I had a conversation with a friend during the holidays that piqued my interest: I was describing some of the hoops I had jumped through to get my level data for my js13k entry to fit into 13kb, and he suggested I might have been able to shortcut some of those hoops by saving the levels as PNG files instead.
This made sense, as my custom algorithm was just doing a simple version of run-length encoding. I’d just be doing the same thing, but adding in the power of imagemin/advpng.
On a whim I did some number crunching to see how feasible this would be for js13k. For
the rest of this post, every PNG and ZIP file size you see has already been recompressed
advpng -z -4 and/or
advzip -z -4, respectively.
My first file is a relatively small JSON file full of numbers. This could be a simple level format, or perhaps a series of monster coordinates, AI paths, maybe font data, etc.
At the top is my raw JSON file, with the output as raw ZIP. Treating the file as raw text (1 ascii byte per pixel) just barely beat the zipped file, but remember that any PNG file will end up in your end ZIP, so you really want to compare it as a ZIP file.
Parsing the JSON file into actual numbers (1 ascii byte per pixel) was a little better, but not by much.
The best results were parsing the JSON file, and then using all 4 channels (RGBA), one for each byte; this of course requires input in the range 0-255 for each byte. This PNG is significantly smaller then the zipped version; after zipping it is at least comparable.
For the second test I used a very large JSON file, one of my levels from js13k this year, although it contains a lot of duplication (many rows of zeroes).
The JSON structure in this case is pretty complex and it would require a lot of work to turn into a single byte stream, so I had to just treat it as text. Again, text-to-RGBA gives the best results, although it’s not significantly better than just zipping the file.
My last example is just a readme file; in a text-heavy adventure game, this might be a series of strings and room descriptions, etc.
As expected, it’s hard to beat a zip file’s dictionary encoding with PNG.
Interested in trying it out yourself? I used pngjs to run my tests. (You could of course generate these out of a canvas in a browser, but in practice you want something that can run as part of your gulp pipeline.)
If you do end up using this technique, then of course you also need to add the code in your game to actually get the level data out of the image in the browser. You can use the standard off-screen canvas technique to do that:
I’m not yet convinced it’s worth it, but I’ll definitely keep this in mind for this year’s comp.