Skip to content

Commit

Permalink
Added Earlybird
Browse files Browse the repository at this point in the history
Added Inkwell
Added Custom
Added install instructions
Added thanks to readme
  • Loading branch information
rob-mccann committed Jul 5, 2012
1 parent 9589059 commit e00b6ca
Show file tree
Hide file tree
Showing 7 changed files with 761 additions and 4 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

Replicate Instragram-style filters in PHP

# Install
# Installation

The tmp directory must be writable.

# Usage

Expand All @@ -24,13 +25,17 @@ require_once('classes/Filter/Earlybird.php');
require_once('classes/Filter/Inkwell.php');
```



# Todo

- Add more filters
- (Somehow) Improve performance
- Improve interface
- Implement more photoshop functions in imagemagick
- Implement 'curves' properly by using polynomial regression to get the coefficients needed for imagick's FX function
- Abstract to separate from Image class
- Abstract and decouple from Image class
- Make composer/packagist compatible

# Thanks

- FuelPHP for use of (parts of) their image class.
- Daniel Box for his Photoshop actions for instram filters (http://dbox.tumblr.com/post/5426249009/instagram-filters-as-photoshop-actions)
257 changes: 257 additions & 0 deletions classes/Filter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<?php namespace Instafilter;

/**
* Filter class
*
* This holds all of the common things between filters like getting access to the imagemagick instance etc.
*
* @package Instafilter
*/
abstract class Filter
{
private $imagick = null;


public function __construct(array $configuration = array())
{
$this->configuration = array_merge(array(
'imagemagick_dir' => '/usr/bin/',
), $configuration);
return $this;
}

/** You should only call this when you're ready to save, it often overwrites your image a few times (not always though) **/
abstract public function apply_filter();

/**
* This is the imagemagick instance to perform the filters on
*
* It's recommended you supply a temporary file here!
* Some functions, such as brightness_contrast, overwrite the image.
*
* @todo decouple from Image class
*
*/
public function imagick(&$imagick = null)
{
if ($imagick === null)
{

return $this->image()->imagick();
}
$this->imagick = $imagick;
return $this;
}

public function image(&$image = null)
{
if ($image === null)
{

return $this->image;
}
$this->image = $image;
return $this;
}

/**
* Replicate Colorize function
* @param string $color a hex or rgb(a) color
* @param int $composition use imagicks constants here
* @return Filter
*/
public function colorize($color, $composition = \Imagick::COMPOSITE_MULTIPLY)
{
$overlay = new \Imagick();
$overlay->newPseudoImage($this->imagick()->getImageWidth(), $this->imagick()->getImageHeight(),"canvas:$color");
$this->imagick()->compositeImage($overlay, $composition, 0, 0);
return $this;
//$this->exec('convert',"\( -clone 0 -fill '$color' -colorize 100% \) -compose {$composition} -composite ");
}

/**
* Change the gamma of an image
* @param float $gamma normally between 0.8 and 2
* @param int $channel Use the Imagick constants for this
* @return Filter
*/
public function gamma($gamma, $channel = null)
{
$this->imagick()->gammaImage($gamma);
return $this;
}

/**
* Replicate Photoshop's levels function.
*
* @param float $gamma
* @param int $input_min between 0 and 255, same as photoshops
* @param int $input_max between 0 and 255, same as photoshops
* @param int $output_min between 0 and 255, same as photoshops
* @param int $output_max between 0 and 255, same as photoshops
* @param int $channel use imagemagicks constants
* @return Filter
*/
public function levels($gamma = 1, $input_min = 0, $input_max = 255, $output_min = 0, $output_max = 255, $channel = \Imagick::CHANNEL_ALL)
{
$range = $this->imagick()->getQuantumRange();
$range = $range['quantumRangeLong'];

//convert photoshop's units to imagemagicks
$input_min = round(($input_min / 255) * $range);
$input_max = round(($input_max / 255) * $range);
$output_min = round(($output_min / 255) * $range);
$output_max = round(($output_max / 255) * $range);

// set input levels
$this->imagick()->levelImage($input_min, $gamma, $input_max, $channel);

// set output levels
$this->imagick()->levelImage(-$output_min, 1.0, $range + ($range - $output_max), $channel);

return $this;
}

/**
* Replicate brightness/contrast photoshop function
*
* Now this one is a bit of a pain. PHP's extension doesn't provide us with this handle (yet?)
* So we have to save the image to disk at this point, perform the function using the command line, and reload the image. yay.
*
* @param int $brightness this is should be -150 <= brightnes <= 150. 0 for no change.
* @param int $contrast this is should be -150 <= contrast <= 150. 0 for no change.
* @return Filter
*/
public function brightness_contrast($brightness, $contrast)
{
//normalise from photoshop's units to imagicks
$brightness = round(($brightness / 3) * 2);
$contrast = round(($contrast / 3) * 2);

$this->exec('convert'," -brightness-contrast {$brightness}x{$contrast} ");

return $this;
}

/**
* Execute a manual CLI command -- normally used when there's no PHP alternatice
*
* @param string $command normally 'convert'
* @param string $params the CLI params
* @return Filter
*/
public function exec($command, $params)
{
$input = $this->imagick->getImageFilename();
$params = " \"{$input}\" " .$params. " \"{$input}\"";
$this->_exec($command, $params);

return $this;
}

/**
* Execute the command
*
* This is duplicated from the image class so that this can be used without the in-built image class
*
* Based on FuelPHP's command
*
* @param type $program
* @param type $params
* @param type $passthru
* @return type
*/
private function _exec($program, $params, $passthru = false)
{
// Determine the path
$this->im_path = realpath($this->configuration['imagemagick_dir'].$program);

// store the filename for this image
$filename = $this->imagick()->getImageFilename();

// check imagemagick is where we expect it
if ( ! $this->im_path)
{
$this->im_path = realpath($this->configuration['imagemagick_dir'].$program.'.exe');
}
if ( ! $this->im_path)
{
throw new \RuntimeException("imagemagick executables not found in ".$this->configuration['imagemagick_dir']);
}

// save the filters that have already been applied
$this->imagick()->writeImage($filename);


$command = $this->im_path." ".$params;

$code = 0;
$output = null;


//do the actual command
$passthru ? passthru($command) : exec($command, $output, $code);

// reload the imagemagick instance once the manual command is done
$this->image->imagick(new \Imagick($filename));

// check if it's successful
if ($code != 0)
{
throw new \Exception("imagemagick failed to edit the image. Returned with $code.<br /><br />Command:\n <code>$command</code>");
}

return $output;
}

/**
* Replicate HSL function
*
* Imagemagick calls this 'modulate
* '
* @param int $hue -100 <= hue <= 100. 0 is no change.
* @param int $saturation -100 <= hue <= 100. 0 is no change.
* @param int $lightness -100 <= hue <= 100. 0 is no change.
* @return Filter
*/
public function hsl($hue = 0, $saturation = 0, $lightness = 0)
{
$hue += 100;
$saturation += 100;
$lightness += 100;

$this->imagick()->modulateImage($lightness, $saturation, $hue);
return $this;
}

/**
* Replicate photoshop's curves function
*
* This takes a series of points and generates a function for the graph that fits the points.
* That function is then applied to each pixes, as in photoshop.
* It should use curves_graph once the function has been generated.
*
* @param array $points an array of arrays. array(array(12, 32), array(32,56)) etc.
* @param int $channel use imagemagicks contants here
* @return Filter
*/
public function curves($points, $channel = null)
{
//polynomial interpolation
throw new \Exception('Curves is not yet implemented.');

return $this;
}

/**
* Perform an imagemagick-style function on each pixel
* @param string $fx the function
* @return Filter
*/
public function curves_graph($fx)
{
$this->imagick()->fxImage($fx);

return $this;
}
}
32 changes: 32 additions & 0 deletions classes/Filter/Custom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php Namespace Instafilter\Filter;

/**
* Custom filter
*
* Allows the user to quickly throw together a filter using closures
*
* new Custom(function($filter){
* $filter->hsl(12,3,1);
* });
*
* @package Instafilter
*/

use Instafilter;

class Custom extends Instafilter\Filter
{
private static $_closure = null;

public function __construct($closure, $configuration)
{
parent::__construct($configuration);
self::$_closure = $closure;
}
public function apply_filter() {
if ($closure !== null)
{
self::$closure($this);
}
}
}
27 changes: 27 additions & 0 deletions classes/Filter/Earlybird.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php Namespace Instafilter\Filter;

/**
* Earlybird class
*
* A sort-of sepia filter
*
* @package Instafilter
*/

use Instafilter;

class Earlybird extends Instafilter\Filter
{
public function apply_filter() {
$this->hsl(0, -32, 1)
->gamma(1.19)
->levels(1, 0, 255, 27, 255, \Imagick::CHANNEL_RED)
->hsl(0, -17, 0)
->levels(0.92, 0, 235)
->brightness_contrast(15, 36)

->colorize('rgb(251,243,220)')
->colorize('rgb(184,184,184)', \Imagick::COMPOSITE_COLORBURN)
->colorize('rgb(200,200,200)');
}
}
28 changes: 28 additions & 0 deletions classes/Filter/Inkwell.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php Namespace Instafilter\Filter;

/**
* Inkwell class
*
* A black and white filter
*
* @package Instafilter
*/

use Instafilter;

class Inkwell extends Instafilter\Filter
{
public function apply_filter() {
$this->hsl(0, -100, 0)
->curves_graph('-0.062*u^3-0.104*u^2+1.601*u-0.175')
->brightness_contrast(-10, 48);

// it would be nice to do the curves like this!
/*$this->curves(array(
array(11,0),
array(69,86),
array(158,185),
array(255,220),
));*/
}
}
Loading

0 comments on commit e00b6ca

Please sign in to comment.