My Profile Photo

MSc. Alban Afmeti


Information Technology is my passion, especially software development. I am trying to grow professionally every day with hard work, with practice and online courses. My objective is clear. Everything is possible if we devote to that. All the persons with a higher position than me are there because the life has bring to them some opportunities, not because they are smarter than me. Let's find these opportunities and let's thank the god that we have a good healthy life.


Optimize images using Intervention in Laravel

a picture paints a thousand words” a famous phrase used by many people to show the importance of the images in our lives. It means that with a single image, we can convey to the others a complex idea, more descriptive and effective than a text description. We use images everyday in different projects, offering our services or products. A text description doesn’t show the quality of a product, better than its own image. So using images at our websites is very important because it interacts directly with the user.

Introduction

In this tutorial article I am going to show how to use Intervention Image Library in a little Laravel 5.3 project to optimize images at your website projects. What we are going to do is to present some utilities of this package, and to write some code using it.

When we build websites, in many cases we need to use images with different dimensions and we don’t have time to open image processing tools like Photoshop to resize images and to optimize them for our websites. It’s a waste of time, especially for us who deal with back-end part. Sometimes we build different management panels to manage dynamic parts of our websites, like for example blogs and gallery, which every time need to be updated with new photos, news etc. In this case, the users who use our panel, upload images with different dimensions. For example, whether they upload a 1200x600 or a 850x1400 image, it should be able to resize it accordingly.
If we don’t handle these cases, we are going to see a mess in our website like the following example:

Mess Images

What we have to do is to change the dimensions of our images in the render time, to create thumbnails for every image depending on the size that is necessary at the specified part of the website. For example, the images I have shown above will be OK if they will have dimensions 500x300, because the website is responsive and they need to have a good quality as for large devices as well as for small devices.

We will use Intervention Image Library to generate thumbnails for every image in our website with different sizes. So for the above example, all the images will get dimensions 500x300. Before using the library, I have created a Laravel 5.3 project and I have added some view files, a simple website showing a portfolio item and the related items.

Intervention Image

Intervention Image is an open source PHP image handling and manipulation library. It provides an easier and expressive way to create, edit, and compose images and supports currently the two most common image processing libraries: GD Library and ImageMagick. No matter if you want to create image thumbnails, watermarks or format large image files Intervention Image helps you to manage every task in an easy way with as little lines of code as possible.

To use intervention image, you need to have ImageMagick PHP extension or GD library installed in your server. For example in Ubuntu, you can install ImageMagick by executing the following command from the terminal:

sudo apt-get install php-imagick

The library requires at least PHP version 5.4 and comes with Laravel Facades and Service Providers to simplify the optional framework integration.

Basic example:

<?php

// open an image file
$img = Image::make('public/foo.jpg');

// now you are able to resize the instance
$img->resize(320, 240);

// and insert a watermark for example
$img->insert('public/watermark.png');

// finally we save the image as a new file
$img->save('public/bar.jpg');

or in one line:

<?php

$img = Image::make('public/foo.jpg')->resize(320, 240)->insert('public/watermark.png');

We need to install this library to our project, so we use Composer, a tool for dependency management in PHP.
To install the most recent version of Intervention Image, run the following command to the root directory of the project: composer require intervention/image

Integration in Laravel

Intervention Image has optional support for Laravel and comes with a Service Provider and Facades for easy integration.
After you have installed Intervention Image, open your Laravel config file config/app.php and add the following lines.

In the $providers array add the service providers for this package:

<?php

Intervention\Image\ImageServiceProvider::class

Add the facade of this package to the $aliases array:

<?php

'Image' => Intervention\Image\Facades\Image::class

Now the Image Class will be auto-loaded by Laravel.

Configuration

By default Intervention Image uses PHP’s GD library extension to process all images. If you want to switch to Imagick, you can pull a configuration file into your application by running on of the following artisan command:

php artisan vendor:publish --provider="Intervention\Image\ImageServiceProviderLaravel5"

In Laravel 5 applications the configuration file is copied to config/image.php, in older Laravel 4 applications you will find the file at app/config/packages/intervention/image/config.php. With this copy you can alter the image driver settings for you application locally.

Our Project

After installing the Intervention Image Library to our Laravel project we have to use it to generate thumbnails for our website.
The images of our website currently are located under the public/assets/images directory separated with subdirectories. This will be the main directory for our images, so define it in a configuration file. Create a customized configuration file called definitions.php under the config directory, with the following content:

<?php

return [
    'images_path' => 'assets/images',
];

Create a helpers.php file (if we don’t have one) to put a global function which will be used inside the view files, to call for the generation of any thumbnail. Inside the app/Http directory, create helpers.php file with a getThumbnail() function. Autoload the helpers.php file:

    "autoload": {
        "files": [
            "app/Http/helpers.php"
        ]
    },

Register the file for autoloading running in the terminal:

composer dump-autoload

Content of helpers.php file:

<?php

function getThumbnail($img_path, $width, $height, $type = "fit")
{
    return app('App\Http\Controllers\ImageController')->getImageThumbnail($img_path, $width, $height, $type);
}

Don’t be surprised because we need to write some code to make this function work. We need to create a controller and an action that will be executed after calling this function. What we have done is a global function which will be used to our views inside the src attribute of our images. For example:

<img src="" alt="Simple Image"/>

This function accepts four parameters:

  1. $img_path which contains the relative path starting from the main image directory up to the actual filename of the image. We said above that the main directory was declared to the definitions.php file, 'images_path' => 'assets/images'. If the image is directly in the main directory, we need to pass only the filename.

  2. $width contains a number, the width of the thumbnail we need.
  3. $height contains a number too, the height of the thumbnail we need.
  4. The last parameter $type contains a string showing the type of resizing we need, for example the best fitting aspect ratio of your given width and height on the current image or resize the image and add a black background for the additional parts.

Inside of the function we use the app('App\Http\Controllers\ImageController') global function which returns an object of ImageController class that we haven’t created yet. Then calls the action ->getImageThumbnail($img_path, $width, $height, $type).

Go to the app/Http/Controllers directory and add a new file called ImageController.php.
The ImageController class is going to have a single action with the content:

<?php

namespace App\Http\Controllers;

use Intervention\Image\ImageManagerStatic as Image;
use Illuminate\Support\Facades\File;

class ImageController extends Controller
{

    public function getImageThumbnail($path, $width = null, $height = null, $type = "fit")
    {

        $images_path = config('definitions.images_path');
        $path = ltrim($path, "/");

        //returns the original image if isn't passed width and height
        if (is_null($width) && is_null($height)) {
            return url("{$images_path}/" . $path);
        }

        //if thumbnail exist returns it
        if (File::exists(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path))) {
            return url("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path);
        }

        //If original image doesn't exists returns a default image which shows that original image doesn't exist.
        if (!File::exists(public_path("{$images_path}/" . $path))) {

            /*
             * 2 ways
             */

            //1. recursive call for the default image
            //return $this->getImageThumbnail("error/no-image.png", $width, $height, $type);

            //2. returns an image placeholder generated from placehold.it
            return "http://placehold.it/{$width}x{$height}";
        }

        $allowedMimeTypes = ['image/jpeg', 'image/gif', 'image/png'];
        $contentType = mime_content_type("{$images_path}/" . $path);

        if (in_array($contentType, $allowedMimeTypes)) { //Checks if is an image

            $image = Image::make(public_path("{$images_path}/" . $path));

            switch ($type) {
                case "fit": {
                    $image->fit($width, $height, function ($constraint) {
                        $constraint->upsize();
                    });
                    break;
                }
                case "resize": {
                    //stretched
                    $image->resize($width, $height);
                }
                case "background": {
                    $image->resize($width, $height, function ($constraint) {
                        //keeps aspect ratio and sets black background
                        $constraint->aspectRatio();
                        $constraint->upsize();
                    });
                }
                case "resizeCanvas": {
                    $image->resizeCanvas($width, $height, 'center', false, 'rgba(0, 0, 0, 0)'); //gets the center part
                }
            }

            //relative directory path starting from main directory of images
            $dir_path = (dirname($path) == '.') ? "" : dirname($path);

            //Create the directory if it doesn't exist
            if (!File::exists(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $dir_path))) {
                File::makeDirectory(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $dir_path), 0775, true);
            }

            //Save the thumbnail
            $image->save(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path));

            //return the url of the thumbnail
            return url("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path);
        } else {

            //return a placeholder image
            return "http://placehold.it/{$width}x{$height}";
        }
    }
}

getImageThumbnail() accepts four parameters, the same we explained above for the getThumbnail function. The default values of $width and $height parameters are null, and the default value for $type is fit. We need to return a string from this function, that’s an url to the specified thumbnail.
Let’s explain the content.
Firstly we save in a local variable $images_path the main directory of images which we get calling config('definitions.images_path') and the value of it we have set assets/images. For safety we make a left trim of the $path string that the user passes because sometimes a user may pass it with a slash and sometimes no (e.g. ‘/projects/project-1.jpg’ or ‘projects/project-1.jpg’.)
If the user doesn’t pass an argument for width and height they take their default value null. In this case we return back the original image, not a thumbnail because the user didn’t specify any value for width and height.

<?php

//returns the original image if isn't passed width and height
if (is_null($width) && is_null($height)) {
    return url("{$images_path}/" . $path);
}

For example if the user calls:
html <img src="" alt="Simple Image"/>
After rendering you will see:
html <img src="http://domain.com/assets/images/projects/project-1.jpg" alt="Simple Image"/>

Images are very useful when we build websites or other applications, so we need to generate thumbnails to resize the images in right dimensions to obtain a smaller page load. But, if we generate thumbnails every time a request is done for an image, there will be executed PHP code every time for every image that we have in our application and the page load will be slower, so we make a check:

<?php

//if thumbnail exist returns it
if (File::exists(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path))) {
    return url("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path);
}

If the requested thumbnail exist we return it immediately. We don’t need to rigenerate it because it exists in thumbs directory.

What is thumbs directory ?

We said that the main images directory was assets/images and inside this live original images within their subdirectories. thumbs directory is the main directory for thumbnails which will be generated and is located under assets/images. Which is the difference in a path of the original image and the thumbnail path?
If the original image has this path assets/images/projects/project-1.jpg , for a thumbnail with width $W and height $H will be: assets/images/thumbs/$Wx$H/projects/project-1.jpg. So we see a directory called thumbs that will contain all thumbnails, and another directory created for every specific dimension called “width x height” (without spaces). Inside the latter is followed the relative path gave by the user projects/project-1.jpg.

For example, original image:
assets/images/projects/project-1.jpg
Thumbnail of image with dimesions 400x600:
assets/images/thumbs/400x600/projects/project-1.jpg

What if the original file doesn’t exist?

In this case we can render a default ‘error’ image showing that the requested image doesn’t exist for example image with text “No Image Available”. We make this because we don’t want our website to look messy so we render such an image because it looks better than without any image. For example a list of books with cover, if the cover misses for one book the row will be messy, so we render a default image of our website. This is optional.
Another choice we can use (more in development mode) is to use some domain image generators with different sizes like placehold.it. This site is an image generator, which we give in only two parameters to the URL for example http://placehold.it/300x200 and it will return back an image with the dimensions written in it.

For example:

Placehold Example

The handling of image missing:

<?php

//If original image doesn't exists returns a default image which shows that original image doesn't exist.
if (!File::exists(public_path("{$images_path}/" . $path))) {

    /*
     * 2 ways
     */

    //1. recursive call for the default image
    //return $this->getImageThumbnail("error/no-image.png", $width, $height, $type);

    //2. returns an image placeholder generated from placehold.it
    return "http://placehold.it/{$width}x{$height}";
}

We need to validate if the $path passed by the user is an image or not:

<?php

$allowedMimeTypes = ['image/jpeg', 'image/gif', 'image/png'];
$contentType = mime_content_type("{$images_path}/" . $path);

if (in_array($contentType, $allowedMimeTypes)) { //Checks if is an image

    //Image processing

} else {

    //return a placeholder image
    return "http://placehold.it/{$width}x{$height}";
}

Create an array of allowed mime types. In this case we have set only three types ['image/jpeg', 'image/gif', 'image/png']. mime_content_type("{$images_path}/" . $path) returns the mime type of passed $path. If that is part of the allowed mime type we continue with the image processing, else we return a default image, wich can be a placeholder image or an error image showing that image is not available.

Suppose that the image was correct, so we need to generate a thumbnail with the given width and height. We use the Intervention Image Library for that:

<?php
$image = Image::make(public_path("{$images_path}/" . $path));

The first thing that you need to do when working with this library is to create an instance of the image from source. This is like saving a copy of the image into memory so that it can be easily manipulated. Any changes made to the image are only in memory until you choose to save it to the filesystem.
public_path("{$images_path}/" . $path) returns the absolute path of the file in server for example if these variables have these values: $images_path = 'assets/images' and $path = 'projects/project-1.jpg', the output of the function public_path("{$images_path}/" . $path) will be /var/www/..../public/assets/images/projects/project-1.jpg.

After this we need to resize the image, but here we need the $type parameter which decides what type of thumbnail do we want. With Intervention Image we can manipulate images in different ways but here I am showing some of them:

  1. fit.
    Combine cropping and resizing to format image in a smart way. The method will find the best fitting aspect ratio of your given width and height on the current image automatically, cut it out and resize it to the given dimension.

  2. resize.
    Resizes current image based on given width and/or height. In this way the image will be stretched.

  3. background.
    I have called it like this because it uses the resize method of Intervention Image but we pass a closure callback defining constraints on the resize. It’s possible to constraint the aspect-ratio and/or a unwanted upsizing of the image. In this case will be created a black background for the missing parts of the image with the new dimensions.

  4. resizeCanvas
    Resize the boundaries of the current image to given width and height. An anchor can be defined to determine from what point of the image the resizing is going to happen. Set the mode to relative to add or subtract the given width or height to the actual image dimensions. You can also pass a background color for the emerging area of the image.

See the code:

<?php

switch ($type) {
    case "fit": {
        $image->fit($width, $height, function ($constraint) {
            $constraint->upsize();
        });
        break;
    }
    case "resize": {
        //stretched
        $image->resize($width, $height, function ($constraint) {
            $constraint->upsize();
        });
    }
    case "background": {
        $image->resize($width, $height, function ($constraint) {
            //keeps aspect ratio and sets black background
            $constraint->aspectRatio();
            $constraint->upsize();
        });
    }
    case "resizeCanvas": {
        //gets the center part of the image
        $image->resizeCanvas($width, $height, 'center', false, 'rgba(0, 0, 0, 0)');
    }
}

We have used the instance of the image we just created to call different methods of Intervention. For example:

<?php

$image->fit($width, $height, function ($constraint) {
    $constraint->upsize();
});

We have called the ->fit() method and we have passed $width, $height and a closure callback defining constraint to prevent unwanted upsizing of the image. The same thing with the other methods, for example ->resize() method:

<?php

$image->resize($width, $height, function ($constraint) {
    $constraint->upsize();
});

In this case, the image will be saved stretched.

After generating the image, we have to create the directory path where the thumbnail will be placed, so we make a check if it exists or not and then we create it.

<?php

//relative directory path starting from main directory of images
//gets only the directory without the filename
$dir_path = (dirname($path) == '.') ? "" : dirname($path);

//Create the directory if it doesn't exist
if (!File::exists(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $dir_path))) {
    File::makeDirectory(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $dir_path), 0775, true);
}

Save the image:

<?php

//Save the thumbnail
$image->save(public_path("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path));

Return the URL for the specified image using the global function url()

<?php

//return the url of the thumbnail
return url("{$images_path}/thumbs/" . "{$width}x{$height}/" . $path);

url() global function returns the absolute url containing the domain. For example, for a thumbnail 300x300 it returns:

http://domain.com/assets/images/thumbs/300x300/projects/project-1.jpg

This was the explanation for the single action of ImageController and we are ready to use it.

Examples

To make some examples for test purposes I created a route and rendered a view, which contains some images. I uploaded some images with different sizes to my assets/images directory and now I will try to make different examples calling the global function getThumbnail().

Here is my route:

<?php

Route::get('/', function () {
    return view('index');
})->name('home');

A part of my view is shown as the following:


<!-- Related Projects Row -->
<div class="row">

    <div class="col-lg-12">
        <h3 class="page-header">Related Projects</h3>
    </div>

    <div class="col-sm-3 col-xs-6">
        <a href="#">
            <img class="img-responsive portfolio-item" src="" alt="">
        </a>
    </div>

    <div class="col-sm-3 col-xs-6">
        <a href="#">
            <img class="img-responsive portfolio-item" src="" alt="">
        </a>
    </div>

    <div class="col-sm-3 col-xs-6">
        <a href="#">
            <img class="img-responsive portfolio-item" src="" alt="">
        </a>
    </div>

    <div class="col-sm-3 col-xs-6">
        <a href="#">
            <img class="img-responsive portfolio-item" src="" alt="">
        </a>
    </div>

</div>
<!-- /.row -->

The images of ‘Related Projects’ section needs to be of size 500x300, so we call `` with a type ‘fit’.

In this case, after rendering is shown the following content:

Fit-Images

The second image doesn’t exist so it returned me a placeholder image, but the page isn’t messy.

If we see the HTML from the Inspect Element of the browser it will be like this:


<div class="row">

        <div class="col-lg-12">
            <h3 class="page-header">Related Projects</h3>
        </div>

        <div class="col-sm-3 col-xs-6">
            <a href="#">
                <img class="img-responsive portfolio-item" src="http://intervention.techalin.com/assets/images/thumbs/500x300/projects/project-1.jpg" alt="">
            </a>
        </div>

        <div class="col-sm-3 col-xs-6">
            <a href="#">
                <img class="img-responsive portfolio-item" src="http://placehold.it/500x300" alt="">
            </a>
        </div>

        <div class="col-sm-3 col-xs-6">
            <a href="#">
                <img class="img-responsive portfolio-item" src="http://intervention.techalin.com/assets/images/thumbs/500x300/projects/project-3.jpg" alt="">
            </a>
        </div>

        <div class="col-sm-3 col-xs-6">
            <a href="#">
                <img class="img-responsive portfolio-item" src="http://intervention.techalin.com/assets/images/thumbs/500x300/projects/project-4.jpg" alt="">
            </a>
        </div>

    </div>

Instead of ‘fit’ type, let’s use ‘background’ type for every image:

<img class="img-responsive portfolio-item" src="" alt="">

The output will be:

Background-Type

Sometimes we need to give only the $width parameter and for the height we want to be calculated automatically, to keep aspect ratio.

In this case, use the generation function as following:

<img class="img-responsive portfolio-item" src="" alt="">

The height of the image will be dynamic dependent from the image.
Or we give the $height and the width will be calculated automatically:

<img class="img-responsive portfolio-item" src="" alt="">

If we don’t pass to the function neither $width nor $height it will return the original image path:

<img class="img-responsive portfolio-item" src="" alt="">

The problem during development mode

During the development mode of my little project while doing and showing you different examples there was a problem with the history of thumbnails, because I wanted to try different types of thumbnails for the same image and the same size. The first time thumbnail will be generated and will be shown OK to the browser, but if I change the type for example from ‘fit’ to ‘resize’ the thumbnail will not be regenerated because it exists in the thumbs directory with the same name and size, and it will be returned instead of recreating a new one.

So how to solve this problem?

I want to present you an artisan command I have built for this case so you can use it when you are in development mode, or when your site needs a refresh.

The commands in Laravel 5.3 are under the directory app/Console/Commands and to generate a command class run in terminal the following command:

php artisan make:command ClearThumbnails

A new file will be generated called ClearThumbnails.php inside app/Console/Commands directory. Put the following content in the file:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class ClearThumbnails extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'images:clear-thumbnails';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Clears thumbnails directory "/assets/images/thumbs" ';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $images_path = config('definitions.images_path');
        $success = File::cleanDirectory(public_path("{$images_path}/thumbs"), true);
        if ($success) {
            $this->info('Cache cleared successfully!');
        } else {
            $this->error('Something went wrong!');
        }
    }
}

$signature contains the string that we will run with artisan and $description property contains the description of the command shown in the list of commands when we run php artisan list.

The handle() function contains the code which is going to be executed when we will run the command.

The method File::cleanDirectory(public_path("{$images_path}/thumbs"), true) will return false if the directory doesn’t exist. Otherwise it returns true when complete.

To preserve the top level directory, we have passed the second argument true. This will keep thumbs directory itself from being removed.
If the action will be successfull we print to the command line a message $this->info('Cache cleared successfully!') or if it fails we print an error message $this->error('Something went wrong!').

Once we have finished our command, we need to register it with Artisan. All commands are registered in the app/Console/Kernel.php file. To register our command, simply add the command’s class name to the list:

<?php

    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        Commands\ClearThumbnails::class
    ];

To run our command we open the terminal and run php artisan images:clear-thumbnails like in the following screenshot:

Artisan-Command

After this, our website will be refreshed and everything will be up-to-date.

Conclusion

In this article I have shown you a way to use Intervention Image Library to optimize the dimensions of images at your websites or other projects.
Intervention Image is very useful for image manipulation, like for example changing the image dimensions or adding any watermark to your image.
For more about it, you can read its documentation here.

This example project is in my github account so you can pull all the source code from this url:
https://github.com/albanafmeti/laravel-intervention-image.git

All you have to do is to clone the Github repository, with:

git clone https://github.com/albanafmeti/laravel-intervention-image.git

then run composer install to install dependencies.

comments powered by Disqus