In this tutorial, I'll demonstrate how to create vintage (just like Instagram does) photos with PHP and ImageMagick. Wait? What? Yes, you can do this very thing with PHP and ImageMagick, and that's just scratching the surface!
We Made Digital Vintage Photos, Before it Was Cool
Once upon a time - technically 22 years ago (5 years before PHP) - ImageMagick was released. Since then, it has evolved to a platform independent software suite that can create, edit, compose, or convert raster images (over 100 formats supported!). You can use it to resize, flip, mirror, rotate, distort, shear and transform images, adjust image colors, apply various special effects, or draw text, lines, polygons, ellipses and Bézier curves. It truly has everything you will ever need, when dealing with image manipulation in web development, video processing, panorama generating, etc. Please note, however: it is not a GUI image editor.
ImageMagick is command-line Photoshop for web.
Image Manipulation With PHP
PHP comes bundled with GD (GIF Draw/Graphics Draw), which is a library for the dynamic creation of images. It can be used for simpler image operation, such as resizing, cropping, adding watermarks, creating thumbnails (Jeffrey wrote about it), applying basic photo filters - you've probably used it before. Unfortunately, if you want to create something more complex with GD, like Instagram effects, you can't. Luckily, though, we have ImageMagick!
GD vs. ImageMagick
These two can't compare on higher levels, therefore, we will use simple example, like resizing. Let's imagine that we've uploaded a new 1024x768 photo.jpg image, and we want to dynamically resize it to 640x480 pixels.
GD
In the example below, we have to call six functions, and possibly perform some calculations if we have variable aspect ratio.
$im = imagecreatefromjpeg('photo.jpg'); $ox = imagesx($im); $oy = imagesy($im); $nx = 640; $ny = 480; $nm = imagecreatetruecolor($nx, $ny); imagecopyresized($nm,$im,0,0,0,0,$nx,$ny,$ox,$oy); imagejpeg($nm, 'photo.jpg');
ImageMagick
IM (short for ImageMagick) has a nice wrapper, called Imagick - a native PHP extension to create and modify images using the ImageMagick API. The only downside is: you will likely have to install it from PECL, which can sometimes be a problem for shared hosting.
$image = new Imagick('photo.jpg'); $image->resizeImage(640, 480, imagick::FILTER_LANCZOS, 0.9);
Even simpler, command-line usage with PHP (this is what we are going to use):
exec('mogrify -resize 640x480 photo.jpg');
That's it! Excellent.
Installing ImageMagick
Although nearly every good hosting company has ImageMagick installed, you probably don't have one on a local machine, simply because it isn't shipped with PHP.
Installing ImageMagick is a cinch, though. Go to the ImageMagick Download page, choose your platform (Unix/Mac/Win), and select recommended package. Simply follow the simple instructions; you can't make a mistake here.
Once finished, go to your terminal/command-prompt, type in convert
and hit Enter
, If you receive a list of options instead of "Command not found", you're good go! Note that you don't need to configure anything in PHP.
How Does Instagram Work?
Well, to be honest, I don't know what system the Instagram team are using for image processing. ImageMagick is also available for iOS; perhaps that is the magic of how Instagram works? To quote Kevin Systrom, Instagram CEO and co-founder:
It's really a combination of a bunch of different methods. In some cases, we draw on top of images, in others we do pixel math. It really depends on the effect we're going for.
For instance, Lomo-fi really isn't much more than the image with boosted contrast. Whereas Toaster is one of the most complex (and slow, yet popular) filters we have with multiple passes and drawing.
I'd give up more info, but it's our secret sauce :) Maybe some day...
"Maybe some day..." isn't good enough for us, Mr Systrom. Challenge accepted!
Show Me Teh Codez!
We are going to mimic gotham (return of gotham), toaster (the complex one), nashville (the popular one), lomo (lomo-fi isn't that good) and kelvin (lord kelvin - original) filters.
Instagraph - the PHP Class
I have created a little PHP wrapper class to make the process of filtering images as simple as possible. In these filters, as you know, we have lots of...
-
colortone
: will color tone an image in highlights and/or shadows. For example, we want to change black to purple. -
vignette
: edges of photo fade out or desaturate gradually. We can even reverse this, or use colors for vignette. -
border
: will add a border to the photo. For example, we want a white or black or any color border of a certain width; note that the width of the border will add up to the photo dimensions. -
frame
: will read specified frame and stretch in to fit to photo. We need this for the Nashville and kelvin filters. -
tempfile
: creates temporary file (copy of original image), to work with. -
output
: simply renames the working copy. -
execute
: we will send all commands through this method to prevent errors that can occur while working with the shell.
I've made all of these mentioned, so we can skip to fun part. Create a new file, called instagraph.php
, and copy & paste the code below.
/** * Instagram filters with PHP and ImageMagick * * @package Instagraph * @author Webarto <[email protected]> * @copyright NetTuts+ * @license http://creativecommons.org/licenses/by-nc/3.0/ CC BY-NC */ class Instagraph { public $_image = NULL; public $_output = NULL; public $_prefix = 'IMG'; private $_width = NULL; private $_height = NULL; private $_tmp = NULL; public static function factory($image, $output) { return new Instagraph($image, $output); } public function __construct($image, $output) { if(file_exists($image)) { $this->_image = $image; list($this->_width, $this->_height) = getimagesize($image); $this->_output = $output; } else { throw new Exception('File not found. Aborting.'); } } public function tempfile() { # copy original file and assign temporary name $this->_tmp = $this->_prefix.rand(); copy($this->_image, $this->_tmp); } public function output() { # rename working temporary file to output filename rename($this->_tmp, $this->_output); } public function execute($command) { # remove newlines and convert single quotes to double to prevent errors $command = str_replace(array("\n", "'"), array('', '"'), $command); $command = escapeshellcmd($command); # execute convert program exec($command); } /** ACTIONS */ public function colortone($input, $color, $level, $type = 0) { $args[0] = $level; $args[1] = 100 - $level; $negate = $type == 0? '-negate': ''; $this->execute("convert {$input} ( -clone 0 -fill '$color' -colorize 100% ) ( -clone 0 -colorspace gray $negate ) -compose blend -define compose:args=$args[0],$args[1] -composite {$input}"); } public function border($input, $color = 'black', $width = 20) { $this->execute("convert $input -bordercolor $color -border {$width}x{$width} $input"); } public function frame($input, $frame) { $this->execute("convert $input ( '$frame' -resize {$this->_width}x{$this->_height}! -unsharp 1.5×1.0+1.5+0.02 ) -flatten $input"); } public function vignette($input, $color_1 = 'none', $color_2 = 'black', $crop_factor = 1.5) { $crop_x = floor($this->_width * $crop_factor); $crop_y = floor($this->_height * $crop_factor); $this->execute("convert ( {$input} ) ( -size {$crop_x}x{$crop_y} radial-gradient:$color_1-$color_2 -gravity center -crop {$this->_width}x{$this->_height}+0+0 +repage ) -compose multiply -flatten {$input}"); } /** RESERVED FOR FILTER METHODS */ }
Instagram Filters
We will go over the filters, one by one; I will explain the necessary PHP methods and Imagemagick commands, including examples. Make sure you update your PHP class with these new methods (paste below RESERVED FOR FILTER METHODS comment). The photo frames are provided in the download package; they are just PNG transparent images without extensions. Feel free to make your own! So let's begin...
Original
Here we simply have a photo of my dogs enjoying the day at the beach. This is straight out of my camera.
Gotham
The Gotham filter produces a black&white, high contrast image with bluish undertones. In real life, this would be created with a Holga camera and Ilford X2 film.
public function gotham() { $this->tempfile(); $this->execute("convert $this->_tmp -modulate 120,10,100 -fill '#222b6d' -colorize 20 -gamma 0.5 -contrast -contrast $this->_tmp"); $this->border($this->_tmp); $this->output(); }
In English: create a working file, load the image into memory, improve brightness a bit, (almost) desaturate, change the remaining colors to deep purple, gamma correction (value below 1 darkens image), add more contrast, add more contrast, and save everything to a file. Add a 20px black border. Simple, eh?
Toaster
The Toaster filter resembles old Polaroid shots; it features vivid colors with pink/orange glow out of the center. By the words of Instagram CEO, it's one of the most difficult effects to create; we'll take his word for it.
public function toaster() { $this->tempfile(); $this->colortone($this->_tmp, '#330000', 100, 0); $this->execute("convert $this->_tmp -modulate 150,80,100 -gamma 1.2 -contrast -contrast $this->_tmp"); $this->vignette($this->_tmp, 'none', 'LavenderBlush3'); $this->vignette($this->_tmp, '#ff9966', 'none'); $this->output(); }
In English: create a working file, load the image into memory, change blacks to dark red, enhance brightness, desaturate by a fifth, perform gamma correction (make image brighter), add more contrast, add more contrast, save. Lastly, add a grayish vignette (desaturates edges a bit), and an "inverted" orange vignette for color burn effect.
Tip: You can even add a white border for a full effect; just add
$this->border($this->_tmp, 'white');
before$this->output();
.
Nashville
Nashville has a nice washed out 80s fashion photo feel. It produces image with a magenta/peach tint. It additionally adds a frame to get that slide look. It's easily one of the most popular Instagram filters.
public function nashville() { $this->tempfile(); $this->colortone($this->_tmp, '#222b6d', 100, 0); $this->colortone($this->_tmp, '#f7daae', 100, 1); $this->execute("convert $this->_tmp -contrast -modulate 100,150,100 -auto-gamma $this->_tmp"); $this->frame($this->_tmp, __FUNCTION__); $this->output(); }
In English: create a working file, load the image into memory, change blacks to indigo, change whites to peach color, enhance contrast, enhance saturation by half, gamma auto-correction. Add a frame from a PNG file.
Lomo
Lomography is all about making high contrast photos with vignettes and soft focus (everywhere you go). In real life, they are mostly made with Holga, LOMO LC-A or so called toy cameras (cameras with plastic lens). This effect is pretty easy to recreate; we will simply enhance the red and green channels' contrast by a third, and add a vignette. Feel free to experiment as you wish.
public function lomo() { $this->tempfile(); $command = "convert {$this->_tmp} -channel R -level 33% -channel G -level 33% $this->_tmp"; $this->execute($command); $this->vignette($this->_tmp); $this->output(); }
Create a working file, load the image into memory, enhance red channel contrast by a third, enhance red channel again, apply a vignette.
Tip: If you prefer lomo effect without vignette, just comment or remove that section of code.
Kelvin
Named after Lord Kelvin, this effect applies a strong peach/orange overlay, and adds a washed out photo frame.
public function kelvin() { $this->tempfile(); $this->execute("convert ( $this->_tmp -auto-gamma -modulate 120,50,100 ) ( -size {$this->_width}x{$this->_height} -fill 'rgba(255,153,0,0.5)' -draw 'rectangle 0,0 {$this->_width},{$this->_height}' ) -compose multiply $this->_tmp"); $this->frame($this->_tmp, __FUNCTION__); $this->output(); }
In English: create a working file, load the image into memory, normalize, enhance brightness by a fifth, desaturate by half, create an peach/orange color overlay, and apply the multiply blending mode. Lastly, add a frame, using the PNG file.
How to Use
It's easy to use these effects! I'll assume that you saved all the code within instagraph.php
file. Now, create a file, called filter.php
and copy the code below that suits you.
If you want to apply only one filter on an image, you can do it this way:
require 'instagraph.php'; try { $instagraph = Instagraph::factory('input.jpg', 'output.jpg'); } catch (Exception $e) { echo $e->getMessage(); die; } $instagraph->toaster(); // name of the filter
That's it! Now, if you want to apply all filters to one image, use this code:
require 'instagraph.php'; try { $instagraph = Instagraph::factory('input.jpg', 'output.jpg'); } catch (Exception $e) { echo $e->getMessage(); die; } // loop through all filters foreach(array('gotham', 'toaster', 'nashville', 'lomo', 'kelvin') as $method) { $instagraph->_output = $method.'.jpg'; // we have to change output file to prevent overwrite $instagraph->$method(); // apply current filter (from array) }
Now, just open it in your browser and enjoy the results!
Performance
Performance is certainly an important part of every application. Because the average time to apply a filter to an image is roughly 1 second, we can safely say it is pretty fast!
ImageMagick Resources
To learn more about ImageMagick, here's a list of links to all the commands and options that were used in these filter methods:
- convert:
- modulate: vary the brightness, saturation, and hue
- contrast: enhance or reduce the image contrast
- size: width and height of image
- fill: color to use when filling a graphic primitive
- draw: annotate the image with a graphic primitive
- compose: set image composite operator
- channel: apply option to select image channels
- level: adjust the level of image contrast
- auto-gamma: automagically adjust gamma level of image
- gamma: level of gamma correction
Additionally, here's a list of ImageMagick scripts, tutorials and examples:
Summary
In this tutorial, we learned a bit about Imagemagick, and demonstrated the power of it by creating filters that are similar to the ones generated by Instagram. We created Instagraph!
If you need any help, or need assistance creating additional filters, such as Tilt Shift or Earlybird, let me know within the comments, and I'll do my best to assist!
Comments