[philiptellis] /bb|[^b]{2}/
Never stop Grokking

Sunday, April 24, 2011

pngtocss and getting back to basics

A couple of days ago Nicole tweeted about the absence of a tool to convert images to gradients. I didn't see the tweet right away because I was asleep at the time, but Sergey retweeted it later under the @perfplanet account and I caught that.

My first thought was that there really must be something like that. Why not just use the gimp's colour picker or something. My second thought was, "what the heck are CSS gradients?" followed closely by, "I've never read a PNG in C before".

Back to basics

All of this brought up a sense of déjà vu back to 1999, to the birth of my first opensource project. That started out with someone on the ilug-bom mailing list asking how he could find out which server was running on a given host. I started to type out an explanation and then figured that it would take me less time to hack up a solution in perl, so I did, and httptype was born.

Yesterday was very similar, the only real difference being that this time I knew how to use version control, so I jumped in with:
mkdir pngtocss
cd pngtocss
git init
mkdir src
And then started on a bit of research. First to find out how to read in a PNG.

Reading in a PNG

libpng.org has a great book on using the libpng library. It's a long book and I'm a lazy dev, so I didn't read the whole thing. Instead I found the Chapter that dealt specifically with reading a PNG into memory. It turned out to be Chapter 13. Now when I say "I found", I really mean that Yahoo! found it for me.

Anyway, I read the doc and it was pretty straightforward, so I jumped right into writing code, but guess what? My C programming skills were rusty. My Makefile skills more so. Having man at hand helped things move along and I was soon up to speed.

Once I had an in-memory array of the image's pixels, I had to figure out how to write out the CSS.

CSS gradients

There were a few sites that helped with this, in particular CSS tricks. A few printfs later and I had simple gradients done.

The code was simple, and handled a very specific case. It didn't work with all possible gradients, and wouldn't be able to handle a sprite, but this is how projects grow. You don't try to solve everything at the start. You solve a use case and push it out there to find out what people want.

The last time around I pushed it out to the mailing list and got a lot of feedback, patches and even a mentor. This time I announced it on twitter and got a bunch of feedback and a ton of retweets.

The physics of gradients

Today I decided to look at multi-colour gradients. Not because anyone had asked for it, but because I thought it might be interesting to figure out where a colour shows up.

Now there are various ways to look at a gradient. If you think about it from a physics background, it's essentially a particle moving with constant velocity. Each pixel position is a unit of time and the colour value is the particle's position at that point in time. The position at any point of time is calculated by the second equation of motion:
s = ut + ½at²
For a simple two colour gradient, there's no acceleration, and the velocity u is the difference in the two colours divided by the number of pixels in that direction. This is the same as the difference in colour of adjacent pixels (colour differential per unit pixel).

The cool thing is that for CSS, you only need to specify the end points and it will fill in everything else.

Multi-colour gradients

When dealing with multi-colour gradients, you need to figure out where the stop points are. Once you know what the colour velocity at the start of the gradient, you keep iterating through the image until the velocity is no longer what you expect it to be. This is a colour stop. Note down the new colour and its position and restart.

To speed things up, rather than look at every pixel along the way, I exponentially increase the pixel index that I look at until I hit a bump and then backtrack till I find what I was looking for. A sort of binary search.

Putting it all together, I ended up with this example page which has gradient images and the equivalent CSS gradient side by side. There are still limitations, but none that I care to fix at the moment.

Other tools

Also of note is Alex Sirota's Gradient Editor as part of Colorzilla. It's completely web-based and can also take in a file upload and generate a gradient from that. It doesn't handle the rainbow gradient yet, but I suppose it won't be too hard to get around that.


In any case, pngtocss is on github at https://github.com/bluesmoon/pngtocss, feel free to fork it and submit patches. The code isn't pretty and isn't really commented either.


Post a Comment