My JS1K 2014's entry: Nyan Cat

Another year, another JS1K! Last year I created a plasma effect, and this year I wanted to do something completely different.

Remember the Nyan Cat viral video? It’s based on a simple GIF animation, so I thought it had to fit in a single kilobyte of JavaScript.

The end result:

Nyan Cat 1K

To my surprise, the GIF file weights in just over 30Kb (33,067 bytes, to be exact), which appears to be quite a lot, but after careful examination, I understood why.

Even though the animation is made using 8-bit retro style, the reality is that it’s quite more uneven than it looks; some squares are 6x6 pixels, other 5x5, other 6x5… also, some of the elements are displaced by 2 pixels.

So, first I went for the easy stuff, and did the rainbow and the body; that didn’t take long, and I could make them completely accurate to the original; unfortunately, by that time I already realized it would be impossible to make everything fit in just 1Kb; the tail and legs have a relatively complex animation, and the head has quite a lot of detail to it.

So, I recalled seeing a very cool demo that ended in 8th place in the JS1K 2012 competition; it had some cool 8-bit style sprites and even a bit of animation.

The core sprite drawing mechanism for it is really smart:

T=parseInt

// color palette
P='000fb6853345789593fffff6933ccc444555222f00'.match(/.../g)

// insane sprite drawing method
S=function(z,x,y,w,s){for(i=0,f='';i<z.length;i++)f+=new Array((o=P[z[i].charCodeAt(0)-97]||'',n=T(z[i+1]),(n?(i++,n):1)+1)).join('"'+o+'",');for(z=eval('['+f+']'),i=0,r=0,l=0;i<z.length;(r=(++r)>=w?0:r),i++,l=T(i/w))if((v=z[i]))a.fillStyle='#'+v,a.fillRect(x+(r*s),y+(l*s),s,s)}

It’s a bit difficult to understand at first, but it’s really cool; it defines a syntax to paint sprites that is quite optimal for low color counts, which is exactly what I was looking for; after asking for permission to use it, I adapted and optimized it a bit for the specific needs of this animation.

After all the core logic was in place, it was time for optimization; first I used Uglify 2 to minify the code (the options it has are very convenient to preserve hand-made optimizations), and then RegPack; one of the feature I like the most of it is the possibility to know what groups have been optimized, so then it’s possible to search the code for similar points that could be changed to take advantage of the tokens that are already used.

It still didn’t fit, so from there it was just a matter of spending hours looking at the code, and fighting for every byte.

The end result? It fits!! Well… kind of; I was disappointed that I couldn’t include the stars of the original animation, but I’m quite sure those I would have needed 200 to 400 additional bytes, quite far from my “byte budget”.

Apples to Oranges

So, this is how the animations look side-by-side:

Comparing Nyan Cat GIF and JS

Apples to Apples

So, let’s be fair! Now, let’s build a GIF and APNG animations that show exactly the same images as the JS version; it’s 12 frames per image, so here are the results:

Format Size
GIF 18,834 bytes
APNG 5,553 bytes

Certainly it’s quite less than the original, but there’s still quite a gap.