Jens Segers on Jan 06 2012

CodeIgniter resizing and cropping images on the fly

One of the more popular articles on my blog was "Codeigniter: resizing and cropping images on the fly". At the time I wrote the article I wanted to create a code that would be really easy to use. However, there is a huge downside in the implementation. All the images are resized in 1 execution cycle of your controller. This means that when your code is executed, it will have to wait until each image is resized until it can go any further and send the complete output to the user.

A better solution would be to create a separate controller that only handles the resizing and cropping. This way your code will execute faster and each image will be requested separately to that specific controller. At first you might think this is a bit overkill because the CodeIgniter framework will have to load for each requested image. This is true, but there is an easy way around this.

Think of it this way, if you request an existing image from your media folder, it is loaded without any CodeIgniter interaction. This is because the .htaccess will only activate CodeIgniter if a non-file is requested. Now, if you would create a controller with the same name as your media folder, and you would request a non existing image, that controller will be activated. This way you can activate the controller for each image that is requested for the first time, all the following request will be taken directly to the generated image.

Installation

Download the files from github and place them in their corresponding folder.

  1. Make sure you are using the .htaccess mod_rewrite
  2. Rename the controller and the media folder to your liking. (must be the same name)
  3. Add a custom route to your config/routes.php file. This will redirect all "non-existing-file" request to the media controller's resize method: $route['media/(:any)'] = 'media/resize/$1';
  4. Optional: autoload the image_helper by adding it to $autoload['helper'] array in your config/autoload.php file.

Configuration

First you need to specify what preset sizes you will be using for your images in the config/images.php file:

$config["image_sizes"]["square"] = array(400, 400);
$config["image_sizes"]["rectangle"] = array(600, 400);
$config["image_sizes"]["long"] = array(200, 600);
$config["image_sizes"]["wide"] = array(600, 200);

$config["image_sizes"]["small"] = array(300, 0);
$config["image_sizes"]["medium"] = array(500, 0);
$config["image_sizes"]["large"] = array(800, 0);

Each preset has a width and a height. If one of the dimensions are equal to 0, it will automatically calculate a matching width or height to maintain the original ratio. A preset of (200, 0) will generate:

preview

Now the cool stuff, if you specify both dimensions it will automatically crop the resulting image so that it fits those dimensions. A preset of (200, 200) will generate:

preview

If you do not want the image to be cropped from the center axis you can specify your own axis by using the default $config['x_axis'] and $config['y_axis']

Usage

To resize and crop the images to the preset sizes you need to load the image_helper. This helper has a function image($path, $preset) that will translate a preset to a generated path that contains the dimensions for the controller or that will take you directly to the image if it has already been resized.

Use this helper in your view files like this:

<img src="<?php echo image("media/smiles.png", "wide"); ?>" alt="smiles" /> 

This will eventually translate into:

<img src="media/smiles-600x200.png" alt="smiles" /> 

The reason I add the dimensions to the original filename instead of the preset name is because when you would change the preset's dimensions, it would still load images with the old dimensions that were already generated instead of the new dimensions.

Library

The resize and crop logic is grouped in a library that extends CodeIgniter's 'image_lib' and adds a 'fit' function. You can use this library to resize and crop an image to fit the specified dimensions like this:

$config["source_image"] = '/path/to/image/mypic.jpg';
$config['new_image'] = '/path/to/new/image/newpic.jpg';
$config["width"] = 100;
$config["height"] = 100;

$this->load->library('image_lib', $config);
$this->image_lib->fit();

The function will return TRUE on sucess or FALSE on failure. Error messages can be read like the normal library with the display_errors() method.

Conclusion

This is a way more efficient way of resizing and cropping your images "on the fly" than the the one in my first article. The output is send to the user without having to wait until all images are resized and there is less interaction with CodeIgniter when the images are already generated.

I hope you enjoy this "snippet". If you encounter any problems, feel free to contact me.


Comments

WeshWesh 3 years ago

Thanks!~


Ow 3 years ago

Nevermind, it was becaise I had my images in a folder called 'images' and renamed the controller 'images'. This broke it!


Ow 3 years ago

This looks perfect for what I need, however it doesn't seem to be working at all for me. The controller doesn't seem to get called at any point, whilst the helper method returns the modified "600x200" url. Any thoughts?


abrandrilo 4 years ago

Hermosa extensión de librería, la función fit es magnifica


Vishnu R 4 years ago

Thank you so much :)


A Martindale 4 years ago

Sorry ignore the code in my previous post. I had to re-jig it to the following:

// ------------------------------------------------------------------------------------------ // mode 2: resize and crop the image to fit both dimensions // ------------------------------------------------------------------------------------------

    // set the master dimension for resizing
    $master_dim_ratio = (intval($this->orig_width) / intval($this->orig_height)) - ($this->user_width / $this->user_height);
    $this->master_dim = ($master_dim_ratio > 0)? "height" : "width";

    // calculate the height and width for resizing
    if ($this->master_dim == 'height') {
        $this->width = ceil($this->orig_width * ($this->user_height / $this->orig_height));
        $this->height = $this->user_height;
    } else {
        $this->width = $this->user_width;
        $this->height = ceil($this->orig_height * ($this->user_width / $this->orig_width));
    }

    // save dynamic output for last
    $dynamic_output = $this->dynamic_output;
    $this->dynamic_output = FALSE;

Thanks again


A Martindale 4 years ago

This is great. Thank you very much.

For certain aspect ratios I was getting black padding to the left and right of my images.

I added the following code to the MY_Image_lib.php, to line 122, before the line "// save dynamic output for last"

// set the master dimension for resizing to prevent empty padding during crop $master_dim_ratio = (intval($this->orig_width) / intval($this->orig_height)) - ($this->user_width / $this->user_height); $this->master_dim = ($master_dim_ratio > 0)? "height" : "width";

This sets the master axis for resizing.

The code came from here: http://kennykee.com/138/codeigniter-resize-and-crop-image-to-fit-container-div-example/

this page also explains why it works.


John sticker 4 years ago

I find this solution on last week. thank you for share this content. i will use this solution to http://www.teedin108.com for create thumbnail on responsive web design (new design)

thank you very much.


bilal 4 years ago

thanks for sharing :)


AndreAmo 4 years ago

Jens I really appreciate your work! Thanks for sharing this amazing CI add-on, many thanks!


Nigel 5 years ago

I managed to solve it by updating the media controller adding in:

    $oldpath = $this->uri->uri_string();

    $array = explode('/', $oldpath);
    $key = array_search("media", $array);
    $newarray = array_splice($array, $key);

    $path = implode("/", $newarray);        

at line 34. Feels like sledge hammer for a nut, but ensures that the $path starts with the "media/"


Nigel 5 years ago

Hi Jens. Really like your script, however i have been running the "example" controller which works with its index function, but if I try to run "example/newpage" a function within example controller, it does not load the image rather the path it creates is website.com/example/newpage/media/filename.jpg rather than website.com/media/filename.jpg. It appears that the the controller name is being added to the request url

I have tried playing with the routes such as: $route['example/newpage/media/(:any)'] = 'media/resize/$1';

But no joy. Any way to do this correctly? Especially for controllers in sub-folders. Appreciate any help you can give.

Thanks


Mike 5 years ago

For those having problems with the black areas, the issue is that the library relies in $this->master_dim that is not set. I think you need to calculate the master dime by yourself, so this worked for me:

Insert this right before line 115 where the master_dim is checked:

$this->master_dim = ($this->orig_width/$this->orig_height > $this->user_width/$this->user_height) ? "width" : "height";

Hope this patch helps somebody! Great extension btw. Couldn't believe CodeIgniter didn't have the fit() function built in.

Cheers!


Jens Segers 5 years ago

@Chris Fallon; you can just use the library that is included.


Jens Segers 5 years ago

@Fer; when I used the library I did not encounter these black strokes. It has been a while since I used CI for a project, so it might be because of an update CI did that broke something.


Chris Fallon 5 years ago

This is a great article, Thanks Jens. My question, does anyone know where to find an article on "Codeigniter - cropping images on the fly"? Something to where a user would upload a file, then crop it while sizing it for a id card?"


Eric 5 years ago

Hi jens,

Same issue than @Fer. I'd like to avoid this black space when I resize it. Is there any solution? Thanks


Fer 5 years ago

Hello, Jens,

Anyway, leaving apart that thing about the squares, I don't find the way to crop the pictures as I want and instead I'm having those black areas. How could I fix this?

Thanks a lot!


Jens Segers 5 years ago

@Fer, it's a bit strange. The fit extension relies on both the resize and fit method with some additional calculations, I don't really see why it is doing that.


Fer 5 years ago

@Jens: Since I was not able to configure it easily with sparks and so on, and I did not want the whole thing working on my project, I just use your library overriding to the core's one. After that, for each thumbnail, I have different settings of width and height according to the type ("small-square", "medium", "special-tall", etc.). This is my code after that:

            $config['source_image'] = $url;

$config['new_image'] = $new_image_path; $config["width"] = $width; $config["height"] = $height; $CI->image_lib->initialize($config); if (!$CI->image_lib->fit()) echo $CI->image_lib->display_errors(); $CI->image_lib->clear();

Also, with this configuration I noticed that some of my squares I create for small sizes such my "small-square" preset (50x50) does not work properly always. It resizes the image at Wx50 or 50xH, keeping ratio, but it does not crop in the end, triggering the "display_errors" part. What I do as a workaround is checking afterwards if the size is correct (50x50). In case not, I redo the process with the new picture as source. Eventually after just one iteration or few more, it works. Is this normal, a bug or I'm doing something wrong?

Thanks a lot!


Jens Segers 5 years ago

@Fer, did you use the fit method to create the thumbnail? Or did you use $config['create_thumb']?


Fer 5 years ago

Hello,

I'm using your awesome library overiding the default behaviour of the lib_image library. I'm very happy with it, but I'm having a small problem (not your fault). For example, I was trying to get a thumbnail for this picture: http://wearephoenix.com/journal/wp-content/uploads/2009/02/crop2.jpg

I set the width and height to 290x160. What I get is this: http://i.imgur.com/KiuA30i.jpg

The problem is that I do not like the "blank black space" at all. I was expecting, for example, to get the width of the original picture and crop centered the height. How can I configure the settings in order to achieve this?

Thank you very much and best regards!


Eric 5 years ago

Hi! I use this solution to create thumbnails and crop images on my CMS and I'm glag I found this blog. However I had to make some changes to make it works in my CMS so please tell me if I forgot something.

Here is the lines I changed to make it work: media.php 37 $original = "media/" . str_ireplace("-" . $size, "", $pathinfo["basename"]); 74 $config['new_image'] = substr($path, 1); 86 if (file_exists( substr($path, 1) )) { 87 $output = substr($path, 1);

I also changed this line to save thumbnails in another directory: image_helper.php 59 $new_path = $pathinfo["dirname"] . "/thumbnails/" . $pathinfo["filename"] . "-" . implode("x", $sizes[$preset]) . "." . $pathinfo["extension"];

Thanks!


Jens Segers 5 years ago

@Mauricio de Abreu Antunes; make sure you have a route that catches '/assets/uploads/files', that redirects to the media controller.


Jens Segers 5 years ago

@kargaa, try the initialize method. I think CodeIgniter ignores your second library load because it is already loaded.

$this->load->library('image_lib'); $this->image_lib->initialize($config);


kargaa 5 years ago

Hello, I want to resize 3 images at same time, I used $this->image_lib->clear(); for creating 2nd and 3nd one but it gives error as .

A PHP Error was encountered

Severity: Warning Message: Division by zero Filename: libraries/MY_Image_lib.php Line Number: 99

{"success":"1"}

My code is below ;

$config['source_image'] = './temp_uploads/' . $this->input->post('userfile'); $config['new_image'] = './uploads/kayip_kopekler/thumbs/'.$randomfile.''; $config["width"] = 80; $config["height"] = 80; $this->load->library('image_lib', $config); $this->image_lib->fit(); $this->image_lib->clear();

$config['source_image'] = './temp_uploads/' . $this->input->post('userfile'); $config['new_image'] = './uploads/kayip_kopekler/thumbs_big/'.$randomfile.''; $config["width"] = 300; $config["height"] = 300; $this->load->library('image_lib', $config); $this->image_lib->fit(); $this->image_lib->clear();


Mauricio de Abreu Antunes 5 years ago

Can you give more examples? It is not working for me. It calls the helper with "media/image_here.JPG" but how does it work?

My images are uploaded into /assets/uploads/files and i want my images to generated into /assets/uploads/resized or just a "fly" generation.


Jens Segers 5 years ago

@Adrian San Martin, the code may seem a bit strange at first. But if you understand what is actually happening when the image is loaded for a first time and the second time, things should become a bit more clear!


Rajan 5 years ago

Awesome Job. It worked like a charm.


Adrian San Martin 5 years ago

Hello everybody okay!

First I would like to thank JS for tutoring.

I wonder if it is already working correctly the code? since appeared with many problems.

Sorry for my english, it is not my first language.


Pushpinder Bagga 6 years ago

Just superb!


Jens Segers 6 years ago

@Leonel did you use $this->image_lib->initialize($config); for each of the images?


duocmaster 6 years ago

good.thanks


Leonel 6 years ago

I've a problem with the "fit()" proccess in a "foreach" loop, only works in the first image of the array. How I can fix it ?


Jens Segers 6 years ago

@efx, no I did not have this issue. The media controller has 'readfile($output);' at the end that should output the generated file.


Jens Segers 6 years ago

@Michiel, I thought CodeIgniter automatically calls the initialize method when you pass the $config while loading. Anyways, I added the line to be sure :)


Michiel 6 years ago

DEA is right: you should add $this->image_lib->initialize($config); immediately after the $this->load->library('image_lib', $config); line in the media controller. Otherwise you'll get a division by zero error (because $this->orig_height is set by the get_image_properties() call in the initialize() method).

Thank you so much for sharing this useful solution!


efx 6 years ago

The first view loading, the image are resizing, but the page not output the thumb at first time, it need to refresh in hand to have back the images. is it the same issue for you Guys ? is it possible to refresh the view one time the controller resizing is invoked ?


Jens Segers 6 years ago

John, I will try to look into this tonight.


john 6 years ago

Hi again. Just noticed it only seems to happen with gif images.


john 6 years ago

Hello,

Great script. Just implemented and on the whole works really well. The only problem I am having is cropping images which have a larger width but shorter height. For example an image with dimensions 246x73, when running it through ‘fit’ with user defined dimensions of 120x120 it re-sizes the image to 404x120 (as expected) but then when it comes to the crop it sort of does it correctly on the newly re-sized image (e.g in this case it zooms in and crops based on the middle) but the actual image size remains at 404px wide with white space on either side.

Hope that makes sense, any ideas on how to fix this?

Thanks a lot


Jens Segers 6 years ago

If the image does not exist, the controller is executed once. The image is then saved to the requested location. If it does exist the image is returned directly without any php. This should be explained in the article. If it is unclear I can rewrite that part.


Deepak Thomas 6 years ago

Hey, does this have a caching system too? Or are the images generated on-the-fly for each request ?


Andy Wright 6 years ago

Thanks for getting back to me.Problem is I am on my second install of this spark and after many hours I have still been unable to get the spark to work. Does it definitely work for you on a fresh install?


Jens Segers 6 years ago

Sparks do not support controllers, but I had to place it somewhere :) I will update the readme to be more clear on this part.


Andy Wright 6 years ago

Hi,This is an awesome system. However, I cannot get it to work as a spark.the problem seems to be that sparks do not support controllers. To fix this I moved the media controller to my controllers folder -- but then I get errors telling me that "The configuration file images.php does not exist" (I am currently reading through the MY_Loader.php file to see how it works -- but it looks like it should be able to access that config file in the search paths).Anyway, the crux is that I have so far been unable to make it work even in a default install of CI...


fl3x7 6 years ago

Hello,i also seem to be getting Division by zero but on line 111 in MY_Image_lib.php. Tried adding the initialize but no joy. Seems it cant get the dimensions of the source_image I think? Any help appreciated as the script looks cool. The source image exists and path seems correct.Thanks


DEA 6 years ago

Looks like I finally figured out the issue. I had to add $this->image_lib->initialize($config); immediately after the $this->load->library('image_lib', $config); line in the media controller. Who knew..?


Jens Segers 6 years ago

Can you email me your code that is causing the error? I will look into the issue.


DEA 6 years ago

Agh, looks like I need to move on. Just can't seem to get this working.


DEA 6 years ago

Think I'm back to square one on this, just can't make it work with a different URI structure. For example:$route['media/searches/(:any)/(:any)/(:any)'] = 'media/resize/media/searches/$1/$2/$3'; is what I'm trying to work with, but I just get 404s.


DEA 6 years ago

Oops, I didn't see that you had replied. Going to try your controller and report back!


Jens Segers 6 years ago

It's a bit strange, on the line you mentioned '$this->orig_height' is zero. This is a value that CI initializes before fit() is called. So I don't see what is happing right away.This is the example controller that I use for my images: https://github.com/segersjens/...


DEA 6 years ago

Have dealt with the path image on my end, all good. However, I am getting 'division by zero' errors on line 99 of the lib and no resizing is happening.


DEA 6 years ago

This looks great, but I have a question: my images are stored in dynamically generated folders such as 'users/454/images/uniqid/whatever.jpg. How would I go about passing path information through the helper from a view? Is this possible?


Jens Segers 6 years ago

I have rewritten the axis part to only use the center axis if no axis options are set by config. This should fix your problem. Settings an x_axis and y_axis to 0 will get you the top left. If you encounter any problems, please contact me!https://github.com/segersjens/...


Paul Wenzel 6 years ago

I love your approach here, and had success implementing both your previous and improved versions in a personal project. However, I would like to pass the x_axis and y_axis parameters used by the CI image manipulation library to crop my images from the top, rather than the center. Do you have any tips on how I could go about doing that with your library/helper?I tried putting these settings in config/image_lib.php, but it doesn't seem to pick up this configuration settings.Edit: I commented out lines 132 and 133 of MY_Image_lib.php to get the effect I desired. Perhaps down the road I could make it a config option.Thanks!