Yeah! JPEG with Alpha Transparency!

You say it’s not possible? Just wait, but let me start from the beginning …


It was about two years ago, when I started to program another website for another client. It was a product website for premium wines of an Austrian winemaker. The design was supplied by Typejockeys, an excellent design studio with really great designers. They have built an awesome appearance and I was excited that I was allowed to realize a functional website from it. I have built many websites before, so it couldn’t be such a challenge for me.

The requirement was, to make a modern website, that behaves great on each device. Because the pictured wines are premium quality products, it was really important to communicate that fact also with a high quality website.

The last ten years there appeared a lot of very different devices, but there is one special type which challenged me anyway. But one after the other.

How the Challenge Started

I built the website within a few days and showed the result to the designers. All in all they were very happy with it but there was one thing, which they asked about. It was something I actually already have registered but ignored until that moment, because I knew it would get a challenge. They asked me about one sub-page, because it loaded very slow. Actually only the images of the page loaded slow.

To understand the problem, I have to explain the design and layout of this page a little bit. On this sub-page there were a list of all wines, each with one photo of the wine bottle and one detail photo of the label. The bottle photo had to be about 80 × 350 pixels and the detail photo about 600 × 560 pixels. But actually I talk about CSS pixels. That means in mind of retina displays I had to double the widths and heights, so that the images were four times bigger on such devices. There were eight premium wines in sum on that page. So we had eight bottle images 160 × 700 pixel sized and eight detail images 1200 × 1120 pixel sized.

Ok, these are a few big images on one sub-page, but if a good compression is used, that’s not such a problem today. The worldwide average size of a sub-page is about three megabytes. And if you use a suitable compression for all the photos, it’s possible to keep the whole sub-page smaller than that average. The website surely would not be the fastest, but faster than 50% of all websites out there would be an acceptable value in that case.

The appearance of the wine page

… so far so good …

But then a tiny design detail brought the challenge. The designers planned a color gradient behind the bottle images which should stretch over the whole website width. This gradient should be adjacent directly to the bottles. So the photo background should be cut away. And like that is not enough, there should also be a parallax effect. So if the page get scrolled, the color gradient and the bottles should move in different speeds.

In other words: I needed an alpha transparency for the images to realize all that. Until now the only image format, that is supported in all major browsers and supports alpha transparency and additionally can handle enough colors to display a high quality photo, is the PNG format. This is a really excellent format and I remember how happy I was, those days when finally all browsers supported it. It can handle many colors and supports alpha transparency and usually have a great compression result too. But there is one single thing, which is not a good idea regarding to the PNG format. One thing that would immediately increase the file size enormously and make all the great qualities completely useless. And that single thing is, to use it for photos.

However, since the PNG format was the only format that supported all the requirements, I simply used it. But when I have shown the website to the designers, the sub-page, where all the images were placed, was in size over ten megabytes. And you could watch the pictures slowly building up while loading. It felt like those days when we had a 65k modem. It was definitely much too slow for a high quality website nowadays. But which other options would we have?

First Try: Be Creative

First I tried to optimize the compression of the images by using several tools like tinypng.com multiple times in serial. In fact I was hoping for a miracle, because actually I knew that it can’t really get the needed result. I thought, the hope dies last. But I should be right, it brought only a few kilobytes. So nearly nothing.

So I had to be more creative. And then I remembered an image format, that I read about, some time ago. It was an image format which would be optimal for my needs, because it supports alpha transparency and can store photos with a very good compression too. It was the BPG image format from Fabrice Bellard (bellard.org). I have found it again very quickly and looked which browsers supports it. I knew that most of the browsers doesn’t support it, but actually it was no single one. But this didn’t disturb me very much, because for the BPG format a small Javascript decoder exist. So I gave it a try. First I converted all the images into BPG. As I saw the file sizes, I was exceedingly happy, because all images in sum had slightly more than two megabytes, even though they already had included the alpha transparency. Incredibly better than I dared to hope. Then I implemented the small Javascript. It was really easy and straightforward. I loaded the sub-page … it was exciting! Would that be the solution? I couldn’t wait for the answer. It just had to work, because what else could I do? I hadn’t to wait long. The page loaded really fast …

… but what was this? …

… the images weren’t there and the fan of my PC revved up. Was the decoder buggy? Maybe an endless loop? Or have I made a mistake? And suddenly the first image appeared … short time later the second one … and then the third … and so on.
Actually it worked. The images looked great. But it was even slower than the PNG files. This time not the loading process was the problem, but the decoder process.

So, back to the start.

Next Try: Be Megalomaniac

Because the BPG files were so small, I knew that in principle it would be possible to bring the image contents from the server to the browser in an acceptable time. I thought about whether I can change the decoder a little bit to make it faster. But I should have invested a lot of time to understand how the decoder works. And I didn’t know if it’s possible at all to make it faster. So it seemed not adequate to me and I discarded that idea again quickly.

My next thought was that if the BPG format can be such small, maybe I can encode the images in an own format which I can decode faster via an own written Javascript decoder. I know, that sounds crazy, and actually it is, but I didn’t want to give up yet. But how could I make the data such small without the BPG format? Of course I couldn’t really develop an own compression algorithm. For photos the JPEG format would actually be the best choice, but the only problem is, that it doesn’t support the alpha transparency. So how would it be possible to add the transparency? First I couldn’t imagine a solution for that, but I knew that the JPEG format would be my last option, so I kept thinking about it. I considered, to storing the transparency information in any way in the JPEG file, so it would become an own file format which hold a JPEG image and the transparency information. Than I would only have to write a decoder which can read that file and put it on a canvas HTML element. It would be very similar to the BPG variant. A file format, which the browser doesn’t know, would be loaded and then painted on a canvas element.

I kept pursuing this idea and I thought about how I could load and read the file. First I thought to load it via an IMG element, but I couldn’t image that there is a way to read the binary data from it. I needed the binary data to separate the two parts of the file, the JPEG part and the transparency part. So next I thought about load it via Ajax. In that case I would be able to read the binary data. Because I would have my own data format for the transparency part, I would be also able to at least paint the transparency on the canvas element then. Actually I didn’t know at that moment in which format I would save the transparency, but I thought that would be easily solvable. First I had to solve a more difficult issue. The JPEG part would be in binary format too and I knew a way to paint image data from an IMG element to a canvas element, but I didn’t know a way to paint binary data of an image on a canvas element. I thought about a few options. I considered whether I need a JPEG decoder, but I thought there must be a better solution. I thought maybe I could inject it into an IMG element somehow and then transfer it from the IMG element to the canvas element.
While I kept thinking about it, I daydreamed that it would be great to already have the JPEG part of the file in an own IMG tag, while the transparency is somewhere else.

Booom!!! … and suddenly the solution was revealed step by step!
The answers to all my questions came in quick succession. It was so incredible simple. I wondered that I didn’t realized it earlier.

The Solution: Just Have the Idea

Of course I hadn’t have to load the JPEG data and the transparency as one single file. I could load the JPEG file separately in an IMG element, while the transparency is anywhere else. So it would be really easy and fast to bring the photo on the canvas element. And the transparency? Actually I already had a solution for the transparency. The solution, to load it via Ajax, would work, but I would still need a suitable data format. Originally I rather thought to create an own format, but well actually, which format would be better suited to this task then the great PNG format? With these ideas all my problems seemed disappeared. I wouldn’t need an own data format and I also wouldn’t need Ajax anymore, because I could load the PNG via an IMG element too. Just like the photo.

The rest was relatively easy. I created two image files for each displayed image. In the JPEG file there was just the photo with a consistent background color. Then I created the PNG file with exactly the same width and height. The parts, that should be transparent in the result, had to be transparent in the PNG. The color of the visible parts didn’t matter, because I only needed the information where the transparency needs to be. Because the compression of a PNG works best for consistent color areas, I just made the visible parts black.

Then I wrote a small Javascript function, that paints the PNG as well as the JPEG on a canvas element. There were only some minor things I had to find out, such as, how I can put only the alpha transparency of the PNG on the canvas and take the rest from the JPEG. So, how to merge the two images. But all in all it was not such a challenge anymore, but just a short research. On GitHub you’ll find a live demonstration and source files of this technique.

Finally I created a simple fallback in case Javascript isn’t executed. So, when the page is loaded, first there is no color gradient, but only the background color that is also defined in the JPEG as background. So, the JPEG is displayed in front of a consistent color background. The PNG is simply hidden via CSS. The Javascript function then replaces both IMG elements with the new created canvas element. At the same time it adds a CSS class to the element, that should get the color gradient. So, the gradient appears only if the image get transparent.


Although the path was somewhat frustrating in some situations, I love such adventures. What do you think about it? And so far, what has been your biggest challenge that you remember most? I’m very curious! Type your thoughts below ?

Do you like my article? Maybe someone could need that technique. So, don’t keep it secret, just share it 😉 …

Be the first to leave a reply …

or

You can post as a guest if you like. But please consider, that I always review such comments before I switch them public, to prevent spam. If you want to see your comment public immediately, just sign up and in. It's just a click away and it's really simple. You don't even need a password. 😉

made by @eHtmlu