Resizing and serving images on the fly with Laravel – Part 2



In the second part of our tutorial on manipulating and serving images on the fly with Laravel we will refactor the controller code and write a library to resize and cache images.

We will also edit our config file to add specific sizes and name them. This tutorial continues where Manipulating and serving images on the fly with Laravel – Part 1 left off.

First, let’s make sure our controller and router accepts an additional size parameter.

Modifying the route and controller

in app/routes.php change the following code:

Route::get(
	'/images/{file}', 
	'ImageController@getImage'
);

We will add the size parameter before the file parameter. Our route will now look like this:

Route::get(
	'/image/{size}/{file}',
	'ImageController@getImage'
);

Now modify your ImageController‘s getImage action declaration to have the size parameter like this:

class ImageController extends BaseController {

	public function getImage($size, $filename) {
		...
	}

}

Creating the Image library

Now we know how to open an image, read it and re-output it. The question is: how do you resize an image with Laravel 4? For this I recommend using Imagine. Follow the instructions for installing Imagine here.

After installing Imagine we have to set up an Image class and a facade.

First, however, let’s re-think our configuration file a bit.

Let’s make it more structured and define sizes inside of it. For example we could set a size called “big” and make it 600×400 pixels.

Our final config file in app/config/assets.php

<?php

return array(
	'images' => array(

        'paths' => array(
            'input' => 'app/assets/images',
            'output' => 'app/storage/cache/images'
        ),

        'sizes' => array(
            'small' => array(
                'width' => 150,
                'height' => 100
            ),
            'big' => array(
                'width' => 600,
                'height' => 400
            )
        )
    )

);

Since we are saving our resized images, this is actually cached. So I chose to output images to app/storage/cache/images.

Now we are ready to create our Image class and facade. Our structure will look like this:

app
  libraries
    facades
       Image.php
    Image.php

Let’s start with our Image class (not the facade). I have commented the source code since it so it should all be clear. Basicly what our class will do is resize the image, save it, and get the mimetype. Save this file as app/libraries/Image.php



<?php namespace App\Libraries\Image;

// We need to add these namespaces
// in order to have access to these classes.
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Config;

class Image {

    protected $imagine;

    // We instantiate the Imagine library with Imagick or GD
    public function __construct($library = null)
    {
        if ( !$this->imagine) {
            if ( !$this->library and class_exists('Imagick')) {
                $this->imagine = new \Imagine\Imagick\Imagine();
            } else {
                $this->imagine = new \Imagine\Gd\Imagine();
            }
        }
    }

    /*
     * Resize function.
     * @param string filename
     * @param string sizeString
     *
     * @return blob image contents.
     */
    public function resize($filename, $sizeString) {

        // We can read the output path from our configuration file.
        $outputDir = Config::get('assets.images.paths.output');

        // Create an output file path from the size and the filename.
        $outputFile = $outputDir . '/' . $sizeString . '_' . $filename;

        // If the resized file already exists we will just return it.
        if (File::isFile($outputFile)) {
            return File::get($outputFile);
        }

        // File doesn't exist yet, so we will resize the original.
        $inputDir = Config::get('assets.images.paths.input');
        $inputFile = $inputDir . '/' . $filename;

        // Get the width and the height of the chosen size from the Config file.
        $sizeArr = Config::get('assets.images.sizes.' . $sizeString);
        $width = $sizeArr['width'];
        $height = $sizeArr['height'];

        // We want to crop the image so we set the resize mode and size.
        $size = new \Imagine\Image\Box($width, $height);
        $mode = \Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND;

        // Create the output directory if it doesn't exist yet.
        if (!File::isDirectory($outputDir)) {
            File::makeDirectory($outputDir);
        }

        // Open the file, resize it and save it.
        $this->imagine->open($inputFile)
            ->thumbnail($size, $mode)
            ->save($outputFile, array('quality' => 90));

        // Return the resized file.
        return File::get($outputFile);

    }

    /**
     * @param string $filename
     * @return string mimetype
     */
    public function getMimeType($filename) {

        // Make the input file path.
        $inputDir = Config::get('assets.images.paths.input');
        $inputFile = $inputDir . '/' .  $filename;

        // Get the file mimetype using the Symfony File class.
        $file = new \Symfony\Component\HttpFoundation\File\File($inputFile);
        return $file->getMimeType();
    }

}

Now we can simply create our facade like this in app/libraries/facades/Image.php

<?php namespace App\Libraries\Facades;

use Illuminate\Support\Facades\Facade;

class Image extends Facade {

    protected static function getFacadeAccessor()
    {
        return new \App\Libraries\Image\Image;
    }

}

We need to make sure composer will autoload our class. So in your composer.json add the app/libraries folder to the autoload paths.

Make sure to run the command composer dump-auto  as well as php artisan dump-autoload afterwards! Not doing so will make it so that your class does not get found.

"autoload": {
		"classmap": [
			"app/commands",
			"app/controllers",
			"app/models",
			"app/database/migrations",
			"app/database/seeds",
			"app/tests/TestCase.php",
            "app/libraries"
		]
	},

Now add the alias in the app/config/app.php

'Image'           => 'App\Libraries\Facades\Image'

 Refactoring the Image Controller

Last thing we need to do is refactor our image controller. Our goal is to make our controllers have as few lines as possible. We can now make our controller very light by modifying the code like this:

<?php

class ImageController extends BaseController {

	public function getImage($size, $filename) {

		// Make a new response out of the contents of the file
        // We refactor this to use the image resize function.
		// Set the response status code to 200 OK
		$response = Response::make(
			Image::resize($filename, $size),
			200
		);

        // Set the mime type for the response.
        // We now use the Image class for this also.
        $response->header(
            'content-type',
            Image::getMimeType($filename)
        );

		// We return our image here.
		return $response;
	}

}

Let’s test it out!

Create a sample view if you want, and put <img> tags in it like this:

<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Laravel PHP Framework</title>

</head>
<body>
	<h1>This image is resized</h1>
    <img src="/image/small/YOURIMAGE.jpg">
    <img src="/image/big/YOURIMAGE.jpg">
</body>
</html>

That’s all there is to it! It only resizes the images once. If they are already resized it will increase your pageload by loading the cached image.

I hope you like this 2-part tutorial. Like us on facebook, follow us on twitter, and share our tutorials on your favorite media to show your appreciation. Thanks!

Share the knowledge!
Share on Facebook0Tweet about this on Twitter0Share on Google+0Share on StumbleUpon138Share on Reddit0Share on LinkedIn0Share on TumblrBuffer this pageDigg this

Comments

You may also like...

Stay updated
Subscribe!