Jump to content

Core ImageManipulation


horst
 Share

Recommended Posts

Hi, I wish to have a core ImageManipulationClass like the ImageSizer but not only for resizing and cropping images. ;)
 
It should provide these easy to use functionality like with the ImageSizer and additionally a step to step image manipulation whereas the user / module author is not restricted anyhow.
 
The basic image manipulation methods should be included, like:

  •         im_flip
  •         im_rotate
  •         im_crop
  •         im_crop_auto
  •         im_resize
  •         im_sharpen
  •         im_stepResize
     

If someone want to do something more fancy or magic, he/she should not to have to reenvent the basics again. He/She just should start with the class by opening the imagefile and create a GD-object, use some basic methods, and at any point get the GD-IM-Reference out, do his fancy magic with it and put it back to the class to use its basics to finalise the file:

  •         im_get_im()
  •         im_set_im( &$im )

I have started to write something for this. I tried to be as close to the ImageSizer as possible, but some differences will be there.
Before I've started with it I have done some tests with sharpening, rotating and others.

First I've tried to create a module that hooks into ImageSizer, but have figured out that this wouldn't solve most things what I imagine one want to do with images.

I have read the code of ImageSizer very carefully and there are allready very good inprovements in it from the community here. Ok, Ryan has written it and done the most work of all, so tribute to him ^-^, -  adamkiss, interrobang, mrx, teppo, u-nikos have contributed the improvements to it: ^-^.

I have looked into apeisas Thumbnail-Module and tried adding functionality for sharpening to it. It think he would have liked if there was a CoreImageManipulation class once when he has written the module. Also Soma actually work on a very cool Module where I really would like to see a link/button or some links/buttons for every image that just let you do some manipulations/corrections to them.  <hint, hint ;-)>
 

Any thoughts or Meinungen are welcome.
 

---
 

EDIT: there is actually a modified ImageSizer class with autoRotation & sharpening available for testing: http://processwire.com/talk/topic/3278-core-imagemanipulation/#entry32284


---

 
Here are an overview of what allready is in, (I post only properties and method names, not the method bodies):
 

class ImageManipulation extends Wire {

	// information of source imagefile

 		/**
		* Filename ImageSourcefile
		*/
		protected $filename;

		/**
		* Extension ImageSourcefile
		*/
		protected $extension;

		/**
		* Type of image ( 1 = gif | 2 = jpg | 3 = png )
		*/
		protected $imagetype;

		/**
		* Information about the image (width/height) and more 
		*/
		protected $image = array();

		/**
		* Was the given image modified?
		*/
		protected $modified = false;


	// default options for manipulations

		/**
		* Image quality setting, 1..100
		*/
		protected $quality = 90;

		/**
		* Allow images to be upscaled / enlarged?
		*/
		protected $upscaling = true;

		/**
		* Allow images to be cropped to achieve necessary dimension? If so, what direction?
		*
		* Possible values: northwest, north, northeast, west, center, east, southwest, south, southeast
		* 	or TRUE to crop to center, or FALSE to disable cropping.
		* Default is: TRUE
		*/
		protected $cropping = true;

		/**
		* Should a optional Auto-Rotation be performed if EXIF-Orientation-Flag is available?
		*/
		protected $auto_orientation = true;

		/**
		* the default sharpening mode
		*
		* @var array with custom pattern or a string: 'soft' | 'medium' | 'strong' | 'multistep'
		*/
		protected $sharpening = 'medium';

		/**
		* if extended imageinfo should be retrieved: number of Channels, Bits/per Channel, Colorspace
		*/
		protected $extended_imageinfo = false;

		/**
		* Extension / Format for resulting Imagefile (default is same as ImageSourcefile-Extension)
		*/
		protected $outputformat;

 		/**
		* Filename ImageTargetfile if $outputformat is different than InputImage (default is same as ImageSourcefile)
		*/
		protected $targetfilename;


	// other properties

		/**
		* Directions that cropping may gravitate towards
		*
		* Beyond those included below, TRUE represents center and FALSE represents no cropping.
		*/
		static protected $croppingValues = array();

		/**
		* Supported image types (@teppo)
		*/
		protected $supportedImageTypes = array();

		protected $option_names = array();

		private $property_names;


	// Methods to set and get Properties

		/**
		 * Here you can specify multiple options as Array, whereas with the
		 * single set* functions you can specify single options
		 *
		 * @param array $options May contain key-value pairs for any valid Options-Propertyname
		 * @return this
		 */
		public function setOptions(array $options)

		public function setQuality($value)
		public function setUpscaling($value)
		public function setCropping($value)
		public function setAuto_orientation($value)
		public function setSharpening($value)
		public function setTargetFilename($value)
		public function setOutputformat($value)

		/**
		 * Return an array of the current options
		 */
		public function getOptions()

		/**
		* makes protected and private class-properties accessible in ReadOnly mode
		*
		* example:   $x = $class->propertyname;
		*/
		public function __get( $property_name )



	// Construct & Destruct the ImageManipulator for a single image

		public function __construct( $filename, $options=array() )

		public function __destruct()

		public function im_release()


	// read image informations, basic and extended

		protected function loadImageInfo()

		private function extendedInfo_gif(&$a)

		private function extendedInfo_jpg(&$a)

		private function extendedInfo_png(&$a)


	// helper functions

		/**
		* check file exists and read / write access
		*
		* @param string $filename
		* @param boolean $readonly
		* @return boolean
		*/
		private function check_diskfile( $filename, $readonly=false )

		/**
		* helper, reads a 4-byte integer from file
		*/
		private function freadint(&$f)



	// the IM's (ImageManipulation Methods)

		private $im_dib_dst = null;     // is the output for every intermediate im-method and optional a check-out for the im!
		private $im_dib_tmp = array();  // holds all intermediate im references

		private function get_next_im( $w=true, $h=null )

		public static function is_resource_gd( &$var )

		public function im_get_im()

		public function im_set_im( &$im )

		public function im_flip( $vertical=false )

		public function im_rotate( $degree, $background_color=0 )

		public function im_crop( $pos_x, $pos_y, $width, $height )

		public function im_crop_auto( $direction, $width, $height )

		public function im_resize( $dst_width=0, $dst_height=0, $auto_sharpen=true, $sharpen_mode='medium' )

		public function im_sharpen( $mode='medium' )

		public function im_stepResize( $dst_width=0, $dst_height=0 )



	// static oneLiner Methods that can be called only with a filename passed to them

		public static function file_get_exif_orientation( $filename, $return_correctionArray=false )

		public static function file_jpeg_auto_rotation( $filename, $quality=95 )

}
Edited by horst
  • Like 11
Link to comment
Share on other sites

Hi horst, great initiative. I'm not sure how far in you are with writing the code but maybe you can take a look at some already existing libs if you run into problems:

Hi SiNNut, many thanks for the links. There are interesting things to see like using the GD with layers like Photoshop or Gimp.

But the GD-related code is allready done. What isn't achieved now is the PW integration. I would like to have it that we can use it like: $img->open($options)->im_rotate(90)->im_resize(700, 0, true, 'medium')->save(94)->url . Don't know if this is possible and if yes don't know how, so thats a big challenge for me. :grin:

Actually I can do it in the basic PHP way:

$manipulator = new ImageManipulation($filename,$options);

$manipulator->im_rotate(90);

$manipulator->im_resize(700, 0, true, 'medium');

$manipulator->save(94);

Edit / Add:

Or with the static OneLiner-Methods you actually can check / correct a imagefile:

ImageManipulation::file_jpeg_auto_rotation( $filename, 95 );

This checks the EXIF-Orientation-Flag of an JPEG-Image, corrects it if necessary and save it with quality 94.

Is useful for uploaded Images.

Link to comment
Share on other sites

Shouldn't the calls be camel case imRotate() ?

Oh, yes it should be. (Haven't realised that) Thanks!

Edit: haven't seen at first (because of 'Tunnelblick'):

Great work and thinking.

Thank you, Soma!

Edited by horst
Link to comment
Share on other sites

...

What isn't achieved now is the PW integration. I would like to have it that we can use it like: $img->open($options)->im_rotate(90)->im_resize(700, 0, true, 'medium')->save(94)->url . Don't know if this is possible and if yes don't know how, so thats a big challenge for me. :grin:

...

Hmm, I have checked how this works with the ImageSizer and have seen that there are more core files invoked (at least Pageimage.php, but maybe also ProcessPageEditImageSelect.module). Initially I have thought with writing an ImageManipulation class it could be the central place for all the images stuff.

This way all further improvements on it gets automatically into all modules and templates that work with it. Also I've thought we only need to rewrite the ImageSizer a little bit so that he includes the basics from there too. Hhm, - that's my intial thoughts.

But, I don't want go into that depth with (other) core files. I'm only interested in getting sharpened images that keeps their original metadata (IPTC) within all its variations.

Therefor I decided to implement AutoRotation, Sharpening and IPTC-Support into the ImageSizer-class, (better: into the resize-method)

You should extend in site/config.php the $config->imageSizerOptions Array like:

$config->imageSizerOptions = array(
	'autoRotation' => true,        // true | false
	'sharpening' => 'medium',      // soft | medium | strong
	'upscaling' => true,
	'cropping' => true,
	'quality' => 90
);

As you know, you set global options there, but you also can set options per/image when calling $img->width, $img->height, or $img->size by passing an options-array as second/third param. The options-array needs only key=>value pairs included with what you want to override the defaultsettings and the global-config-settings.

Now it's a bit like an ImageManipulatorLITE :D

You may paste this code into a template and call a page that has at least one viewable image:

if($page->images->count()>0)
{
	$size = 240;
	$options = array(  // these are the default settings
		'autoRotation' => true,
		'sharpening' => 'medium',
		'upscaling' => true,
		'cropping' => true,
		'quality' => 90
	);
	$quality = array(20,55,90);
	$sharpen = array('soft','medium','strong');

    echo "<p>Quality: 20 - 55 - 90</p>\n";
    foreach($quality as $q)
    	echo '<img src="'.$page->images->first->width($size++, array('quality'=>$q))->url.'" alt="" />';

    echo "<p>Sharpening: soft - medium - strong</p>\n";
    foreach($sharpen as $s)
    	echo '<img src="'.$page->images->first->width($size++, array('sharpening'=>$s))->url.'" alt="" />';
}

 Here is the modified ImageSizer.php for all of you who want to test it out. If you do so and find some issues / bugs / glitches, it would be nice to post it here.

(removed zip attachement because it is obsolete)

EDIT: I definitely will finish the ImageManipulation class too. But only in the basic PHP way. Actually i have to put in PW-Errorhandling and to do some testing.

---[technical information]---------------

  what was modified  

  added properties:

  • protected $autoRotation
  • protected $sharpening
  • protected $optionNames

added methods:

  •     public function setAutoRotation($value)
  •     public function setSharpening($value)
  •     protected function imRotate()
  •     protected function imFlip()
  •     protected function imSharpen()
  •     protected function checkOrientation()
  •     protected function iptcPrepareData()
  •     protected function iptcMakeTag()

modified methods:

  •     protected function loadImageInfo()
  •     public function setOptions(array $options)
  •     public function getOptions()
  •     public function __construct()
  •     public function ___resize()
Edited by horst
removed zip attachement because it is obsolete
  • Like 2
Link to comment
Share on other sites

Thanks for your work here Horst, it looks great! I look forward to going through in more detail so that I can understand it all and then integrate into the dev branch. It makes sense to have this built-in. 

  • Like 4
Link to comment
Share on other sites

  • 2 weeks later...

When working on ImageEditor (formerly named ImageManipulation) I come across some points that I want to [ discuss | get advice | point out ]
-----
1) The ImageEditor now can be used like this:

$myImage = $page->images->first;
$imedit = new ImageEditor($myImage);
if($imedit->imRotate(90)->imResize(300,0,false)->imSharpen('soft')->imSave()) {
echo '<img src="'.$myImage->url.'" />';
}


It needs a PageImage as source. This can be an original Image, but than it needs a targetFilename, because it doesn't allow to overwrite the original Image! When it gets an ImageVariation it can save to that filename (overwrite).
 
You also may save to another image format, e.g. your source is JPEG and you apply some alpha masking vodoo to it, you want to save it as PNG. This can be done by define the optional option outputFormat.
 
-----
2) I think there is a need for a additional Image Method: $image->nameAppendix() 
or something like that because there is allready a naming convention for images
( originalimagename.300x0.jpg or originalimagename.0x300.jpg ) that could be extended to something like
originalimagename . 300x0 (userdefinedAppendixWrappedBySpecialChars) .jpg
 
The $image->nameAppendix('userDefinedAppendix') should set or exchange this filename part. This would be of great help to keep imagenames consistent across the site.
 
I have read a thread where Ryan has said that when trying do delete all Variations of a site that cannot be done by iterating through $images and use ->removeVariations() for large sites. On the other hand when users or module authors can name the images without be restricted that may become a mess with imagenames.
 
At least there should be defined a image naming convention with respect to UserNameAppendices, - than I can implement it into the ImageEditor.
 
-----
3) I need some advice on how to handle Errors & Warnings, e.g. when not all needed options are passed, or they aren't valid. Should we silently use defaultValues where available or should we use that and write to ErrorLogFile. Don't know how fast a Logfile can grow, or even more terrible, - when should I throw an Exception? When gets an admin email invoked?
I can upload the code or provide otherwise more informations on that. Please ask / tell what is needed.
 
-----
4) Here are some examples / possibilities of usage:

$myImage = $page->images->first;
$imedit = new ImageEditor($myImage);

output of $imedit->image :
array(10)
{
["type"] string(3) "jpg"
["imageType"] int(2)
["mimetype"] string(10) "image/jpeg"
["width"] int(1800)
["height"] int(1196)
["landscape"] bool(true)
["ratio"] float(1.505016722408)
["bits"] int(8)
["channels"] int(3)
["colspace"] string(9) "DeviceRGB"
}


$myImage = $page->images->first;
$imedit = new ImageEditor($myImage, $options);

//second param is optional $options array like with ImagesSizer

/**
* valid options, identical to that 5 from (new) ImageSizer,
* width 3 additions: targetFilename and outputFormat
*
* - targetFilename is needed because we don't want to overwrite the original Imagefile
* there are two exceptions:
* 1) if you have allready created a variation and have passed that to the ImageEditor
* than a targetFilename is not needed.
* (we check reference '$image->original' to know about that)
* 2) using the static function fileAutoRotation, because this is mainly implemented
* for correcting an original imagefile on upload! So a file gets uploaded only one
* time and only then we may apply rotation to the original image.
*
* - outputFormat is optional and only should give ability to
* import for example a JPEG, do something with fancy masking and
* save it to PNG-Format with alpha transparency.
*
* - bgcolor may be used when wrapping borders around an image or create Thumbnails like Slides
* (squares with landscapes or portraits in it)
*
*/
protected $optionNames = array(
'autoRotation',
'upscaling',
'cropping',
'quality',
'sharpening',
'bgcolor',
'targetFilename',
'outputFormat'
);


// you may also at any point get out the memoryimage, apply your own stuff like a SepiaFilter and put it back:

$myImage = $page->images->first;
$imedit = new ImageEditor($myImage);

$im = $imedit->imRotate(90)->imResize(300,0,false)->imSharpen('soft')->imGetIm();

  imagefilter($im,IMG_FILTER_GRAYSCALE);
  imagefilter($im,IMG_FILTER_COLORIZE,100,50,0);

$imedit->imSetIm($im)->imSave();


// the OneLiner

// this one should get invoked when an image is uploaded
   public static function fileAutoRotation( $filename, $quality=95 )

// and this one is a present for apeisa, (but pssst,    don't tell him)
   public static function fileThumbnailModule( $filename, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality=95, $sharpening='medium' )
  • Like 1
Link to comment
Share on other sites

Image manipulation is something that has been dealt with many times, please, please use an existing library for this.

Imagine is the most downloaded and starred lib for this on packagist so they must be doing something correctly.

Truth of the day:

- WordPress is one of the most downloaded CMS so they must be doing something correctly.

- specially for the germans: BILD-zeitung is the most selled Newspaper, so they must be doing something correctly.

Yes of corse they will do something correctly, but what does it tell me about the quality / usability of it?

---

Petsagouris, I have had two quick views to this lib, and it is like it is with most others of them:

1) sharpening - https://github.com/avalanche123/Imagine/blob/develop/lib/Imagine/Gd/Effects.php

they have 1 pattern! That's that pattern that is into all libs that have sharpening and of corse you can find it all over the web. (php.net usernotes, stackoverflow, etc, etc).

With the new addition to ImageSizer we now have 3 patterns (soft, medium, strong). (and they are well tested over a set of 30 images, from lowkey to highkey covering not only most common scenes). Maybe that in a year the other libs have them included too, because they have found them here in PW. who knows

And, last but not least: sharpening isn't a _effect_, sharpening is essentially image processing. And there exists more than 20 different common methods to apply sharpening to an image. One of that is added to the ImageEditors sharpening method, it's called multistep-sharpening! So with it we now have 4 different patterns available, and you also may pass an array to the method with your own pattern, if the available do not suite your needs in some cases. (with this the advanced users have much more than 4 patterns that they can apply to an image)

I don't know who are the people what have done all those libraries. They may be good or very good developers or enthusiats and they may have done really good work or awesome work, - but one thing is fact: I'm sure there are no photographers with them. ;-)

(Don't go to a butcher when you want to buy a bread!)

2) there should be only the basics in it, in ImageSizer and in ImageEditor. But these should be robust and with a high comfort for users who don't know much about image-processing but may expect an equal behave like they know from PW.

And with ImageSizer this is allready reached because of

... very good improvements in it from the community here. Ok, Ryan has written it and done the most work of all, so tribute to him ^-^,

adamkiss, interrobang, mrx, teppo, u-nikos have contributed the improvements to it: ^-^.

The only thing what was really needed and wasn't in there was a good sharpening. There is all in what is needed to do a perfect job with resizing, - regardless of image formats, filenames, transparencies, and what ever.

But not more!

And ImageEditor should be like that with what could be usefull for most users handling images in an CMS, and not more! At first it should be a tool for module authors who want to deal with images, so that they haven't to go and take a sharpening method from somewhere in the web without really knowing what it does / how it works. They should be able to simply resize an image, and sharpening is applied automatically. (They even have not to know about the fact that images could/should be sharpened when resized, - like I can store some Text into a PW-Page without have to know how a DB works on that).

  • Like 3
Link to comment
Share on other sites

Great idea's here. Would be nice to have the ability to add a watermark to an image. At the moment i'm using phpThumb class to achieve this.

Hi Raymond, yes watermarking makes sense together with a CMS.

  • Like 1
Link to comment
Share on other sites

@horst relax, you are gonna have a heart attack. Sharpening matters to you, I get it.

There are two possible explanation why Image manipulation libraries have basic sharpening.

a) 90% of the people will never notice the difference between soft, medium, strong sharpening. Many of them may even not be satisfied with the sharpening that is done by the library because they might have chosen the wrong one.

b) The web isn't about image quality that much, 72dpi doesn't let it matter.

Recognizing that this is an area of your expertise I'd recommend you help the guys on that library out so PHP in general gets an advantage (and you get attributed for it too).

Link to comment
Share on other sites

smiley_emoticons_blog-messias.gif

 

 

EDIT: (deleted because of yellow card)

 

EDIT 2: put backup in, - because referee was to quick with yellow card

 

------->> original postContent

 

...

There are two possible explanation why Image manipulation libraries have basic sharpening.

...

 

@Petsagouris:

 

I think there is only one possible explanation: the libraries are baken by butchers ^_^

 

...

and that's with the sharpening was meant as an example. Read the source of ImageSizer and you will see the other improvements from the community. Than look to the posts of that contributors and, for shure, you should get a clue of the quality level on which they [ work / act / post / contribute ].

 

 

U-Turn of the day:

 

first I should go and take their lib, not write an own | U-Turn | now I should go and fix their's.   Hey, you are joking!   :biggrin:

Edited by horst
Link to comment
Share on other sites

I think you both have valid points from your perspective.

I think it comes down to that we're having the current ImageSizer getting some enhancement and trying to keep improving it and not having a dependency on an other lib which would need to be maintained also.

The matter image and all its facets is such a huge subject in web development it's hard to sometimes argue about it. It's great to have some movement here happen and we all appreciate the work you do horst. It's also nice to suggest a different approach but it somehow sparked unwanted reactions because theres some good free time spent here. It's like telling someone after building his dreamhouse why not buy a house thats already built.

  • Like 3
Link to comment
Share on other sites

smiley_emoticons_blog-messias.gif

EDIT: (deleted because of yellow card)

That wasn't a good idea, you didn't say anything really wrong there, but now it looks like you did...

And the referee was too quick with the yellow card :)

  • Like 1
Link to comment
Share on other sites

  • 3 months later...

currently I'm back working on the ImageManipulator. I've put it into a module that extends PageImage. It integrates nicely into PWs workflow.
There are only a few things that I have to solve before I can release it:

  •  finishing and testing the watermark-methods
  •  write documentation and some examples
  •  provide support for imageVariations, namely: removeVariations() ;)

with point 3, I already have a method that can be called when in PIM-mode (PageImageManipulator-mode) which unlink all PIM-created files, but I think it would be more comfortable if a user calls one of the PageImage-methods getVariations() or removeVariations() if the PIM-created variations are included in the collections.
 

EDIT: in the meantime, we have had a release already: http://processwire.com/talk/topic/4264-release-page-image-manipulator/

-----

here are a screenshot of my current Testpage

pw_screenshot_pim_exampleThumbs.jpg

Edited by horst
  • Like 7
Link to comment
Share on other sites

  • 6 months later...

I have a problem with autorotation and resizing. I have a jpg, which is rotated. When i call $img->size(200,100) for the rotated image the result of is width=100, height=200.

As a workaround I currently chain 2 size call to get the correct result ($img->size(10000,10000,array('upscaling' => false, 'cropping' => false))->size(200,100)).

I am using the latest dev from PW (2.4.1). Is there something special with this jpeg? I don't have other rotated images here, so I can't test myself.

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...