Pure CSS Perversion
Sometimes it seems that not a month goes by without another “Pure CSS Something” project. Most recently, it was the Pure CSS Twitter “Fail Whale” page that has been making the rounds on Twitter and social news sites for the last few days. Before that, we had the Opera logo made in CSS, and many, many others.
It got me wondering – could one write a program that converts any image into pure CSS & HTML?
As it turns out, that’s already been done. However, I was pretty sure it could be done better – despite limiting the maximum output resolution to 100×100, the CSS/HTML code produced by the aforementioned converter was truly humongous. Surely I could improve on that. Thus I went ahead and wrote my own image to CSS converter anyway.
The result, lovingly code-named “Pure CSS Perversion”, produces remarkably space-efficient* HTML and CSS code for a wide range of input images. You can try it out here.
*Only in the context of image-to-CSS conversion. It’s laughably inefficient when compared to most image file formats.
Awesome Features
- Highly perverse HTML/CSS coding
Almost as space-efficient as storing your images in uncompressed bitmaps! - Glorious 256 color palette
Evokes fond memories of DOS and VGA.
- Dithering support
Greatly improves image quality with only an enormous increase in file size.
- Cross-browser compatibility
Tested in Firefox 3.6, Opera 10.53, Chrome 6.0, and even Internet Explorer 8. - Supports JPG, PNG and GIF
Also supports PCX, ARJ and ponies. Okay, not really.
Examples
Click a screenshot to go to the corresponding converter results page.
You can find more examples on the converter’s homepage.
Descent Into Optimization
For those interested in such things, here is a detailed explanation of how the converter works, including a tedious step-by-step account of how I arrived to the current algorithm. You can also download the source code.
1. Starting Simple
The simplest way to encode an image as HTML/CSS is to convert each image pixel to a separate <div> element 1×1 pixels in size and use an inline style attribute set the div’s background color to that of the pixel. Example :
<div class="css-image"> <div class="row"> <div style="background: #abcdef; width: 1px; height: 1px;"></div> <div style="background: #abcdef; width: 1px; height: 1px;"></div> <!-- more pixels --> </div> <div class="row"> <div style="background: #abcdef; width: 1px; height: 1px;"></div> <div style="background: #abcdef; width: 1px; height: 1px;"></div> <!-- more pixels --> </div> <!-- more rows/scanlines -->
This produces good-looking results, but the amount of HTML code quickly balloons out of control. Even a modest 200×200 picture would result take up over 1 MB when encoded in this way, which is wasteful and can even crash some browsers.
2. Palletise and Cassify
The easiest way to reduce the file size of the “pure CSS” image is to convert it to a palette-based format and create a new CSS class for each palette color. This is what my first version of the image-to-CSS converter did.
<style> div { width: 1px; height: 1px; float: left; } .color1 { background: #abcdef; } .color2 { background: #f00000; } /*....*/ </style> ... <div class="color1"></div><div class="color2"></div>
As you can see, I’ve also moved the rules that apply to all DIVs to a separate CSS block.
3. Run-Length Encoding
To further reduce the code size, we can detect continuous runs of the same color and encode each of them using only a single, wider <div>. To avoid explicitly writing out the width of each element, lets generate CSS classes for widths :
<style> .width2 { width: 2px; } .width3 { width: 3px; } .width4 { width: 4px; } .width5 { width: 5px; } </style> ... <div class="color1 width3"></div>
All <div>’s have a 1-pixel width by default, so we don’t need a “width1” class.
4. Shorter Class Names
All those “colorA” and “widthB” class names are wastefully long. Lets shorten them to a single-letter prefix and a hex-encoded index.
<style> .c1 { background: #abcdef; } .c2 { background: #f00000; } /* ... */ .w9 { width: 9px; } .wa { width: 10px; } .wb { width: 11px; } </style> <div class="c2a wf"></div><div class="c2 wa2"></div>...
5. Default Colors
We can squeeze out a few more bytes by determining the color that’s most frequently used in a given row and adding it as the background color to the container element. Then we can omit the color class from those elements that match the most common color.
<div class="c2"> <div class="c1"></div> <div><!-- matches the row's background color --></div> <div class="c3"></div> </div>
6. Perversion Begins
Hmm, look at all those byte-hogging <div> elements – each eating up 3 bytes on tag name alone. Plus, we’re forced to waste another 6 bytes on the completely useless closing tag! That will not do.
Luckily, the closing tags are optional for <p> and <li> elements. We can rework our HTML structure to make use of this.
<div class="css-image"><ul> <li> <p class="c1 w2"> <p class="c3"> <p class="cba w5"> <!-- more pixels --> <li> <p class="c1 w2"> <p class="c3"> <p class="cba w5"> <!-- more pixels --> <!-- more rows --> <ul></div>
7. Going Classless
We’re also wasting 5 bytes/div on the “class” attribute, so lets get rid of it and use custom attributes instead.
<style> p[c="1"],li[c="1"]{ background: #abcdef; } p[c="2"],li[c="2"]{ background: #f00000; } /*...*/ p[w="9"]{ width: 9px; } p[w="a"]{ width: 10px; } p[w="b"]{ width: 11px; } </stlye> <!-- ... --> <p c="1"><p c="16" w="5"><p c="ab">
It may not validate, but it works. Who cares about validation anyway? π
8. Even More Perversion
Well, that’s probably the limit of size optimization. We need to specify the background color and width somehow, so we can’t completely eliminate the attributes. We could remove the quotes from attribute values and format our output like “<p c=1 w=ab>”, but that’s it. Right?
Actually, there is a way to compress the output even more. We can use the attribute name itself to specify pixel color and width. Observe :
<style> p[c1],li[c1]{ background: #abcdef; } p[c2],li[c2]{ background: #f00000; } /*...*/ p[w9]{ width: 9px; } p[wa]{ width: 10px; } p[wb]{ width: 11px; } </stlye> <!-- ... --> <p c1><p c16 w5><p cab>
Yes, that actually works. It even works in Internet Explorer 8 (when in standards mode).
9. Huffman And Friends
In any given image, some colors are more common than others. For example, a picture of a lush countryside might have a lot of greens, whereas a beach photo would be predominantly blue and yellow. Our current algorithm is not aware of that – it simply enumerates all colors in sequential order, assigning attribute names as it goes. There’s no relation between the color and the codename it gets.
We can reduce output size by assigning shorter attribute names to more common colors. This is known as Huffman coding, and is one of the simplest algorithms used for lossless data compression.
The general idea is thus :
- Iterate over the image and record how many times each color is seen.
- Sort the list of colors by their frequency.
- Assign shorter names to more common colors, longer ones to rare ones.
In fact, we can use this idea to eliminate the color vs. width attribute distinction completely. Lets look at our HTML stucture again :
<p c1><p c16 w5><p cab><p ce2><p c1 w12><!-- ... -->
If we forget the “<p …>” wrappers for a moment…
c1 c16 w5 cab ce2 c1 w12
…it’s just a string of codes! Some more common, some less. We don’t need to know what each of them means to assign a optimally short and unique name to each one.
The actual implementation is somewhat complicated, but the HTML/CSS it produces is very space-efficient. This is what the output of my final algorithm looks like (line breaks inserted for clarity) :
<style> p[a]{width:6px;} p[b],li[b]{background:#dfdbda;} p[c]{width:9px;} p[d],li[d]{background:#dfe3e8;} p[e],li[e]{background:#d4d5dc;} /*...*/ </style> <!-- ... --> <div class="css-image"><ul><li y><p d f><p b c><p d a><p b xb><p o><p b _g><p e><p b l><!-- etc -->
Improve On That
Unbelievable though it may seem, this is still not the limit of size optimization for “pure CSS” images. For example, you could use variable-width right and left borders on the <p> element to encode additional pixels in a single element. This would decrease the HTML size at least by another 20%. You could also replace the primitive RLE algorithm I used in my converter with something more advanced. There might also be some interesting HTML hacks I haven’t even thought about.
I’ll leave that as an exercise to the reader π
Related posts :
I like the “<p a>” hack π
Aye aye. And if someone implemented the border hack I suggested and the ::before and ::after selector hacks that I forgot to mention, each <p> would look more like this :
(A color + width attribute codename pair for the element itself, then its left border, right border, ::after pseudo-element and ::before pseudo-element.)
this looks pretty neat. If I were to write something like this, I’d probably chose something like python to implement the trickier bits. Things are always easier in python π
Significant whitespace :/
I meant for creating the converter of course (in case there was confusion?). Whitespace helps for readability in that case.
I know; I meant that as a matter of personal preference, I dislike the fact that Python’s syntax has significant whitespace.
I like this article. Opera logo One of the most beautiful logo made with css .
I dont know why browser fail or hang when render monolesa and photos but this is interesting.
Neat! It’s like cross stitching a picture.
But how this affects the time of page downloaging?
And if I have several pictures on a page?
The resulting HTML+CSS is rather large, so in the vast majority of cases you’d be better off using a real image like .JPG or .PNG instead. This converter is mostly a fun proof-of-concept, not something you should seriously consider using on your sites.
Interesting but useless. It’s not possible to get smaller size of image after converting it from JPG to HMTL + CSS. Write application to convert JPG to SVG (SVG size smaller than JPG) – it’s theoretical possible but very hard. π If you write it you’ll be my master. π
It is theoretically possible to get a smaller file if the input image is highly compressible, e.g. contains only large, simple shapes filled with the same colour. But yes, it’s still impractical for most inputs.
SVG is not inherently “smaller” than JPG. In fact, the two formats are not even directly comparable as they have different purposes. SVG is a vector graphics format, suitable for storing images constructed from a large number of simple geometric shapes – e.g. logos, icons, and so on. JPEG is a lossily compressed bitmap, best used for photos.
“SVG is not inherently βsmallerβ than JPG. In fact, the two formats are not even directly comparable as they have different purposes. SVG is (…)”
SVG – vector graphics, JPG – raster graphics, yes I know it. You don’t need to explain. π
“It is theoretically possible to get a smaller file if the input image is highly compressible, e.g. contains only large, simple shapes filled with the same colour.”
Like in your second and third image. It’s why I wrote my first comment. π