Archive for the 'images' Category

Installing a bunch of PNG tools on the Mac

Friday, June 12th, 2009

This is one of those note-to-self type of posts. Just went through the exercise of installing a number of PNG tools on the Mac and here are some notes. The instructions below should probably work on any unix box.

AdvDef, AdvPng, ...

There is a number of Adv* tools (advdef, advpng, advmng, advzip) packed together as AdvComp. Installation difficulty: fairly straighforward.

Download:

$ curl http://softlayer.dl.sourceforge.net/sourceforge/advancemame/advancecomp-1.15.tar.gz \
         > advcomp.tar.gz

Uncompress:

$ tar -xzvf advcomp.tar.gz

Compile and install:

$ cd advancecomp-1.15/
$ sudo ./configure
$ sudo make install

Test:

$ advdef
advancecomp v1.15 by Andrea Mazzoleni
Usage: advpng [options] [FILES...]

Modes:
  -z, --recompress      Recompress the specified files
Options:
  -0, --shrink-store    Don't compress
  -1, --shrink-fast     Compress fast
  -2, --shrink-normal   Compress normal
  -3, --shrink-extra    Compress extra
  -4, --shrink-insane   Compress extreme
  -f, --force           Force the new file also if it's bigger
  -q, --quiet           Don't print on the console
  -h, --help            Help of the program
  -V, --version         Version of the program

Excellent! Next.

OptiPng

OptiPng is another easy install.

Download and decompress:

$ curl http://superb-west.dl.sourceforge.net/sourceforge/optipng/optipng-0.6.3.tar.gz \
            > optipng.tar.gz 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1035k  100 1035k    0     0   171k      0  0:00:06  0:00:06 --:--:--  196k
$ tar -xzvf optipng.tar.gz

Compile and install:

$ cd optipng-0.6.3
$ sudo ./configure
$ sudo make install

Test:

$ optipng 
OptiPNG 0.6.3: Advanced PNG optimizer.
Copyright (C) 2001-2009 Cosmin Truta.

Synopsis:
    optipng [options] files ...
Files:
    Image files of type: PNG, BMP, GIF, PNM or TIFF
Basic options:
    -?, -h, -help	show the extended help
    -o <level>		optimization level (0-7)		default 2
    -v			verbose mode / show copyright and version info
Examples:
    optipng file.png			(default speed)
    optipng -o5 file.png		(moderately slow)
    optipng -o7 file.png		(very slow)
Type "optipng -h" for extended help.

Beauty! Next - pngout.

PNGout

PNGOut's source is not distributed openly. But there are binaries for a number of platforms here.

Download:

$ curl http://static.jonof.id.au/dl/kenutils/pngout-20070430-darwin.tar.gz \
         > pngout.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 97597  100 97597    0     0  46704      0  0:00:02  0:00:02 --:--:-- 56739
$ tar -xzvf pngout.tar.gz

This way you end up with a binary named pngout-darwin. Rename and move somewhere where executables live:

$ sudo mv pngout-darwin /usr/bin/pngout 

Test:

$ pngout
PNGOUT [In:{PNG,JPG,GIF,TGA,PCX,BMP}] (Out:PNG) (options...)        Apr 30 2007
by Ken Silverman (http://advsys.net/ken)
Mac port assistance by Jonathon Fowler (http://jonof.edgenetwork.org/pngout)
PNGOUT optimizes PNG size losslessly using my own deflate algorithm (not Zlib)
With the right options, it can often beat other programs by 5-10%. Options:
   -c# PNG output color type: 0=Gray, 2=RGB, 3=Pal, 4=Gray+Alpha, 6=RGB+Alpha
   -f# PNG output filter...

PngRewrite

PNGRewrite was a little trickier, until I realized I need to install libpng first.

Download and install libpng:

$ curl ftp://ftp.simplesystems.org/pub/libpng/png/src/libpng-1.2.37.tar.gz \
         > libpng.tar.gz
$ tar -xzvf libpng.tar.gz
$ cd libpng-1.2.37/
$ sudo ./configure
$ sudo make install

Download, unzip pngrewrite:

$ curl http://entropymine.com/jason/pngrewrite/pngrewrite-1.3.0.zip \
         > pngrewrite.zip
$ unzip pngrewrite.zip

Compile pngrewrite (the make file didn't work for me) and copy the binary where executables are comfortable.

$ gcc -lpng pngrewrite.c -o pngrewrite
$ sudo cp pngrewrite /usr/bin/

Test:

$ pngrewrite
pngrewrite v1.3.0: PNG image palette optimizer
Usage: pngrewrite infile.png outfile.png

That's all, folks

Installing PNGCrush? Blogged before.

And if anyone has an idea how to get deflopt installed, please comment.

 

MHTML – when you need data: URIs in IE7 and under

Friday, April 10th, 2009

UPDATE: It's very important to have a closing separator in the MHTML document, otherwise there are known issues in IE7 on Vista or Windows 7. The details are here.

In the previous post I described what data: URIs are and how they are useful to reduce the number of HTTP requests. Now, the problem with data: URIs is that they are not supported by IE < 8. But, thanks to this article (in Russian), here's a way to work around that limitation: using MHTML for IE7 and under.

MHTML-a-what?

MHTML is MIME HTML, or if you insist on me spelling it out completely is like "Multipurpose Internet Mail Extensions HyperText Markup Language". In short it's HTML but like email with attachments. In one "multipart" email you can have several... hm, things - HTML version of the email, text-only version, attachment, another attachment...

MHTML is the same but for HTML. In one file you put a bunch of stuff (e.g. image files) and you save on the precious HTTP requests.

MHTML example

Let's check out an example. The HTML is actually not important, it's the CSS where we'll stick all our inline images. Once with data: sheme for all normal browsers and once with the mhtml: scheme for IE before 8.

The overall "template" would look like so:

/*
Content-Type: multipart/related; boundary="_ANY_SEPARATOR"
 
--_ANY_SEPARATOR
Content-Location:somestring
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAA[...snip...]SuQmCC
--_ANY_SEPARATOR--
*/
 
#myid {
  /* normal browsers */
  background-image: url("data:image/png;base64,iVBORw0[...snip...]");
  /* IE < 8 targeted with the star hack */
  *background-image: url(mhtml:http://phpied.com/mhtml.css!somestring); 
}

The multipart stuff goes into a CSS comment. You then use data: URI scheme for all normal browsers. Then you use the star hack to target IE browsers before 8. For these browsers you give the URL of the CSS, exclamation and then a string that uniquely identifies the desired "part" in the multipart comment. Simple, eh? Didn't think so, but hey, it's not exactly rocket science.

Demo example with more parts

Check out a demo page that has two "parts" in the multipart MHTML comment.

The HTML is noting special and the CSS goes like:

/*
Content-Type: multipart/related; boundary="_ANY_STRING_WILL_DO_AS_A_SEPARATOR"
 
--_ANY_STRING_WILL_DO_AS_A_SEPARATOR
Content-Location:locoloco
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC
--_ANY_STRING_WILL_DO_AS_A_SEPARATOR
Content-Location:polloloco
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUhEUgAAABkAAAAUBAMAAACKWYuOAAAAMFBMVEX///92dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnYvD4PNAAAAD3RSTlMAACTkfhvbh3iEewTtxBIFliR3AAAAUklEQVQY02NgIBMwijgKCgrAef5fkHnz/y9E4kn+/4XEE6z/34jEE///A4knev7zAwQv7L8RQk40/7MiggeUQpjJff+zIpINykbIbhFSROIRDQAWUhW2oXLWAQAAAABJRU5ErkJggg==
--_ANY_STRING_WILL_DO_AS_A_SEPARATOR--
*/
 
#test1 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC"); /* normal */
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml.css!locoloco); /* IE < 8 */
}
 
 
#test2 {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAUBAMAAACKWYuOAAAAMFBMVEX///92dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnYvD4PNAAAAD3RSTlMAACTkfhvbh3iEewTtxBIFliR3AAAAUklEQVQY02NgIBMwijgKCgrAef5fkHnz/y9E4kn+/4XEE6z/34jEE///A4knev7zAwQv7L8RQk40/7MiggeUQpjJff+zIpINykbIbhFSROIRDQAWUhW2oXLWAQAAAABJRU5ErkJggg=="); /* normal */
  *background-image: url(mhtml:http://phpied.com/files/mhtml/mhtml.css!polloloco); /* IE < 8 */
}
 
div {
  width: 100px;
  height: 100px;
  font: bold 24px Arial;
}

Drawback

In addition to the drawbacks of data: URIs (bigger CSS, small CSS change invalidates your cached inline images), this method has the obvious drawback that the inline images are repeated twice. But sometimes... a person's gotta do what a person's gotta do ;)

 

data:urls – what are they and how to use them

Friday, April 10th, 2009

If you follow this blog you already know the infamous website performance rule #1 - reduce the number of HTTP requests. Actually, to celebrate Earth Day and to jump the "go-green" wagon/jargon, my favourite performance mantra as of late is "Reduce, Reuse, Recycle" (the Recycle part is a wee fuzzy but, oh well)

So to reduce the number of requests for JavaScript files, you combine all .js into one monolithic file.

Same for .css

For images - use CSS sprites to create one image files that contains all your little icons.

Yet another way to minimize the number of HTTP requests is to use data URLs (proper name is data URI scheme).

"data... uri? urgh-i? An example, please!"

Say you have this minuscule image:

which you include like:

<img src="http://phpied.com/images/check.png" />

This way you're actually using the http URI scheme.

Same thing using data URI will look like:

And the code for it:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC" />

This way the image in inlined in the HTML and there is no extra HTTP request to retrieve it.

data URI syntax

Let's take a look again at this img tag and its slightly disturbing syntax:

<img src="data:image/png;base64,iVBOR..." />

You have:

  • data - name of the scheme
  • image/png - content type
  • base64 - the type of encoding used to encode the data
  • iVBOR... - the encoded data
  • some , and ; and : sprinked for good measure

How do I base64-encode?

While there are some online tools that will let you upload an image or point to a URL, you can simply do it from PHP and the command line.

Say you have the filename check.png. Then go:

$ php -r "echo base64_encode(file_get_contents('check.png'));"

This will spit out encoded content for copy-pasting pleasure. Naturally you can also do this from your application code, if needed.

Using data URLs in CSS

If you want the same image as a repeating CSS background, you can use it like this:

<div id="data-uri-test"></div>
<style type="text/css">
#data-uri-test {
  width: 20px;
  height: 20px;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC");
}
</style>

Where the gist of it really boils down to the same:

background-image: url("data:image/png;base64,iVBOR...");

Lights, camera... IE!

Everybody's favourite part of every technique... IE support. The thing is IE8 supports data URIs. Earlier IEs do not. But, there is a solution involving MHTML, which I'll describe in a follow-up post, since this one turn out kinda longish.

For the impatient and Russian-speaking among you - click here.

 

Picassa’s progressive photo rendering

Thursday, March 5th, 2009

If you've followed the series of image optimization posts on the YUIblog, you've probably seen the one about progressive JPEGs. In short, if a photo is over 10K it has a high probability of being smaller in file size when you use progressive JPEG.

How do you turn a normal, baseline JPEG into a progressive one? Well, most image software has this capability. On the command line you can use JPEGTRAN's -progressive flag.

Progressive JPEGs don't render progressively in IE but that's not a big deal, they still render.

Mi casa, Pi-cassa

That's all nice and good, but as I was browsing Picassa, I noticed some serious progressive photo rendering going on. Imagine my surprise when I found the photos on picassa seemed to be rendering progressively even in IE. And upon inspecting one photo - it wasn't even using progressive encoding!

small thumb first, bigger image later

Picassa uses a clever trick where they quickly load a small thumbnail of the photo you want to see, and then start loading the real big image. This way you get something that gives you an idea of the image and if you don't like it, you can click next and move on without loading for the whole image. And what gives the feeling of progressive loading is the fact that the thumbnail is stretched to take the whole width/height of the real image.

See for yourself

This page initially shows a small 128x96 image:
small thumb

But this image is stretched to 640x480 so it looks like:
small thumb stretched

The actual final image is
big image

This is pretty trivial to achieve, something like (not tested, just typing away):

var i = new Image();
i.onload = function() {
    document.getElementById('small').src = i.src;
}
i.src = "big.jpg";

So once the big image arrives, it replaces the small one and the visual effect is like progressive rendering - as if the image increases quality gradually.

 

Hide broken images

Monday, February 16th, 2009

I know, you don't have broken images on your site, it's unprofessional and ugly. But sometimes you may be loading images that you don't control and you never know what's going on on the other server you're expecting to serve, but it may not feel up to the task.

One nice and simple strategy to deal with this uncertainty is to hide the images that fail to load. Browsers sent an "error" event when the worst happens and an image fails for whatever reason. Subscribe to this event using your favorite event-listener-attaching approach or library and hide the image.

Or with some ugly old-school inline event handler:

<img 
  src="broken.png" 
  onerror="this.style.display='none'"
/>

Simple, eh?

 

Installing JPEGTRAN on a Mac or Unix/Linux

Friday, January 16th, 2009

JPEGtran is cool because it lets you optimize JPEG images losslessly by:

  1. Stripping meta data (meta is sometimes bulky and useless for web display)
  2. Optimizing Huffman tables or
  3. Convert a JPEG to progressive encoding

From my experience 1 is more important than 2 or 3 and 3 gives better results than 2 for images over 10K

Installation

I never had to install jpegtran before because all unix/linux machines I've touched already have it. And on windows you just copy a binary somewhere in your path.

Well, I got this MacBook now and it doesn't have jpegtran so had to figure it out myself. Here's how you can do it, worked for me on Mac OS should work on any unix/linux too.

BTW, jpegtran is part of a package of few tools known as libjpeg, so you'll be installing a few programs not only jpegtran.

  1. Get the source code from here. It's the file called jpegsrc.v6b.tar.gz. Using cURL you can download like:
    curl http://www.ijg.org/files/jpegsrc.v6b.tar.gz > /tmp/libjpeg.tar.gz
  2. Uncompres the package, e.g. tar -xzvf /tmp/libjpeg.tar.gz
  3. go to the directory that contains the uncompressed code, e.g. cd /tmp/jpeg-6b
  4. ./configure
  5. sudo make install

Done.

You can test your shiny new set of tools like this and get some help information about the various options:

> jpegtran -h
> cjpeg -h
> djpeg -h
> rdjpgcom -h
> wrjpgcom -h

You also test by optimizing my book cover from Amazon like:

curl http://ecx.images-amazon.com/images/I/41ckBp3bBUL._SL500_AA240_.jpg > oojs.jpg
jpegtran -copy none -progressive oojs.jpg > oojs-opt.jpg

This gives you 10% smaller file with not a pixel of quality loss. Not bad, eh, for a minute of work, or less.

 

Image optimization – in Chinese

Thursday, January 8th, 2009

Thanks to Joseph Jiang who translated in Chinese parts of my image optimization articles from the YUI blog

If you read Chinese, visit http://josephj.com/entry.php?id=209.

 

Paint.NET is cool…

Tuesday, December 23rd, 2008

... but doesn't write PNG8 with alpha transparency, unfortunately.

This comment on the YUI blog got me all excited by the possibility of having another designers tool other than Fireworks that creates PNG8 (palette PNGs) with alpha transparency.

Overall Paint.NET is a very simple and friendly program (as a non-designer I'm often intimidated by Photoshop's plethora of features) and I can see how I can use it for my future (rare, I hope) design needs :) It's pretty impressive, free and from what I read, it has a dedicated community, plus a number of plugins (hmm, a smush.it plugin would be nice... one day).

I tried saving a PNG8 with alpha but unfortunately this feature is not (yet!) supported. I created an image with a sold red square and blue gradient that fades to 100% transparency. When saving as 8-bit (in order to create a palette PNG) the semi transparent pixels are all gone. It treats palette PNGs like GIFs (like Photoshop does).

Default (produces truecolor PNG):
png 32

With 8 bit option selected:
png8

 

Installing ExifTool on Dreamhost

Tuesday, December 23rd, 2008

ExifTool looks like a very promising tool to fiddle with all sorts of JPEG metadata (needed for smush.it) but first I had to make sure I can install it on Dreamhost. Although installation didn't go as described on the exiftool site (since I don't have sudo access on Dreamhost), it's still installable and it's actually pretty easy.

  1. ssh to your DH box and go to the directory that will contain the tool, in my case it's my home directory
  2. Download the latest version of the code, e.g.
    wget http://www.sno.phy.queensu.ca/~phil/exiftool/Image-ExifTool-7.59.tar.gz
  3. Uncompress the code archive
    tar -xf Image-ExifTool-7.59.tar.gz
  4. Delete the original archive
    rm Image-ExifTool-7.59.tar.gz
  5. Rename the newly created Image-ExifTool-7.59 to something shorter to type, e.g. "et"
    mv Image-ExifTool-7.59 et
  6. All done! You can now run it from any directory and show help info like
    ~/et/exiftool - h

You can also test the new installation with some of the images found in the exiftool test directory, like so:

$ ~/et/exiftool ~/et/t/images/IPTC-XMP.jpg

ExifTool Version Number         : 7.59
File Name                       : IPTC-XMP.jpg
File Size                       : 20 kB
File Modification Date/Time     : 2005:12:31 13:05:50-08:00
File Type                       : JPEG
MIME Type                       : image/jpeg
JFIF Version                    : 1.02
Exif Byte Order                 : Little-endian (Intel, II)
Image Description               : A witty caption
Make                            : FUJIFILM
Camera Model Name               : FinePix2400Zoom
Orientation                     : Horizontal (normal)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : Adobe Photoshop 7.0
Modify Date                     : 2004:02:26 09:36:46
Artist                          : Phil Harvey
Y Cb Cr Positioning             : Co-sited
Copyright                       : Copyright 2004 Phil Harvey
F Number                        : 3.5
Exposure Program                : Program AE
ISO                             : 100
Exif Version                    : 0210
Date/Time Original              : 2001:05:19 18:36:41
Create Date                     : 2001:05:19 18:36:41
Components Configuration        : YCbCr
Compressed Bits Per Pixel       : 1.6
...
... [snip]
...
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Aperture                        : 3.5
Image Size                      : 100x80
Shutter Speed                   : 1/64
Focal Length                    : 6.0 mm
Light Value                     : 9.6
 

PNG optimization tools

Monday, December 22nd, 2008

I'm currently experimenting with different tools for optimizing PNG images to figure out strengths/weaknesses of each. Only considering free, ideally open-source, tools that can be run from the command line. For smush.it I just picked pngcrush for no particluar reason and I was thinking that once I have the optimization tool up and running and get the point across that images can be optimized with no human intervention as part of the normal "push" process... then the actual behind-the-scenes png optimizer can be tweaked without affecting the overall workflow.

So, I'm considering these PNG optimizers:

Can you think of any other PNG optimizers out there? Please comment.

Well, I just came back from a cruise with no internet access ($1/minute is unacceptable!) so I used my laptop's idle time to run some of the tools on a bunch of files (well over 10 000 PNG files, smartly collected right before the cruise ;) ).

So far some (very preliminary!) results from running the tools.

  1. pngcrush's -brute option is slower, as expected and usually doesn't help much
  2. pngcrush with -rem alla (strip all chunks only keeping the transparency chunk) doesn't get much better than -rem none (keep all chunks, even useless ones)
  3. pngout seems to give the best results, but it's probably the slowest
  4. pngrewrite is only for pallete PNGs (PNG8), it also seems to convert truecolor PNGs with less than 256 colors to PNG8

Again, these are very preliminary and again, please let me know if you know of any other PNG optimizers.

And keep an eye on the YUIblog's img opt series for more detailed report of the results.

 

image diff

Saturday, November 15th, 2008

Was having fun today with idiff.php - a PHP shell script to tell you if two images are visually different by comparing them pixel by pixel. If there's a difference, the script creates a third image - black background with the different pixels in green. Only after writing the script I found that there's an imagemagick command compare that does the same :)

the script

The idea is that two GD images are created from the input files, also a third GD image with black background to store the diff. Loop through all the pixels and compare them one by one. If one is different, write a green pixel at the same location of the diff image.

<?php
/**
 * Shell script to tell if two images are identical.
 * If not, a third image is written - black background with the different pixels painted green
 * Code partially inspired by and borrowed from http://pear.php.net/Image_Text test cases
 */
 
// check if there's enough input
if (empty($argv[1]) || empty($argv[2])) {
    echo 'gimme at least two image filenames, please.', "\n";
    echo 'e.g. "php idiff.php img1.png img2.png"';
    echo "\n", 'third filename is the image diff, optional, default is "diffy.png"';
    exit(1);
}
 
// create images
$i1 = @imagecreatefromstring(file_get_contents($argv[1]));
$i2 = @imagecreatefromstring(file_get_contents($argv[2]));
 
// check if we were given garbage
if (!$i1) {
    echo $argv[1] . ' is not a valid image';
    exit(1);
}
if (!$i2) {
    echo $argv[2] . ' is not a valid image';
    exit(1);
}
 
// dimensions of the first image
$sx1 = imagesx($i1);
$sy1 = imagesy($i1);
 
// compare dimensions
if ($sx1 !== imagesx($i2) || $sy1 !== imagesy($i2)) {
    echo "The images are not even the same size";
    exit(1);
}
 
// create a diff image
$diffi = imagecreatetruecolor($sx1, $sy1);
$green = imagecolorallocate($diffi, 0, 255, 0);
imagefill($diffi, 0, 0, imagecolorallocate($diffi, 0, 0, 0));
 
// increment this counter when encountering a pixel diff
$different_pixels = 0;
 
// loop x and y
for ($x = 0; $x < $sx1; $x++) {
    for ($y = 0; $y < $sy1; $y++) {
 
        $rgb1 = imagecolorat($i1, $x, $y);
        $pix1 = imagecolorsforindex($i1, $rgb1);
 
        $rgb2 = imagecolorat($i2, $x, $y);
        $pix2 = imagecolorsforindex($i2, $rgb2);
 
        if ($pix1 !== $pix2) { // different pixel
            // increment and paint in the diff image
            $different_pixels++;
            imagesetpixel($diffi, $x, $y, $green);
        }
 
    }
}
 
 
if (!$different_pixels) {
    echo "Image is the same";
    exit(0);
} else {
    if (empty($argv[3])) {
        $argv[3] = 'diffy.png'; // default result filename
    }
    imagepng($diffi, $argv[3]);
    $total = $sx1 * $sy1;
    echo "$different_pixels/$total different pixels, or ", number_format(100 * $different_pixels / $total, 2), '%';
    exit(1);
}
?>

usage

Type in the command line something like:

php idiff.php img1.png img2.png result.png

This command will compare img1.png with img2.png. If they are not the same dimensions, will exit with error code. If their pixels exactly the same will exit with success code 0 and print a success message. If the images are not the same, will write the file result.png.

If you omit result.png, the generated file will be called diffy.png

example

img1.png:

img2.png

running the command:

>php idiff.php img1.png img2.png
70/24600 different pixels, or 0.28%

the result diffy.png:

imagemagick

yep, so after I did this I found that imagemagick has a command called compare that does the same, only with more features, like the -fuzz option for example that allows you to specify how different should the pixels be, in order to consider them important.

the command:

>compare img1.png img2.png diffy-im.png

the diffy-im.png result

As you can see the different pixels are red and the actual image is a very pale background. Nice.

 

php|works Atlanta

Thursday, November 13th, 2008

Thanks to everyone who attended my image optimization talk at the php|works + PyWorks conference in Atlanta. And thanks for all the questions! I love questions, feels more natural - just geeks talking to geeks - as opposed to one guy sitting on a podium and talking.

And the slides:

 

smush.it update

Sunday, October 26th, 2008

What's new in smush.it?

  • The old domain smushit.com now redirects to the new one smush.it, which is what we originally intended but the domain registration took a while and we quickly got smushit.com just in time for the Ajax Experience announcement of the tool
  • There's a bookmarklet version of the Firefox extension so you can now run in IE or any other browser capable of bookmarking
  • There's an FAQ
  • The API, while not really documented, is at least described in the FAQ
  • So is the privacy policy. People said we should have privacy policy, we're not lawyers, so we couldn't write something that sounds important. The policy is basically: 1) we won't use the images you upload in any way other than gathering aggregated optimization statistics (how much we save on average, how many files get smushed daily...). 2) Images will be deleted from the server, once we get around to writing some sort of stats script, so download the optimized zip asap, don't rely on it being there for long. 3) The images you upload are available to anyone who can guess the random URL smush.it creates, so don't upload images you want to keep private
  • Flash 10 support, updated to YUI 2.6's flash uploader and sweated a lot to make the uploader work in the tightened flash 10 security, but that's a whole other blog post
  • Smu contest! What's a Smu? We want to know too. Send us you idea of a Smu to smu at smush.it
 

Smush.it presentations

Sunday, October 5th, 2008

Smush.it is getting more and more buzz all over the internets. Now there's even a song about it! Me and Nicole are pretty busy answering email, but a little slow to document the thing, I though I should at least shed some light on how the tool works by using some of the presentations.

What the tool does can be summarized in these steps:

  • Turn GIFs into PNG8. Results are reported only if there's a saving, the file name then becomes source.gif.png. Smush.it uses imagemagick to do the conversion and then pngcrush to crush the pngs
  • Crush PNGs using pngcrush
  • Strip JPEG metadata and make them progressive, using jpegtran
  • GIF animations: use gifsicle to remove pixels that don't change from one frame to another

This has been documented here on developer.yahoo.com together with the command line tools and options.

So all the tool does is run the appropriate command for each file type. Easy as that ;) All the tools mentioned are free open-source and available on all operating systems, including Windows.

Here are some presentations on slideshare that might explain things a little more:

  1. High-perf web sites - PHP Quebec Montreal, March 2008
  2. 7 mistakes in image optimization - O'Reilly's Velocity, SFO, June 2008
  3. Ajax Experience, Boston, earlier this week. Draft 1, Draft 2. The final and shortest version is below. It doesn't say much but the talk was just 5 minutes and included a demo. It's weird how little you can say in 5 minutes, I mean just "hello, my name, ... welcome to blah, blah..." is 20 seconds
Images - 7 mistakes
View SlideShare presentation or Upload your own. (tags: images optimization)

Happy smushing!

"Smush it! Smush it real good..." - hothardware.com ;)

 

smush.it is a smash hit

Thursday, October 2nd, 2008

Since me and Nicole announced smush.it yesterday at Ajax Experience and thanks to Christian Heilmann posting it on Ajaxian and Yahoo Developer Network, this thing seems to have exploded! It's all over the blogosphere, twitter-sphere and every other sphere.

BTW, Chris never seizes to amaze - he posted the video on Ajaxian at 11:01 am and our talk was from 10:30 to 10:35 am :)

Last night, about 12 hours after the announcement, I checked the directory that stores the results and it had over 10 000 entries. There's one entry for every smush.it run, which means for example every page you smush using the FF extension. If you have 5 images on a page on average, this means over 50 000K smushed images in a day, nice!

We've received great feadback and great suggestions, keep'em coming. People are already making smush.it part of their dev/build process. People are already seeing response time performance improvement.

I'm really looking forward to releasing the official API, open source the code, the command-line version and all the fun stuff. We were just too busy trying to come up with something presentable for the Ajax conference.

The URL again: http://smushit.com

So, what's a smu? ;)

 

php|works and pyWorks

Wednesday, September 10th, 2008

I'll be speaking at the php|works and pyWorks conferences in Atlanta, Georgia in November, they'll be held together and there is a central track that has topics of interest to both phpiers and pythonistas, this is where I come in.

The conference(s) schedule is here and this is me: "Image optimization for the web"

 

Installing pngcrush at dreamhost

Thursday, September 4th, 2008

pngcrush is an excellent optimizer for png images, simple and fast, highly recommended. Basically any time before you post a PNG on the web, you should run it through PNGCrush. It's a command line utility, there's a quick way to integrate pngcrush in windows explorer. (note to self: I actually wrote a wordpress plugin to do crush uploaded images, gotta find and post it)

I'm working (together with Nicole) on an online image optimizer, using the free command line tools I talked about at PHP Quebec, Montreal, O'Reilly's Velocity and the Yahoo! Performance pages. The tool will be free, of course, and hosted on Dreamhost. OK, finally getting to the point, am I?

Here's what I did to install pngcrush on Dreamhost, hope it helps anyone out there who's sweating with the same task.

  1. ssh to your dreamhost box, you need to give yourself ssh privileges from the dreamhost control panel
  2. mkdir tmp - make a temp directory
  3. cd tmp - enter the temp directory
  4. wget http://voxel.dl.sourceforge.net/sourceforge/pmt/pngcrush-1.6.10.tar.gz - download latest version of the pngcrush source code, this is the sourceforge mirror, closest to me, yours might be different and the pngcrush version might be newer at the time you install it, you can check here
  5. tar zxvf pngcrush-1.6.10.tar.gz - uncompress the code. This creates a new directory, enter it like so: cd pngcrush-1.6.10
  6. now all you need to do is run make, but didn't work for me, so I edited the Makefile file first, like so vi Makefile. In the vi editor, press i to enter INSERT mode and once you're done with the changes, press ESC, then type :wq (meaining write, quit). Here I removed the version information from the gcc compiler, so the two lines I edited became
    CC = gcc
    LD = gcc
  7. make - this compiles the pngcrush code and creates a binary called pngcrush
  8. cp pngcrush /usr/bin/ - copy the new binary to where binaries usually live
  9. All set, try if everything is ok pngcrush, this should display the pngcrush's help information

Crush'em PNGs!

Like so:
pngcrush image.png -rem alla -reduce -brute result.png

Need hosting?

[advertisement]Dreamhost is a really good host, use this code MAXDISCO for $50 off [/advertisement] ;)

 

Image optimization – 7 mistakes

Thursday, June 26th, 2008

The slides from my talk at the Velocity conference

» Download

 

Load a photo in a canvas, then flip

Saturday, May 3rd, 2008

zlati-nathalie.jpg

Today our family went to the yearly photo session with the girls. We took one shot that can be looked normally, as well as upside down, so I was wondering can you flip an image using a canvas tag. Turns out, yes, you can and it's pretty easy.

» Demo is here.

How to load an image in a canvas tag?

Start unpretentiously with an empty canvas tag:

<canvas id="canvas"></canvas>

Now the javascript. Two variables to store a handle to the canvas element and the 2D context of the canvas:

var can = document.getElementById('canvas');
var ctx = can.getContext('2d');

Now let's load an image into the canvas. Using the new Image() constructor you can create an image object, then set its src property to point to the location of the image file. Then set an onload handler for the image which is an anonymous function to be called when the image is done loading. There you put the image inside the canvas using the drawImage() method of the canvas context.

var img = new Image();
img.onload = function(){
    can.width = img.width;
    can.height = img.height;
    ctx.drawImage(img, 0, 0, img.width, img.height);
}
img.src = 'zlati-nathalie.jpg';

You can also notice how the dimensions of the canvas are adjusted to match the dimensions of the image.

How to flip the image upside down

The canvas context provides a rotate() method. The rotation always happens around the top left corner of the image, so we first translate() the image to the bottom right. This way when the image is rotated, it fits back into the canvas. (There is also a one pixel correction, I have no idea why, just saw that the image wasn't flipping exactly otherwise). Assigning this functionality to the onclick:

can.onclick = function() {
    ctx.translate(img.width-1, img.height-1);
    ctx.rotate(Math.PI);
    ctx.drawImage(img, 0, 0, img.width, img.height);
};

C'est tout! Once again, the demo is here.

 

Canvas pie with tooltips

Saturday, March 8th, 2008

This is very flattering: Greg Houston took my script for DIY canvas pie and added tooltips and better colors logic. Here's the result, it's really nice, built with some MooTools. The tooltips are not supported in <canvas> but Creg used an image that overlays the pie and set the tooltips with an image map. Clever, isn't it?

I just used random colors for the pieces of the pie, but Greg knows better. Based on a tutorial by Jim Bumgardner, he was able to achieve much more pleasing, yet reliable results. Funny thing that Jim works in Yahoo! two floors above me (used to be even on the same floor).

Check the demo and the post, cool stuff, works in IE too!

 

YUI pie chart example

Wednesday, January 16th, 2008

If case you haven't noticed - YUI Charts hit the streets.

As with everything new, it's best shown and understood by example. So here's the simplest example of using a pie chart. Basically I took the example from the YUI page, changed all the paths to point to yahooapis.com (where YUI is hosted for free) and stripped everything that could be stripped (there's even no html or head tags, but turned out the body tag is required). The result is a short html with all dependencies satisfied.

OK, so here's the example, grab, paste, customize:
chart.html

 

Image fun with PHP – part 2

Tuesday, November 13th, 2007

This post is a demo of what the imagefilter() PHP function can do for you.

The Original

Nathalie

imagefilter() called with different filter constants

img_filter_brightness_5.png
Filter: IMG_FILTER_BRIGHTNESS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_BRIGHTNESS5);
imagepng($image'img_filter_brightness_5.png');
imagedestroy($image);
?>

img_filter_brightness_50.png
Filter: IMG_FILTER_BRIGHTNESS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_BRIGHTNESS50);
imagepng($image'img_filter_brightness_50.png');
imagedestroy($image);
?>

img_filter_brightness_100.png
Filter: IMG_FILTER_BRIGHTNESS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_BRIGHTNESS100);
imagepng($image'img_filter_brightness_100.png');
imagedestroy($image);
?>

img_filter_grayscale.png
Filter: IMG_FILTER_GRAYSCALE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagepng($image'img_filter_grayscale.png');
imagedestroy($image);
?>

img_filter_contrast_5.png
Filter: IMG_FILTER_CONTRAST
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_CONTRAST5);
imagepng($image'img_filter_contrast_5.png');
imagedestroy($image);
?>

img_filter_contrast_-40.png
Filter: IMG_FILTER_CONTRAST
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_CONTRAST, -40);
imagepng($image'img_filter_contrast_-40.png');
imagedestroy($image);
?>

img_filter_contrast_50.png
Filter: IMG_FILTER_CONTRAST
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_CONTRAST50);
imagepng($image'img_filter_contrast_50.png');
imagedestroy($image);
?>

img_filter_colorize_100_0_0.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE10000);
imagepng($image'img_filter_colorize_100_0_0.png');
imagedestroy($image);
?>

img_filter_colorize_0_100_0.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE01000);
imagepng($image'img_filter_colorize_0_100_0.png');
imagedestroy($image);
?>

img_filter_colorize_0_0_100.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE00100);
imagepng($image'img_filter_colorize_0_0_100.png');
imagedestroy($image);
?>

img_filter_colorize_100_100_-100.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE100100, -100);
imagepng($image'img_filter_colorize_100_100_-100.png');
imagedestroy($image);
?>

img_filter_colorize_50_-50_50.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE50, -5050);
imagepng($image'img_filter_colorize_50_-50_50.png');
imagedestroy($image);
?>

img_filter_edgedetect.png
Filter: IMG_FILTER_EDGEDETECT
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_EDGEDETECT);
imagepng($image'img_filter_edgedetect.png');
imagedestroy($image);
?>

img_filter_emboss.png
Filter: IMG_FILTER_EMBOSS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_EMBOSS);
imagepng($image'img_filter_emboss.png');
imagedestroy($image);
?>

img_filter_gaussian_blur.png
Filter: IMG_FILTER_GAUSSIAN_BLUR
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GAUSSIAN_BLUR);
imagepng($image'img_filter_gaussian_blur.png');
imagedestroy($image);
?>

img_filter_selective_blur.png
Filter: IMG_FILTER_SELECTIVE_BLUR
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_SELECTIVE_BLUR);
imagepng($image'img_filter_selective_blur.png');
imagedestroy($image);
?>

img_filter_mean_removal.png
Filter: IMG_FILTER_MEAN_REMOVAL
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_MEAN_REMOVAL);
imagepng($image'img_filter_mean_removal.png');
imagedestroy($image);
?>

img_filter_smooth_5.png
Filter: IMG_FILTER_SMOOTH
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_SMOOTH5);
imagepng($image'img_filter_smooth_5.png');
imagedestroy($image);
?>

img_filter_smooth_50.png
Filter: IMG_FILTER_SMOOTH
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_SMOOTH50);
imagepng($image'img_filter_smooth_50.png');
imagedestroy($image);
?>

img_filter_negate.png
Filter: IMG_FILTER_NEGATE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_NEGATE);
imagepng($image'img_filter_negate.png');
imagedestroy($image);
?>

A lazy way to do sepia

In order to do sepia, first you do grayscale, then colorize. Here are some experiments:

sepia_100_50_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE100500);
imagepng($image'sepia_100_50_0.png');
imagedestroy($image);
?>

sepia_100_70_50.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE1007050);
imagepng($image'sepia_100_70_50.png');
imagedestroy($image);
?>

sepia_90_60_30.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE906030);
imagepng($image'sepia_90_60_30.png');
imagedestroy($image);
?>

sepia_60_60_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE60600);
imagepng($image'sepia_60_60_0.png');
imagedestroy($image);
?>

sepia_90_90_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE90900);
imagepng($image'sepia_90_90_0.png');
imagedestroy($image);
?>

sepia_45_45_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE45450);
imagepng($image'sepia_45_45_0.png');
imagedestroy($image);
?>

You can read about the right way to do sepia in Wikipedia, but I'd say that the fakes above look pretty good too.
You can try with different values for R, G and B, but hear this - the thing about sepia is that it's brownish-yellow.
Brown is something that has more red, little blue and the green exactly in between the red and the blue. So brown examples would be (200, 100, 0) or (150, 100, 50).
Yellow on the other hand is equal red and green and no blue, like (255, 255, 0). So if you want more brownish sepia, use the first pattern when calling the colorize filter.

About part one

Part one of the image fun is here, it contains code that more or less does the same things, but pixel by pixel, which is very slow, but also works in PHP4.
At the time of writing part one, imagefilter() funtion was probaly only in cvs, not part of the official PHP. imagefilter() is PHP5-only.

 

Image_Text 0.6 beta is out

Thursday, April 19th, 2007

» Download here

This is my first PEAR release and I was actually surprised how easy it is to package and roll out a release.

So you have your local copy of the CVS repository that contains the scripts you want to release as part of the package. In order to release, you need package.xml, a configuration file, which you can either create yourself or have a script (which uses PEAR_PackageFileManager) to create the xml file for you.

The pear command line tool does all the rest.

  1. pear convert - creates package2.xml based on your package.xml. (package2 is the newer improved version of package2.xml. You can actually use PEAR_PackageFileManager2 instead and skip this step)
  2. pear package - creates the package archive which then you upload to pear.php.net
  3. pear cvstag package2.xml - tags the cvs repository with a tag figured out from the package2. In my case the tag was RELEASE_0_6_0beta

Thanks!

As stated in the change log notes, many thanks go to Christian Weiske and James Pic for helping out with this release!

 

Laziest image resize in PHP

Wednesday, December 13th, 2006

Today I saw a post at digg.com on image resizing with PHP and there was quite a discussion. Let me share the laziest way (that I know of) how to do it - PEAR::Image_Transform is all it takes. Here goes:

<?php
require_once 'Image/Transform.php';
$i =& Image_Transform::factory('');
 
$i->load('test.jpg');
$i->fit(100,100);
$i->save('resized.png', 'png');
?>

In addition, the Image_Transform library offers diffferent ways (to skin the old cat) to resize an image - by given pixel value, only on the X axis, on Y, scalling in percentage and so on. And, of course, the library can do much more than resizing, as you can see in the API docs.

It supports all kinds of image manipulation extensions - GD, GD1, ImageMagick, NetPBM, Imlib... If you want to use a specific one, you set as a parameter to the factory() method. In the example above I passed an empty string, so it will try to figure out what's available in my PHP setup and use it, trying Imagick2 first, then GD, then Imlib.

You have setOption() and setOptions() methods if you want to play around with the image quality and those sort of things.