Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer. You can expect quick tips, links to interesting tutorials, opinions and packages. Want to learn the cool stuff? Then sign up now!

Easily optimize images using PHP (and some binaries)

Our recently released image-optimizer package can shave off some kilobyes of PNGs, JPGs, SVGs and GIFs by running them through a chain of various image optimization tools. In this blog post I’ll tell you all about it.

First, here’s a quick example on how you can use it:

use Spatie\ImageOptimizer\OptimizerChainFactory;

$optimizerChain = OptimizerChainFactory::create();

$optimizerChain->optimize($pathToImage);

The image at $pathToImage will be overwritten by an optimized version which should be smaller. Here are a few images that were optimized by this package.

Why we built this

On nearly every website we make, images are displayed. In fact, in most cases images make up the bulk of the size of the entire page. So to provide a fast page load it’s best to make those images as small as they can be. The less bytes the browser needs to download the faster the page will be.

Now, to make images as small as they can be (without sacrificing a lot of detail) there are a lot of tools available. These tools, such as jpegtran, pngquant, and gifsicle, work more or less by applying a little bit of compression, removing metadata and reducing the number of colors. In most cases these tools can make your images considerably smaller without you noticing, unless you have a trained eye for that.

These tools are free to use and can be easily installed on any system. So far so good. What makes them a bit bothersome to use from PHP code is that you need to create a process to run them. You must make sure that you pass the right kind of image to the right optimizer. You also have to decide which optimization parameters you’re going to use for each tool. None of this is rocket science, but I bet that the vast majority of small to medium sites don’t bother writing this code or researching these optimizations.

Our package aims to do all of this out of the box. It can find out which optimizers it should run, it can execute the binary and by default uses a set of optimizers with a sane configuration.

How to optimize images

Optimizing an image is very easy using our package.

use Spatie\ImageOptimizer\OptimizerChainFactory;

$optimizerChain = OptimizerChainFactory::create();

$optimizerChain->optimize($pathToImage);

The image at $pathToImage will be overwritten by an optimized version which should be smaller.

The package will use these optimizers if they are present on your system:

The readme of the package includes instructions on how to install these on Ubuntu and MacOS.

Which tools will do what?

Like already mentioned, the package will automatically pick the right tool for the right image.

JPGs

JPGs will be made smaller by running them through JpegOptim. These options are used:
--strip-all: this strips out all text information such as comments and EXIF data
--all-progressive: this will make sure the resulting image is a progressive one, meaning it can be downloading using multiple passes of progressively higher details.

PNGs

PNGs will be made smaller by running them through two tools. The first one is Pngquant 2, a lossy PNG comprossor. We set no extra options, their defaults are used. After that we run the image throug a second one: Optipng. These options are used:
-i0: this will result in a non-interlaced, progressive scanned image
-o2: this set the optimization level to two (multiple IDAT compression trials)

Customizing the optimization

If you want to customize the chain of optimizers you can do so by adding Optimizers manually to a OptimizerChain.

Here’s an example where we only want optipng and jpegoptim to be used:

use Spatie\ImageOptimizer\OptimizerChain;
use Spatie\ImageOptimizer\Optimizers\Jpegoptim;
use Spatie\ImageOptimizer\Optimizers\Pngquant;

$optimizerChain = (new OptimizerChain)
   ->addOptimizer(new Jpegoptim([
       '--strip-all',
       '--all-progressive',
   ]))

   ->addOptimizer(new Pngquant([
       '--force',
   ]))

If you want to use another tool than the package supports out of the box, you can easily write your own optimizer. An optimizer is any class that implements the Spatie\ImageOptimizer\Optimizers\Optimizer interface. If you want to view an example implementation take a look at the existing optimizers that ship with the package.

Integration in other packages

Our image-optimizer is not the first package we wrote the revolves around handling images. There also our image package which makes modifying images very easy. And we have laravel-medialibrary which can associate all kinds of files (including images) with Eloquent models. And lastly we have Browsershot, which can turn any webpage into an image. Let’s take a look on how image-optimizer has been integrated in all of those.

Image

Using the image package you can manipulate an image like this:

Image::load('example.jpg')
    ->sepia()
    ->blur(50)
    ->save();

This creates a sepia version that is blurred. The package has many other available manipulations. Here’s how you can create an optimized version.

Image::load('example.jpg')
    ->sepia()
    ->blur(50)
    ->optimize()
    ->save();

Yea, just add the optimize method in the chain and you’re done. Easy right?

laravel-medialibrary

In laravel-medialibrary this is just as easy. Using that package you can define conversion profiles on your models. Whenever you associate a file with that model a derived file using that conversion profile is being generated. This is handy for creating thumbnails and such.

Here’s a quick example of such a conversion profile.

use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia\Interfaces\HasMediaConversions;
use Spatie\MediaLibrary\HasMedia\HasMediaTrait;

class NewsItem extends Model implements HasMediaConversions
{
    use HasMediaTrait;

    public function registerMediaConversions()
    {
        $this->addMediaConversion('thumb')
              ->width(368)
              ->height(232)
              ->sharpen(10);
    }
}

Let’s add an image to the medialibrary:

$media = NewsItem::first()->addMedia($pathToImage)->toMediaCollection();

Besides storing the original item, the medialibrary will also create a derived image.

$media->getPath();  // the path to the where the original image is stored
$media->getPath('thumb') // the path to the converted image with dimensions 368x232

$media->getUrl();  // the url to the where the original image is stored
$media->getUrl('thumb') // the url to the converted image with dimensions 368x232

That’s a crash course into using medialibrary. But wait, we didn’t create an optimized version of that thumbnail. Let’s do that now. Under the hood all conversions are done by the aforementioned image package. So you can just use optimize function in your conversion profile.

    public function registerMediaConversions()
    {
        $this->addMediaConversion('thumb')
              ->width(368)
              ->height(232)
              ->sharpen(10)
              ->optimize();
    }

Boom done.

In the next major version of medialibrary we’ll automatically call optimize behind the screens for all image conversions. So you’ll get optimized conversion by default. We’ll add a nonOptimized method if you want to opt out of that. We haven’t introduced that behaviour in the current version because it’s breaking change.

Browsershot

Browsershot is a package that leverages headless Chrome to turn any webpage into an image. Here’s how to use it:

Browsershot::url('https://example.com')->save($pathToImage);

And here’s how to save an optimized version:

Browsershot::url('https://example.com')->optimize()->save($pathToImage);

In closing

I should mention that our optimize package is based upon another one by Piotr Śliwa. All the basic principles on how the package should work are inspired by Piotr’s work. The reason why we rewrote it is because his was not that easy to extend and did not use modern stuff such as Symfony’s Process component or PSR-3 compatible logging.

In this post I’ve mainly mentioned tools you can install locally, but there actually are a lot of SaaS solutions as well such as TinyPNG, Kraken.io, imgix.com and many many others. In this first release of our image-optimizer package I’ve mainly concentrated on supporting local optimizers. With remote optimizers you have to deal with slowness of the network and API keys and such. But I do recognize the value of those remote services. That’s why you’ll probably see some remote optimizers being referenced or included in package in the future. Here’s an issue on the repo where the first thoughts on that were being exchanged.

The package contains a few more features not covered by this blogpost. So check out image-optimizer on GitHub. I hope our tool can make your images smaller and your pages faster. If you haven’t already done so, check out our previous work in the open source space. Please send us a postcard if any of our stuff makes in into your production environment.

Freek Van der Herten is a partner and developer at Spatie, an Antwerp based company that specializes in creating web apps with Laravel. After hours he writes about modern PHP and Laravel on this blog. When not coding he’s probably rehearsing with his kraut rock band. He loves waffles and butterflies.
  • Barry Bigtime

    A very nice tool indeed. Particularly useful for when users upload photos to my site. I’d also quite like to have a command for the package which I could run from the root of my Laravel package which would go into my images folder and optimise them.

    • Rasmus Schultz

      Here’s an excellent tool for batch JPEG optimization:

      https://github.com/danielgtaylor/jpeg-archive

      Also, here’s a wrapper around the JPEG optimization tool for PHP:

      https://github.com/kodus/jpeg

      My goals for this little lib are different from the one presented here – mainly, I wanted something that would install directly from Composer and run on all major platforms without additional installation or building tools from sources.

  • Pingback: Optimize images in Laravel apps - murze.be()

  • Clark Winkelmann

    Is “lossy PNG comprossor” a new technology I never heard of or a typo ? :p

  • Nice Post!

  • Nepomuceno Calatrava

    Awesome! Do it work with PHP Version 5.6.3?

    • Robin Kramer

      Doubt it, because I got that reply, when trying to install it with composer:
      [InvalidArgumentException]
      Could not find package spatie/image-optimizer at any version matching your PHP version 5.6.30.0

  • Robin Kramer

    Is there a way to get it running on php 5.2?

    Otherwise it would be nice to atleast make a note somewhere that it requires php5.3 or higher~