Table of Contents generated with DocToc
- What does it do?
- How do I use it?
- Using the decoder
- Should I use this?
- When should/shouldn't I use this?
- Converter Requirements
- Browser Support
- Performance considerations
- Considerations for sprite sheets
JPEllucent lets you use lossy compression (via JPEG) on full color images with alpha channels. It does this by encoding the color and alpha data separately, and packing them into a JSON file.
On the client side, the components are then decompressed and combined into a single image once more.
Here's a demo that shows off the results.
JPEllucent comes in two parts: jpelcompress.js and JPEllucent.js.
The easiest way to install is to run sudo npm install -g git://github.com/osuushi/JPEllucent.git
.
Note that this depends on ImageMagick 6.6.9 or later, which must be installed separately (earlier versions may work, but have not been tested).
This can be called from the command line like
jpelcompress input_path [-c color_quality] [-a alpha_quality]
or it can be used as a node module and called like
jpelcompress = require('./jpelcompress').compress;
JPEl.compress(inputPath, colorQuality, alphaQuality, function(result) {
doSomethingWithJPElData(result);
});
When called from the command line, a JSON file will be created containing the compressed image. Even at high quality, this file will typically be considerably smaller than the input PNG. The compressed image data is encoded in base64, so it will be even smaller (by about 30%) once it is gzipped.
When used as a module, the convert
method will pass the JPEl object (not stringified) to the
callback you provide.
This is the decoder for the JSON files created by jpelcompress.js. It can be found at
js/JPEllucent.js. It should be included in your HTML immediately before the closing </body>
tag.
The simplest way to use the decoder is to use an ordinary image tag with a data-jpel
attribute
like this:
<img data-jpel="logo.json">
The JPEllucent library will automatically load logo.json and decode it. This will also work for dynamically added image tags (see below for browser support).
Unless you're using a template, using the data-jpel
attribute is a bit cumbersome. A better way is
to use JPEl.createImage(url)
, which will create and return a new image element into which the
decoded data will be loaded.
In some cases, such as when using CSS sprites, you'll need to get the data URI for the decoded image
directly. To do this, you can call JPEl.loadImageToURI(url, callback)
.
That depends. By using JPEllucent, you can reduce image bandwidth significantly, but at the cost of additional processing time on the client side. As with many optimizations, you'll need to run benchmarks to determine if the tradeoff is worthwhile. Also keep in mind that different browsers will take different amounts of time to decode.
JPEllucent is effective for compressing images that are complex, or that have complex alpha channels. A good rule of thumb is this: if it looks like vector art, it will probably compress better as a PNG.
- Node.js 0.10.21 or later
- ImageMagick 6.6.9 or later
JPEllucent will work fully in any modern browser that supports canvas pixel manipulation and the MutationObserver API.
In browsers without adequate canvas support, the JPEl.getFallbackURL
will be called to transform
the JSON URL. By default, this simply changes the extension to "png". So if you keep your original
PNG files next to the JPEllucent JSON files, the default behavior will serve them to older browsers.
Of course, you can overwrite JPEl.getFallbackURL
to return a different URL if needed.
If MutationObserver is not available, JPEllucent will not attempt to fetch images that are added after page load. If you need to support dynamically added images in browsers without MutationObserver, you have four options:
-
Use a shim for MutationObserver.
-
Create the image by calling
JPEl.createImage(url)
, which will return an Image element. This is the recommended way to create JPEl image elements programatically. -
Convert the URL by calling
JPEl.loadImageToURI(url, callback)
, which will pass the result tocallback
. You'll mainly want to do this if you're using a sprite sheet, since you can't use the JSON file in a style sheet. -
Call
JPEL.loadDOMImages([node=document.body])
, which will convert every image element descendent ofnode
which has adata-jpel
attribute. This is convenient for use with templates, but it it is by far the slowest method. For best performance, pass innode
as the nearest ancestor possible. Note: if the MutationObservers are available and enabled, callingJPEl.loadDOMImages
will have no effect. This saves you from having to feature detect before calling it.
#Performance considerations
Because JPEllucent has to decode images on the client side using JavaScript, it may not be appropriate for very large images. There is a trade off of bandwidth and client side processing that you'll need to balance.
For the sake of templating, JPEllucent uses MutationObservers to watch for new <img>
tags with a
data-jpel
attribute. This incurs a performance hit for all DOM modifications. If you are not
adding JPEl images to the DOM dynamically via templates, you can improve performance by calling
JPEl.stopObservers()
, which will disable the MutationObservers.
If you are using templates including JPEl images, you can also improve performance by disabling
MutationObservers and then manually calling JPEl.loadDOMImages(node)
where node
is a node
containing the newly added image elements. You will need to do this whenever a new image is added
or the data-jpel
attribute of an image is changed.
One obvious application for JPEllucent is for compressing sprite sheets. There are a couple of considerations you'll need to account for when doing this:
The obvious difficulty with sprites and JPEllucent is that you cannot use jpelcompress's output as
the URL for a CSS attribute. The workaround for this is to call JPEl.loadImageToURI(url, callback)
and then use JavaScript to add a <style>
element to the page, setting the background to the
generated data URI. Of course, the rest of your sprite CSS can be static.
As noted, JPEllucent excels for complex images. If your sprites are flat and smooth, you're probably better off sticking with PNG. Where this gets complicated is if you have both simple and complex sprites. In this case, you will have to use two separate sprite sheet images, and determine if this tradeoff is worth the better compression ratio from using JPEllucent.
It's not uncommon to see PNG sprite sheets with large areas of empty space. This makes sense; it lets you organize your sprites, and the empty space compresses well, so there's little cost.
Unfortunately, this is not the case with JPEllucent. The decoder works by merging two separate images (one for color data, one for alpha data), and has no way of knowing if a large block of pixels is totally transparent. This may change in the future (by adding extra data at the encoding stage), but for now, you'll see a significant performance benefit if you ensure that your sprite sheets are compact.