Image Optimization Guide

On the forum I administer, I am forced to run a tight attachment policy. Disk space doesn’t grow on trees. Occasionally this leads to questions about the small attachment size limit of 50 KiB. This guide is intended to clarify that this is not nearly as tiny as you might think. Note that although I’ll mention commands without much explanation for the sake of brevity, you’re always recommended to further explore the possibilities offered by those commands with the --help flag as well as by running man the-command-here.

First you need to ask yourself what kind of file type is appropriate, if you have the choice. On screenshots, the main purpose of attachments on my forum, you’ll often encounter large areas of uniform background colors. PNG is therefore almost invariably the right choice. Crop out everything but what’s relevant. JPEG is appropriate for more dynamic pictures such as photographs. If you want to do a lot with photographs, you might want to consider an external hosting service. My wife likes SmugMug. Still, for thumbnails you might be able to do a fair bit more within a few hundred KiB than you might think. Finally, the vector graphics in SVG result in pictures that always look sharp. You’ll typically have drawn these in a program like Inkscape or Adobe Illustrator.

Table of Contents

  1. 1. Optimizing JPEG
  2. 2. Optimizing PNG
  3. 3. Optimizing SVG
  4. Addendum A: Scanned Documents
  5. Addendum B: Video

1. Optimizing JPEG

Often you’ll want to crop your file. Do not edit your JPEG followed by resaving it because this will result in reduced quality! You can crop losslessly with cropgui. On Windows you can use IrfanView.

If you don’t want to crop, and also potentially for some post-cropgui optimization, use jpegtran -copy none -progressive -optimize file.jpg > file-opt.jpg. Note that this will get rid of all metadata, which may be undesirable. If so, use jpegtran -copy all -progressive -optimize file.jpg > file-opt.jpg.

Of course if you want to scale down your JPEG there’s no point in mucking about with lossless cropping first. After scaling down, check how long your quality can go (also see a little helper script I wrote). In any case, you should avoid introducing any unnecessary compression steps with associated quality loss. Here are some results:

  • The original 11.jpg at 2.19 MB.
  • Losslessly cropped 11-crop.jpg at 1.11 MB.
  • Optimized with -copy all -progressive -optimize 11-crop-opt.jpg at 1.04 MB. -copy none would’ve saved an extra whopping 40-some KiB, which on this kind of filesize has little benefit, and besides, I quite like the metadata. For thumbnail-sized files the balance is likely to be different. For example, the 52.2 KiB SmugMug auto-generated thumbnail below can be insignificantly reduced to 51.1 KiB with --copy all, but to 48.2 KiB with --copy none. I think an 8% reduction is not too shabby, plus it brings the file size down to under the arbitrary 50 KiB limit on my forum.

2. Optimizing PNG

As I wrote in the introduction, for screenshots PNG is typically the right choice. If you want to use lossless PNG, use optipng -o7. In my experience it’s ever so slightly smaller than other solutions like pngcrush. But as long as you use a PNG optimizer it shouldn’t much matter which one you fancy. Also see this comparison.

If you don’t care about potentially losing some color accuracy, use pngquant instead. To top it off, if you really want to squeeze out your PNG, you can pass quality settings with --quality min-max, meaning you can pass --quality 30-50 or just --quality 10. Here are some quick results for the screenshot in the SVG section below, but be sure to check out the pngquant website for some impressive examples.

$ du -h --apparent-size inkscape-plain-svg.png
27K	inkscape-plain-svg.png

$ du -h --apparent-size inkscape-plain-svg-fs8\ default.png 
7.6K	inkscape-plain-svg-fs8 default.png

$ du -h --apparent-size inkscape-plain-svg-fs8\ quality\ 10.png 
4.3K	inkscape-plain-svg-fs8 quality 10.png

In this case there is no visual distinction between the original PNG and the default pngquant settings. The quality 10 result is almost imperceptibly worse unless you look closely, so I didn’t bother to include a sample.

3. Optimizing SVG

For using SVG on the web, I imagine I don’t have to tell you that in Inkscape, you should save your file as Plain SVG.

Save as Plain SVG in Inkscape.

What you may not know is that just like there are lossy PNGs, you can also create what amounts to lossy SVGs. There are some command-line tools to optimize SVGs, including (partially thanks to this SO answer):

  • Scour is probably the best command line tool for some quick optimization. You can just use the defaults like scour < in.svg > out.svg or scour -i in.svg -o out.svg. But I recommend you go further.
  • SVGO (SVG Optimizer)
  • SVG-optimiser (by Peter Collingridge)
  • SVG-editor (by Peter Collingridge

My personal preference for squeezing out every last byte goes toward the web-based version of the SVG-editor by Peter Collingridge. By running it in a browser with inferior SVG support such as Firefox, you’ll be sure that your optimized SVG still works properly afterward. The command line tools can only safely be used for basic optimizations, whereas the effects of going lossy (such as lowering precision) can only be fully appreciated graphically.

Addendum A: Scanned Documents

Scanned documents are a different item altogether. The best format for private use is DjVu, but for public sharing PDF is probably preferable. To achieve the best results, you should scan your documents in TIFF or PNG, followed by processing with unpaper or ScanTailor. If you’ve already got a PDF you’d like to improve, you can use pdfsandwich or my own readablepdf.

Addendum B: Video

I’m not aware of any lossless optimization for video compression such as offered by jpegtran, but you can often losslessly cut video. In the general purpose editor Avidemux, simply make sure both video and audio are set to copy. There is also a dedicated cross-platform app for lossless trimming of videos called, unsurprisingly, LosslessCut. If you do want to introduce loss for a smaller file size you can use the very same Avidemux with a different setting, ffmpeg, mpv, VLC, and so forth. You can get reasonable quality that’ll play many places with something like:

ffmpeg -i input-file.ext -c:v libx264 -crf 19 -preset slow -c:a libfaac -b:a 192k -ac 2 output-file.mp4

For the open WebM format, you can use something along these lines:

ffmpeg -i input.mp4 -c:v libvpx -b:v 1M -c:a libvorbis output.webm

More examples on the ffmpeg wiki. Note that in many cases you should just copy the audio using -acodec copy, but of course that’s not always an option. Extra compression artifacts in audio detract significantly more from the experience than low-quality video.


Le Champignon qui s’est retiré du monde

Les Levantins en leur légende
Disent qu’un certain Champignon las des soins d’ici-bas,
Dans un fromage de Hollande
Se retira loin du tracas.
La solitude était profonde,
S’étendant partout à la ronde.
Notre ermite nouveau subsistait là-dedans.
Il fit tant de pieds et de dents
Qu’en peu de jours il eut au fond de l’ermitage
Le vivre et le couvert : que faut-il davantage ?
Il devint gros et gras ; Dieu prodigue ses biens
A ceux qui font voeu d’être siens.

Il a continué de manger et de manger
Pas de pause, pas de rentrer.
Enfin, le résultat est ici,
Le Champignon est désormais fini.

PS Naturellement, c’est de La Fontaine.

PPS J’ai écrit ce post en avril, mais j’ai oublié de le publier.

CommentsTags: ,

Cloud, Kluit, Clod?

Just a quick demonstration of the power of I dubbed my “personal cloud” experiment kluit: a Dutch word meaning both clod and the ball of earth around the roots of a tree. In other words, kluit is firmly grounded because you’ve got your own ground with you wherever you go. Be like Dracula. With a name in mind, I also wanted a matching logo. Following a quick search for leaves, root (or was it tree) and after a little initial play something like attraction, this is the quick and satisfying result.

A couple of floating leaves still connected with their roots. This arrangement symbolizes how creating your personal cloud keeps it grounded.

And of course the remix is free for all. Enjoy.

Comments (1)Tags: , , ,

Trois champignons

J’ai compris plus longtemps que, pour apprendre une langue (comme le français), il ne suffise pas d’exercices purement textuels. Il y a trois choses importantes, en ordre :

  1. Lire, lire, lire. Quantité, pas de qualité. Il est mieux de lire dix bandes dessinées pour des enfants que de ne lire aucun texte plus complexe.
  2. Écouter. Lire est bonne pour le vocabulaire, mais pour comprendre la langue on a besoin de langue orale.
  3. Créer. Écrire, parler… c’est plus difficile.

Inspiré par les bandes dessinées, je vous présente trois champignons. Le premier champignon est en belle forme. Le deuxième champignon a bu. Le troisième champignon a utilisé du LSD ou de quelque chose.

CommentsTags: , ,

Just a Star

Messing about a little in Inkscape with my wife’s Wacom CTH-680S tablet on Linux 4.1, after first trying it in Xournal. It seems to be functioning a fair bit better than a few kernel versions ago.

The tablet is really good. I’d recommend it.

Comments (1)Tags: ,

How To Fix SVG Height in Webkit/Blink

It’s pretty simple: also specify a height if you specify a width. To quote from the workaround in my stylesheet, which I added on account of my previous post:

figure svg {
	/*what follows is because Webkit/Blink is broken*/

I really shouldn’t have added that as I don’t cater to broken browsers anymore, but I have a bit of a soft spot for Opera—even if it’s never required any workarounds before. Presto is clearly superior, and so is Gecko. Chromium still doesn’t support SVG favicons. Gecko does. And just look at this table. Unfortunately Gecko doesn’t support SVG fonts, so it’s all looking pretty miserable without Presto.

Comments (1)Tags:

Feed Pick: My New Feed Icon

Recently, it came to my attention that most browsers no longer visually notify users of the presence of newsfeeds. To rectify this gross negligence, I decided to add a little feed link on the top right. It was immediately obvious that an icon would go nicely with it. More specifically of course, an SVG icon.

First I looked at the most obvious source. These are some icons Mozilla put out there to promote feeds: the very same icons that for unfathomable reasons they no longer display in their browser by default.
I made some quick modifications to integrate it better with my theme.
I tried a few different looks. I realized this was opposed to the (not legally binding) guidelines for this icon. Partially because of that and partially because I still wasn’t happy with the visuals, I moved on.
I took a quick peek on openclipart, or rather in my local copy that integrates with Inkscape, and found this beautiful icon. So I took it as a new starting point.
And finally, I ended up with this. I’m still debating whether it needs a stroke on the outside, but I’m happy with it for now. Because it’s SVG it scales beautifully, and it’s only 600 bytes after some quick manual optimization. A 24×24 PNG image is twice that. Finally, to be a good net citizen and all that, I uploaded it to openclipart as a remix of the original.

Comments (1)Tags:

Javascript Associative Array of SVG Color Keywords

I thought I needed an associative array of SVG color keywords. I couldn’t find one, so I created this. Enjoy.

// see
var SVGColors = new Object();
SVGColors['aliceblue'] = [240,248,255];
SVGColors['antiquewhite'] = [250,235,215];
SVGColors['aqua'] = [0,255,255];
SVGColors['aquamarine'] = [127,255,212];
SVGColors['azure'] = [240,255,255];
SVGColors['beige'] = [245,245,220];
SVGColors['bisque'] = [255,228,196];
SVGColors['black'] = [0,0,0];
SVGColors['blanchedalmond'] = [255,235,205];
SVGColors['blue'] = [0,0,255];
SVGColors['blueviolet'] = [138,43,226];
SVGColors['brown'] = [165,42,42];
SVGColors['burlywood'] = [222,184,135];
SVGColors['cadetblue'] = [95,158,160];
SVGColors['chartreuse'] = [127,255,0];
SVGColors['chocolate'] = [210,105,30];
SVGColors['coral'] = [255,127,80];
SVGColors['cornflowerblue'] = [100,149,237];
SVGColors['cornsilk'] = [255,248,220];
SVGColors['crimson'] = [220,20,60];
SVGColors['cyan'] = [0,255,255];
SVGColors['darkblue'] = [0,0,139];
SVGColors['darkcyan'] = [0,139,139];
SVGColors['darkgoldenrod'] = [184,134,11];
SVGColors['darkgray'] = [169,169,169];
SVGColors['darkgreen'] = [0,100,0];
SVGColors['darkgrey'] = [169,169,169];
SVGColors['darkkhaki'] = [189,183,107];
SVGColors['darkmagenta'] = [139,0,139];
SVGColors['darkolivegreen'] = [85,107,47];
SVGColors['darkorange'] = [255,140,0];
SVGColors['darkorchid'] = [153,50,204];
SVGColors['darkred'] = [139,0,0];
SVGColors['darksalmon'] = [233,150,122];
SVGColors['darkseagreen'] = [143,188,143];
SVGColors['darkslateblue'] = [72,61,139];
SVGColors['darkslategray'] = [47,79,79];
SVGColors['darkslategrey'] = [47,79,79];
SVGColors['darkturquoise'] = [0,206,209];
SVGColors['darkviolet'] = [148,0,211];
SVGColors['deeppink'] = [255,20,147];
SVGColors['deepskyblue'] = [0,191,255];
SVGColors['dimgray'] = [105,105,105];
SVGColors['dimgrey'] = [105,105,105];
SVGColors['dodgerblue'] = [30,144,255];
SVGColors['firebrick'] = [178,34,34];
SVGColors['floralwhite'] = [255,250,240];
SVGColors['forestgreen'] = [34,139,34];
SVGColors['fuchsia'] = [255,0,255];
SVGColors['gainsboro'] = [220,220,220];
SVGColors['ghostwhite'] = [248,248,255];
SVGColors['gold'] = [255,215,0];
SVGColors['goldenrod'] = [218,165,32];
SVGColors['gray'] = [128,128,128];
SVGColors['green'] = [0,128,0];
SVGColors['greenyellow'] = [173,255,47];
SVGColors['grey'] = [128,128,128];
SVGColors['honeydew'] = [240,255,240];
SVGColors['hotpink'] = [255,105,180];
SVGColors['indianred'] = [205,92,92];
SVGColors['indigo'] = [75,0,130];
SVGColors['ivory'] = [255,255,240];
SVGColors['khaki'] = [240,230,140];
SVGColors['lavender'] = [230,230,250];
SVGColors['lavenderblush'] = [255,240,245];
SVGColors['lawngreen'] = [124,252,0];
SVGColors['lemonchiffon'] = [255,250,205];
SVGColors['lightblue'] = [173,216,230];
SVGColors['lightcoral'] = [240,128,128];
SVGColors['lightcyan'] = [224,255,255];
SVGColors['lightgoldenrodyellow'] = [250,250,210];
SVGColors['lightgray'] = [211,211,211];
SVGColors['lightgreen'] = [144,238,144];
SVGColors['lightgrey'] = [211,211,211];
SVGColors['lightpink'] = [255,182,193];
SVGColors['lightsalmon'] = [255,160,122];
SVGColors['lightseagreen'] = [32,178,170];
SVGColors['lightskyblue'] = [135,206,250];
SVGColors['lightslategray'] = [119,136,153];
SVGColors['lightslategrey'] = [119,136,153];
SVGColors['lightsteelblue'] = [176,196,222];
SVGColors['lightyellow'] = [255,255,224];
SVGColors['lime'] = [0,255,0];
SVGColors['limegreen'] = [50,205,50];
SVGColors['linen'] = [250,240,230];
SVGColors['magenta'] = [255,0,255];
SVGColors['maroon'] = [128,0,0];
SVGColors['mediumaquamarine'] = [102,205,170];
SVGColors['mediumblue'] = [0,0,205];
SVGColors['mediumorchid'] = [186,85,211];
SVGColors['mediumpurple'] = [147,112,219];
SVGColors['mediumseagreen'] = [60,179,113];
SVGColors['mediumslateblue'] = [123,104,238];
SVGColors['mediumspringgreen'] = [0,250,154];
SVGColors['mediumturquoise'] = [72,209,204];
SVGColors['mediumvioletred'] = [199,21,133];
SVGColors['midnightblue'] = [25,25,112];
SVGColors['mintcream'] = [245,255,250];
SVGColors['mistyrose'] = [255,228,225];
SVGColors['moccasin'] = [255,228,181];
SVGColors['navajowhite'] = [255,222,173];
SVGColors['navy'] = [0,0,128];
SVGColors['oldlace'] = [253,245,230];
SVGColors['olive'] = [128,128,0];
SVGColors['olivedrab'] = [107,142,35];
SVGColors['orange'] = [255,165,0];
SVGColors['orangered'] = [255,69,0];
SVGColors['orchid'] = [218,112,214];
SVGColors['palegoldenrod'] = [238,232,170];
SVGColors['palegreen'] = [152,251,152];
SVGColors['paleturquoise'] = [175,238,238];
SVGColors['palevioletred'] = [219,112,147];
SVGColors['papayawhip'] = [255,239,213];
SVGColors['peachpuff'] = [255,218,185];
SVGColors['peru'] = [205,133,63];
SVGColors['pink'] = [255,192,203];
SVGColors['plum'] = [221,160,221];
SVGColors['powderblue'] = [176,224,230];
SVGColors['purple'] = [128,0,128];
SVGColors['red'] = [255,0,0];
SVGColors['rosybrown'] = [188,143,143];
SVGColors['royalblue'] = [65,105,225];
SVGColors['saddlebrown'] = [139,69,19];
SVGColors['salmon'] = [250,128,114];
SVGColors['sandybrown'] = [244,164,96];
SVGColors['seagreen'] = [46,139,87];
SVGColors['seashell'] = [255,245,238];
SVGColors['sienna'] = [160,82,45];
SVGColors['silver'] = [192,192,192];
SVGColors['skyblue'] = [135,206,235];
SVGColors['slateblue'] = [106,90,205];
SVGColors['slategray'] = [112,128,144];
SVGColors['slategrey'] = [112,128,144];
SVGColors['snow'] = [255,250,250];
SVGColors['springgreen'] = [0,255,127];
SVGColors['steelblue'] = [70,130,180];
SVGColors['tan'] = [210,180,140];
SVGColors['teal'] = [0,128,128];
SVGColors['thistle'] = [216,191,216];
SVGColors['tomato'] = [255,99,71];
SVGColors['turquoise'] = [64,224,208];
SVGColors['violet'] = [238,130,238];
SVGColors['wheat'] = [245,222,179];
SVGColors['white'] = [255,255,255];
SVGColors['whitesmoke'] = [245,245,245];
SVGColors['yellow'] = [255,255,0];
SVGColors['yellowgreen'] = [154,205,50];


Pancake Visions

Some of you may be aware that I often imagine things in random shapes that other people have trouble envisioning, sometimes even after I draw them out. On June 3rd my wife and I baked tiny pancakes, and here’s what I saw in two of them.

An evil cat in a pancake.
The first pancake that managed to attract my attention was an evil cat.
A face in a pancake.
This pancake also happened to be on the plate while taking a picture of the evil cat one, so I figured I’d demonstrate that I do indeed see something in just about anything.

Note, these are animated SVG images. At the time of writing they only render correctly in Opera and Webkit browsers, whereas Gecko displays a static image. Internet Explorer is served with fallback PNGs.

The SVGs now also render correctly in Firefox 4.

Replaced OBJECT elements with PICTURE elements.


No Taco Bell Allowed in the EU

I know that Taco Bell’s lack of presence in Europe is simply a business decision, and that Taco Bell is actually expanding into Europe. Nevertheless, while I was thinking about the EU and Taco Bell simultaneously, I figured a “Taco Bell forbidden” rule, demonstrated by signs at customs, was as good an explanation as any. Besides, it ought to look funny, right? So I grabbed the EU flag, the Taco Bell logo and a random forbidden sign from Wikipedia, and I combined them. By the way, Wikipedia is a fantastic repository for SVG images of road signs, flags and the like.

No Taco Bell Allowed in the EU Spoof Logo

No Taco Bell Allowed in the EU Spoof Logo Wavy


Older Entries »