Jump to content

Instagram module


summer
 Share

Recommended Posts

Hello there!

Appreciation

First of all I would like to say Thank You for making such a fantastic back-end. In my ten years of web development it's the first one to actually make sense to me. When I used to develop in Flash I was searching every where for a back-end that would just let me output my data as xml without the hassle of learning a new "language" or syntax, or messing with settings directly in the back-end itself. For my current portfolio and some other sites I ended up using Drupal with some plugins for managing the data and then just dug in to the database directly, pulling out whatever i needed. It wasn't easy, but it was easier than learning how to make an actual plugin that did the same.

With process wire I realize now I could have saved weeks of development, had I known of it's existence. It was so easy to get into that I basically just sad down a few hours one day to get familiar with the UI and I have only had use for the cheat sheet ever since. This stuff is build in a very logical way and without unneeded complexity. I can see a lot of thought / experience has gone into this, and I really appreciate you letting us use it!

Question

I have some ideas for new modules that I would like to build. They would add some extra functionality to the core functions of Processwire. One is to implement getID3() (http://getid3.sourceforge.net/) to get the extra data in mp3s like $fileMp3->composer for example.

The other one is to add extra image functionality on both the front- and back-end using imagemagick. I'm updating my portfolio to an html5 version and it's going to have a worn down/outdoor look, and therefore I'd like to add some Instagram type of effects to certain images (http://net.tutsplus....lters-with-php/).

On the back-end I would like to add options for adding image effects when the image has been uploaded. I would like to keep the original while the modified Image is used by the front-end. Ideally it would be a drop down with some predefined effects while having the option to add more than one effect.

Next would be to extend the Image with more options, just in the same way resize works. For example $fileImage->resize(150,150)->kelvin(75, "#4499ff")

I'll post what I came up with so far. It's inspired mainly by the tutsplus article and it contains obvious errors, but it's more to give you an idea of what I want to achieve.

I looked for an Image class to add a hook to - in the same way that resize works, but I couldn't really find out how it was put together. I also don't quite understand why this module shows up under an "Image" section as I didn't define it anywhere (see attached image).

I'm not asking for finished code here, but looking for guidance as http://processwire.com/api/modules/ and the hello_world.module don't tell me what I want to know. Or maybe I'm looking in the wrong place?

Anyway, here's the code so far. Any tips would be greatly appreciated! ( And sorry for the wall of text ;) )

<?php
class ImageInstagram extends WireData implements Module
{
	public $_image = NULL;
	public $_output = NULL;
	public $_prefix = 'IMG';
	private $_width = NULL;
	private $_height = NULL;
	private $_tmp = NULL;

	public static function getModuleInfo()
	{
		return array(

			// The module'ss title, typically a little more descriptive than the class name
			'title'	=> 'Image Instagram Effects',

			// version: major, minor, revision, i.e. 100 = 1.0.0
			'version'  => 003,

			// summary is brief description of what this module is
			'summary'  => 'Add InstaGram effects to images',

			// Optional URL to more information about the module
			'href'	 => 'http://www.processwire.com',

			// singular=true: indicates that only one instance of the module is allowed.
			// This is usually what you want for modules that attach hooks.
			'singular' => true,

			// autoload=true: indicates the module should be started with ProcessWire.
			// This is necessary for any modules that attach runtime hooks, otherwise those
			// hooks won't get attached unless some other code calls the module on it's own.
			// Note that autoload modules are almost always also 'singular' (seen above).
			'autoload' => true,
		);
	}

	public function init()
	{
		$this->addHook('Images::kelvin', $this, 'kelvin');
	}

	public function kelvin($event)
	{
		$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();
	}

	public function tempfile()
	{
		# copy original file and assign temporary name
		$this->_tmp = $this->_prefix . rand();
		copy($this->_image, $this->_tmp);
	}

	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 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);

	}

	public function output()
	{
		# rename working temporary file to output filename
		rename($this->_tmp, $this->_output);
	}
}

post-460-0-89476700-1344332622_thumb.png

  • Like 2
Link to comment
Share on other sites

Hi and welcome to the forums!

I also don't quite understand why this module shows up under an "Image" section as I didn't define it anywhere (see attached image).

That comes from the classname. Camelcases are preferred there and modules directory will use first word as a category.

I would myself try to build the API methods first and after that is working start to build the admin usage. You also might find some help from my thumbnails modules: https://github.com/apeisa/Thumbnails

Interesting module and I hope to help you more soon (pretty busy today). That is not the simplest one for first module, but the need is a good motivator ;) I'm sure you find good help from here. Ryan is on holidays this week, but there are many who can help you out. Also, if you can point out more exact questions it will help us to help you.

  • Like 1
Link to comment
Share on other sites

Hello there!

Antti answered the question regarding grouping, your functions will work out themselves (if not, there is always forums).

However if I may propose one suggestion, it would be nice if you renamed the module to something like FieldtypeImageEffects (because ultimately, your field will be fieldtype), so this doesn't confuses your users: yes, in some groups, instagram may have popularized these effects; However, they were here long before Instagram and additionaly, the name 'Instagram Module' as well as 'imageInstagram' are better suited to module connecting your PW install to your Instagram account.

Welcome to PW community

Link to comment
Share on other sites

Thanks for the kind words, glad that you are enjoying ProcessWire.

Regarding the module, I agree that Antti's Thumbnails module is probably the best one to look at since it is the only one I know of that lets you manipulate images in the admin like this.

$this->addHook('Images::kelvin', $this, 'kelvin');

This line above I wasn't sure about. What you are attempting to do is add a "kelvin" method to a class called "Images". But I'm not aware of any class that we have called "Images", so don't think that this hook would do anything at present. Instead, you may want to add it to the "Pageimage" class:

$this->addHook('Pageimage::kelvin', $this, 'kelvin'); 
public function kelvin($event) {
 $arg1 = $event->arguments[0]; // if needed
 $arg2 = $event->arguments[1]; // if needed
 $image = $event->object; // Pageimage object you are operating on
 // ...
}

However if I may propose one suggestion, it would be nice if you renamed the module to something like FieldtypeImageEffects (because ultimately, your field will be fieldtype)

That's assuming that the module goes the route of Antti's Thumbnails module. But if it's something that continues in the direction of the posted module code example, then it probably doesn't fit as a Fieldtype. Also, all Fieldtype modules should descend from the Fieldtype core class (or one of the other Fieldtypes descending from it).

Link to comment
Share on other sites

hello and thanks to both of you. I have taken your suggestions into consideration but have run into a development block.

I tried to install my module and am now greeted with an error message:

Fatal error: Class 'FieldtypeImage' not found in /Users/Development/persommer html/site/modules/FieldtypeImageEffects.module on line 2

And I can't make it go away :-/

<?php
class FieldtypeImageEffects extends FieldtypeImage implements Module
{
 public static function getModuleInfo()
 {
  return array
  (
   'title'    => 'Image Effects',
   'version'  => 001,
   'summary'  => 'Add effects to images',
   'href'	 => 'http://www.persommer.com',
   'singular' => true,
   'autoload' => true,
  );
 }
 public function init()
 {
 }
}

Link to comment
Share on other sites

Hey again and thanks for the replies! I had some trouble getting image magic to work on my mac, but now all is good.

My question is now: how do I update the data on the image object so it points towards the modified file?

$image->size(232, 176)->kelvin(55)->url

still outputs the same as

$image->size(232, 176)->url

Code so far:

<?php

class FieldtypeImageEffects extends FieldtypeImage implements Module
{
	public $url;

	public static function getModuleInfo()
	{
		return array
		(
			'title'	=> 'Image Effects',
			'version'  => 001,
			'summary'  => 'Add effects to images',
			'href'	 => 'http://www.persommer.com',
		);
	}

	public function init()
	{
		$this->addHook('Pageimage::kelvin', $this, 'kelvin');
	}

	public function kelvin(HookEvent $event)
	{
		$prefix	 = "kelvin";
		$img		= $event->object;
		$imgPath	= $img->pagefiles->path . "" . $img->basename;
		$imgPathNew = $img->pagefiles->path . "kelvin_" . $img->basename;
		// $imgUrl  = $img->pagefiles->url . $prefix . "_" . $img->basename;
		// $event->object->setUrl($imgUrl);

		$strExe = "convert '$imgPath' -modulate 120,10,100 -fill '#222b6d' -colorize 20 -gamma 0.5 -contrast -contrast '$imgPathNew'";

		if (is_file($imgPathNew))
		{
			// echo("file exists: <br>");
		}
		else
		{
			// echo("converting file: <br>");
			$this->execute($strExe);
		}

		$this->url = "HEHEHE";

		$event->return = $event->object;
	}

	public function execute($command)
	{
		// echo("COMMAND INPUT: " . $command . "<br>");
		$out = array();
		putenv("PATH=" . $_ENV["PATH"] . ":/opt/local/bin");
		exec($command . " 2>&1", $out);
		// echo("COMMAND OUTPUT: " . print_r($out, true) . "<br>");
	}
}
Link to comment
Share on other sites

Well, for starters, you should not return event->object; which is unmodified object you got as an argument.

Probably (and this is huge pseudocode), you should do something along:

<?php
 $img = $event->object;
 // create image copy
 $img->setUrl($newPath);
 $event->return = $img;

---

Or you could do

<?php
 $event->return = $newImgUrl;

and use it as following:

<?php
 echo $img->size($x,$y)->kelvin($args);
Link to comment
Share on other sites

yes, but what i wanted to do was actually modify the original object. this is also possible by calling

$event->object->setFilename($imgBaseNameNew);

so it's basically working the way i want it to. except for some reason it seems "kelvin()" gets called twice. will try to figure that out later.

thanx

Link to comment
Share on other sites

My question is now: how do I update the data on the image object so it points towards the modified file?

The size() function is basically returning a brand new Pageimage object, so if you wanted kelvin to do the same, take a look at the size() function in /wire/core/Pageimage.php -- let me know how this works out.

Link to comment
Share on other sites

Hello again.

I kinda got this working with a few different effects.

I had to implement a weird fix so the functions don't get called twice (notice the counter used to check if even or uneven) I just can't figure out why that is. If somebody could help that would be great!

home.php

<?php
/**
* Home template
*
*/
include("./head.inc");

foreach($homepage->images as $img)
{
echo("Original</br>");
$image =$img->size(400, 400);
echo "<img id='photo' src='{$image->url}' alt='{$image->description}' width='{$image->width}' height='{$image->height}' /><br/>";
echo("Gotham</br>");
$image =$img->size(400, 400)->fx(FieldtypeImageEffects::GOTHAM, 5, 1);
echo "<img id='photo' src='{$image->url}' alt='{$image->description}' width='{$image->width}' height='{$image->height}' /><br/>";
echo("Toaster</br>");
$image = $img->size(400, 400)->fx(FieldtypeImageEffects::TOASTER, 1.2);
echo "<img id='photo' src='{$image->url}' alt='{$image->description}' width='{$image->width}' height='{$image->height}' /><br/>";
echo("Nashville</br>");
$image = $img->size(400, 400)->fx(FieldtypeImageEffects::NASHVILLE, 1.2);
echo "<img id='photo' src='{$image->url}' alt='{$image->description}' width='{$image->width}' height='{$image->height}' /><br/>";
echo("Custom frame</br>");
$image = $img->size(400, 400)->fx(FieldtypeImageEffects::NASHVILLE, 1.2)->fx(FieldtypeImageEffects::FRAME, "polaroid01.png");
echo "<img id='photo' src='{$image->url}' alt='{$image->description}' width='{$image->width}' height='{$image->height}' /><br/>";

break;
}
include("./foot.inc");

FieldtypeImageEffects.module

<?php

class FieldtypeImageEffects extends FieldtypeImage implements Module
{
const GOTHAM = "fxGotham";
const TOASTER = "fxToaster";
const NASHVILLE = "fxNashville";
const FRAME = "fxFrame";
private $counter = 0;
public static function getModuleInfo()
{
return array
(
'title' => 'Image Effects',
'version' => 001,
'summary' => 'Add effects to images',
'href'	 => 'http://www.persommer.com',
);
}
public function init()
{
$this->addHook('Pageimage::fx', $this, 'fx');
}
public function fx(HookEvent $event)
{
$this->counter++;
if ($this->counter % 2)
{
switch ($event->arguments[0])
{
 case $this::GOTHAM:
 $event->return = $this->fxGotham($event);
 break;
 case $this::TOASTER:
 $event->return = $this->fxToaster($event);
 break;
 case $this::NASHVILLE:
 $event->return = $this->fxNashville($event);
 break;
 case $this::FRAME:
 $event->return = $this->fxFrame($event);
 break;
 default:
 $event->return = $event->object;
}
}
else
{
//echo $this->counter . " is even<br>";
}
}

private function fxGotham(HookEvent $event)
{
isset($event->arguments[1]) ? $intColorize = $event->arguments[1] : $intColorize = 20;
isset($event->arguments[2]) ? $floatGamma = $event->arguments[2] : $floatGamma = 0.5;
$prefix = $this->sanitizeFilename("_" . $event->arguments[0] . $intColorize . $floatGamma);
$img = $event->object;
$imgBaseNameNew = $this->appendToFileName($img->basename, $prefix);
$imgPath = $img->pagefiles->path . $img->basename;
$imgPathNew = $img->pagefiles->path . $imgBaseNameNew;
$this->execute("convert '$imgPath' -modulate 120,10,100 -fill '#222b6d' -colorize $intColorize -gamma $floatGamma -contrast -contrast '$imgPathNew'");
//!is_file($imgPathNew) ? $this->execute($strExe) : false;
$event->object->setFilename($imgBaseNameNew);
return $event->object;
// $pageimage = clone $event->object;
// $pageimage->setFilename($imgBaseNameNew);
// $pageimage->setOriginal($event->object);
// return $pageimage;
}

private function fxToaster(HookEvent $event)
{
isset($event->arguments[1]) ? $floatGamma = $event->arguments[1] : $floatGamma = 1.2;
$width = $event->object->width();
$height = $event->object->height();
$prefix = $this->sanitizeFilename("_" . $event->arguments[0] . $floatGamma);
$img = $event->object;
$imgBaseNameNew = $this->appendToFileName($img->basename, $prefix);
$imgPath = $img->pagefiles->path . $img->basename;
$imgPathNew = $img->pagefiles->path . $imgBaseNameNew;
$this->colortone($imgPath, $imgPathNew, '#330000', 100, 0);
$this->execute("convert '$imgPathNew' -modulate 150,80,100 -gamma $floatGamma -contrast -contrast '$imgPathNew'");
$this->vignette($imgPathNew, $imgPathNew, $width, $height, 'none', 'LavenderBlush3');
$this->vignette($imgPathNew, $imgPathNew, $width, $height, '#ff9966', 'none');
//!is_file($imgPathNew) ? $this->execute($strExe) : false;
$event->object->setFilename($imgBaseNameNew);
return $event->object;
}

private function fxNashville(HookEvent $event)
{
isset($event->arguments[1]) ? $floatGamma = $event->arguments[1] : $floatGamma = 1.2;
$width = $event->object->width();
$height = $event->object->height();
$prefix = $this->sanitizeFilename("_" . $event->arguments[0] . $floatGamma);
$img = $event->object;
$imgBaseNameNew = $this->appendToFileName($img->basename, $prefix);
$imgPath = $img->pagefiles->path . $img->basename;
$imgPathNew = $img->pagefiles->path . $imgBaseNameNew;
$this->colortone($imgPath, $imgPathNew, '#222b6d', 100, 0);
$this->colortone($imgPath, $imgPathNew, '#f7daae', 100, 1);
$this->execute("convert '$imgPathNew' -contrast -modulate 100,150,100 -auto-gamma '$imgPathNew'");
$this->frame($imgPathNew, $imgPathNew, "grunge01.png", $width, $height);
//!is_file($imgPathNew) ? $this->execute($strExe) : false;
$event->object->setFilename($imgBaseNameNew);
return $event->object;
}
private function fxFrame(HookEvent $event)
{
isset($event->arguments[1]) ? $frameFile = $event->arguments[1] : $framefile = "grunge01.png";
$width = $event->object->width();
$height = $event->object->height();
$path_parts = pathinfo($frameFile);
$prefix = $this->sanitizeFilename("_" . $path_parts['filename']);
$img = $event->object;
$imgBaseNameNew = $this->appendToFileName($img->basename, $prefix);
$imgPath = $img->pagefiles->path . $img->basename;
$imgPathNew = $img->pagefiles->path . $imgBaseNameNew;
$this->frame($imgPath, $imgPathNew, $frameFile, $width, $height);
//!is_file($imgPathNew) ? $this->execute($strExe) : false;
$event->object->setFilename($imgBaseNameNew);
return $event->object;
}
private function execute($command)
{
// echo("COMMAND INPUT: " . $command . "<br>");
$out = array();
putenv("PATH=" . $_ENV["PATH"] . ":/opt/local/bin");
exec($command . " 2>&1", $out);
// echo("COMMAND OUTPUT: " . print_r($out, true) . "<br>");
}
private function appendToFileName($filename, $prefix)
{
if ($prefix != "")
{
$path_parts = pathinfo($filename);
return $path_parts['filename'] . $prefix . "." . $path_parts['extension'];
}
return "errorprefix-" . $filename;
}
private function sanitizeFilename($strFilename)
{
$strFilename = str_replace(".", "", $strFilename);
return $strFilename;
}
public function colortone($input, $inputNew, $color, $level, $type = 0)
{
$args[0]	 = $level;
$args[1]	 = 100 - $level;
$negate	 = $type == 0 ? '-negate' : '';
$strColortone = "convert '$input' \( -clone 0 -fill '$color' -colorize 100% \) \( -clone 0 -colorspace gray $negate \) -compose blend -define compose:args=$args[0],$args[1] -composite '$inputNew'";
$this->execute($strColortone);
}
public function border($input, $color = 'black', $width = 20)
{
$this->execute("convert $input -bordercolor $color -border " . $width . "x" . $width . " $input");
}
public function frame($input, $inputNew, $frame, $width, $height)
{
$strFramePath = $_SERVER['DOCUMENT_ROOT'] . "/site/modules/frames/" . $frame;
//echo ("DOC LOCATION: " . $strFramePath . "<br>");
$strExe = "convert '$input' \( '$strFramePath' -resize {$width}x{$height}! -unsharp 1.5×1.0+1.5+0.02 \) -flatten '$inputNew'";
//echo ("EXE: " . $strExe . "<br>");
$this->execute($strExe);
}

public function vignette($input, $inputNew, $width, $height, $color_1 = 'none', $color_2 = 'black', $crop_factor = 1.5)
{
$crop_x = floor($width * $crop_factor);
$crop_y = floor($height * $crop_factor);
$strVignette = "convert \( '$input' \) \( -size {$crop_x}x{$crop_y} radial-gradient:$color_1-$color_2 -gravity center -crop {$width}x{$height}+0+0 +repage \) -compose multiply -flatten '$inputNew'";
$this->execute($strVignette);
}
}

Outputs the attached image

post-460-0-48237300-1344600810_thumb.png

  • Like 2
Link to comment
Share on other sites

Looking good! Thanks for posting this. I look forward to seeing this one in the modules directory.

I think the reason your fx() function is getting called twice is because there are likely two hooks being attached to Pageimage (as a result of your init being called twice). I'm guessing that you have two image fields using this fieldtype? So your init() is getting called twice and two hooks are getting added. If you are keeping this as a Fieldtype, you may want to move your counter to the init() function or use self::isHooked('Pageimage::fx()'); However, I'm also wondering if this module might be better on it's own rather than extending FieldtypeImage? At least, I don't currently see a reason for it to extend FieldtypeImage, though I may be missing something or you may have more plans yet. But am thinking that your function would only get called once if it weren't extending FieldtypeImage.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...