Transparent JPGs
Reducing the file size of transparent images
During a recent project - a web app for creating custom training shoe designs from the colours in your Instagram photos - I needed to reduce the total page download size substantially for delivery to mobiles.
At one stage during early development one of the pages contained over 750Kb of 32-bit PNG files. Obviously I wanted to get that size down. However the shoe images themselves had to remain transparent for the app to work as intended.
Initial PNG set up

PNG on top of JPG - 162KB
Example HTML
<div class="item" style="background:url('img/background-1.jpg');">
<img src="img/AM1.png" width="306" height="306" alt="Transparent PNG">
</div>
Initially the app had a typical set up. One transparent 32-bit PNG placed on top of a background JPG. Both images are high-res 512px x 512px for retina displays resized down to 306px x 306px with CSS. Even with severe compression the total file size is 162KB.
Our app was going to need something radical to reduce this total.
Canvas compositing
The Canvas
element has been around for quite some time now (Firefox 1.5). It’s generally seen as somewhat of an alternative to a Flash. An interactive and scriptable container for charts, games and 3D environments. However there are a couple of less well know features of the Canvas API that can be very useful.
Firstly the element itself is transparent - just like a div
. It can be sized, stacked and have background colours applied to it using CSS. And secondly it’s great for image manipulation such as drawing, scaling, cropping and compositing.
The globalCompositeOperation
method provides a number of different options. The XOR
composite removes the pixels from one image where they are overlapped by an image on top leaving the combined image transparent.
Transparent Canvas implementation
We took the Instagram image and set it as the background of the div
containing our canvas
element.

Background image JPG - 25KB
The base for our composite was a JPG of our shoes with a white background. This was compressed ~40% leaving us with an image file size around 32KB.

Shoe JPG - 32KB
The key part of to the compositing operation is the mask image. This is a black and white transparent PNG that knocks out the pixels from the JPG image. These are 8-bit PNGs around ~4Kb.

Mask 8-bit PNG - 4KB
I wrote a simple JavaScript class (3KB) that loads the shoe and mask images and performs the compositing leaving a transparent canvas element. This is displayed on top of the background image in the same way as the PNG implementation above.
Javascript
(function (window, document) {
var TransparentJPGs = function () {
var transparentJPGs,
i,
init = function () {
transparentJPGs = document.getElementsByClassName('transparent-jpg');
for (i = 0; i < transparentJPGs.length; i++) {
var canvasElement = transparentJPGs[i];
loadImages({
rgb: canvasElement.getAttribute('data-rgb'),
mask: canvasElement.getAttribute('data-mask')
}, render, i);
}
},
loadImages = function (files, callback, count) {
var toLoad = 0,
images = {},
id,
image,
loaded;
loaded = function () {
--toLoad;
if (!toLoad) {
callback(images, count);
}
};
for (id in files) {
if (files.hasOwnProperty(id)) {
image = new Image();
++toLoad;
image.onload = loaded;
image.src = files[id];
images[id] = image;
}
}
},
render = function (images, i) {
var canvasContext = transparentJPGs[i].getContext('2d'),
x = 0,
y = 0;
canvasContext.drawImage(images.rgb, x, y);
canvasContext.globalCompositeOperation = 'xor';
canvasContext.drawImage(images.mask, x, y);
};
init();
};
window.TransparentJPGs = new TransparentJPGs();
})(window, document);
Final markup
<figure class="item">
<canvas class="transparent-jpg" style="background:url('img/background-1.jpg');" width="612" height="612" data-rgb="img/AM1.jpg" data-mask="img/AM1-mask.png">
</canvas>
</figure>
Result!
Combined file size of images - 65KB
That’s an 80% saving! Nearly 100KB over a single PNG for no noticeable loss in image quality. There is an extra network request for the mask image and one for the JS but they are cached and used multiple multiple times.
I’m not sure if this is a niche application or if it can have more wide-spread uses. Also with new image formats slowly coming over the horizon we soon won’t need to do this kind of thing for much longer. If you like the idea take a look at the demo, check out the repo on GitHub.