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 Optimizer
s 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.
What are your thoughts on "Easily optimize images using PHP (and some binaries)"?