Archive for October, 2007

Analyzing image properties

Imagick provides various ways to identify image properties. These properties include for example some basic attributes like width, height, format and some more advanced attributes like exif tags, profiles and colorspaces.

This example will demonstrate a few basic ways to read these attributes from an image. The image used in this example is from www.exif.org and can be found here http://exif.org/samples/fujifilm-dx10.jpg.

The support for getImageProperties and getImageProfiles was added in ImageMagick version 6.3.5-9 and Imagick version 2.0.0RC4.

PHP:
  1. <?php
  2.  
  3. $im = new Imagick( "fujifilm-dx10.jpg" );
  4. $identify = $im->identifyImage();
  5.  
  6. /* Ouput some basic attributes */
  7. echo '<b>Basic image properties:</b> <br />';
  8. echo 'Image geometry: ' , $identify['geometry']['width'] , 'x' , $identify['geometry']['height'] , '<br />';
  9. echo 'Image format: ' , $identify["format"] , '<br />';
  10. echo 'Image type: ' , $identify["type"] , '<br />';
  11. echo 'Image compression: ' , $identify["compression"] , '<br />';
  12. echo 'Image size: ' , $identify["fileSize"] , '<br />';
  13.  
  14. echo '<br /><br />';
  15.  
  16. echo '<b>All image properties:</b> <br />';
  17.  
  18. /* Loop trough image properties */
  19. foreach ( $im->getImageProperties() as $k => $v )
  20. {
  21.     echo $k , ' => ' , $v , '<br />';
  22. }
  23.  
  24. echo '<br /><br />';
  25.  
  26. echo '<b>All image profiles:</b> <br />';
  27. /* Same goes for properties  */
  28. foreach ( $im->getImageProfiles() as $k => $v )
  29. {
  30.     echo "Profile name: ", $k , " (size: ", strlen( $v ) ,") <br />";
  31. }
  32.  
  33. ?>

The output looks something like this:

Basic image properties:
Image geometry: 1024x768
Image format: JPEG (Joint Photographic Experts Group JFIF format)
Image type: TrueColor
Image compression: JPEG
Image size: 129.955kb


All image properties:

exif:ApertureValue => 41/10
exif:BrightnessValue => -27/10
exif:ColorSpace => 1
exif:ComponentsConfiguration => ...
exif:CompressedBitsPerPixel => 14/10
exif:Compression => 6
exif:Copyright => J P Bowen
exif:DateTime => 2001:04:12 20:33:14
exif:DateTimeDigitized => 2001:04:12 20:33:14
exif:DateTimeOriginal => 2001:04:12 20:33:14
exif:ExifImageLength => 768
exif:ExifImageWidth => 1024
exif:ExifOffset => 258
exif:ExifVersion => 0210
exif:ExposureBiasValue => 0/10
exif:ExposureProgram => 2
exif:FileSource => .
exif:Flash => 1
exif:FlashPixVersion => 0100
exif:FNumber => 42/10
exif:FocalLength => 58/10
exif:FocalPlaneResolutionUnit => 3
exif:FocalPlaneXResolution => 2151/1
exif:FocalPlaneYResolution => 2151/1
exif:InteroperabilityIndex => R98
exif:InteroperabilityOffset => 708
exif:InteroperabilityVersion => 0100
exif:ISOSpeedRatings => 150
exif:JPEGInterchangeFormat => 856
exif:JPEGInterchangeFormatLength => 10274
exif:Make => FUJIFILM
exif:MaxApertureValue => 41/10
exif:MeteringMode => 5
exif:Model => DX-10
exif:Orientation => 1
exif:ResolutionUnit => 2
exif:SceneType => .
exif:SensingMethod => 2
exif:ShutterSpeedValue => 66/10
exif:Software => Digital Camera DX-10 Ver1.00
exif:XResolution => 72/1
exif:YCbCrPositioning => 2
exif:YResolution => 72/1
jpeg:colorspace => 2
jpeg:sampling-factor => 2x1,1x1,1x1
Signature => 434d8554488bf9af5fc551adeba43e6d1d04ac36559a02357cf5df93db4b35c5

All image profiles:
Profile name: exif (size: 11136)

Imagick 2.0.1RC1 windows builds

I updated the dynamic Windows builds.

The latest version is 2.0.1RC1 (18.10.2007) and it is the last version I can currently provide.

Creating a simple line graph

This example demonstrates how Imagick can be used to create graphs. This graphing class itself is mostly a demonstrative example. There are also some hardcoded values in the graph (like the steps in the values on the left hand side) but it should not be hard to refine it into a more elegant graphing solution.

The graph is generated using the ImagickDraw drawing features. Since the ImagickLineGraph class extends the ImagickDraw class the instance of it can be passed directly to the Imagick::drawImage method.

The ImagickDraw class behaves a little like a ”drawing stack”; you can perform multiple operations on the object and then render the operations on a canvas. Operations like rotate and translate take affect on all drawing operations executed after them.

PHP:
  1. <?php
  2.  
  3. class ImagickLineGraph extends ImagickDraw
  4. {
  5.     /* Maximum value in the graph */
  6.     public $maxValue = 0;
  7.  
  8.     /* Graph data */
  9.     private $values;
  10.  
  11.     /* how large steps to take on the y axis values  */
  12.     private $yValueStep;
  13.     private $yStepPixels;
  14.  
  15.     /* The canvas to draw the graph on*/
  16.     private $canvas;
  17.  
  18.     /* Amount of steps on the left hand side */
  19.     private $amount;
  20.  
  21.     /* The size of the graph viewport */
  22.     private $viewPortSize;
  23.  
  24.     public function __construct( array $values )
  25.     {
  26.         /* Loop trough values and find the max value */
  27.         foreach ( $values as $key => $value )
  28.         {
  29.             if ( $value['value']> $this->maxValue )
  30.             {
  31.                 $this->maxValue = $value['value'];
  32.             }
  33.         }
  34.        
  35.         /* Set the values into a property */
  36.         $this->values = $values;
  37.  
  38.         /* How large steps to take on the left hand side values */
  39.         $this->yValueStep = 20;
  40.         $this->yStepPixels = 20;
  41.  
  42.         /* Create the horizontal lines */
  43.         $this->createHorizontalLines();
  44.  
  45.         /* Create the captions for the values */
  46.         $this->createCaptions();
  47.  
  48.         /* Create the polyline that represents the values */
  49.         $this->createGraphLine();
  50.     }
  51.  
  52.     private function createHorizontalLines()
  53.     {
  54.         /* The amount of vertical lines to draw */
  55.         $this->amount = ceil( $this->maxValue / $this->yValueStep );
  56.  
  57.         /* Use zero based origo but render it a bit lower */
  58.         $this->translate( 0, 20 );
  59.  
  60.         /* Set the viewport */
  61.         $this->viewPortSize = $this->amount * $this->yStepPixels;
  62.  
  63.         /* Loop trough the amount of values.. */
  64.         for ( $i = 0 ; $i <= $this->amount; $i++ )
  65.         {   
  66.             /* Y position on the image */
  67.             $yPos = $this->yStepPixels * $i;
  68.  
  69.             /* Draw a horizontal line*/
  70.             $this->line( 50, $yPos, 490, $yPos );   
  71.            
  72.             /* Create line for this value */
  73.             $this->annotation( 20, $this->viewPortSize - $yPos, $yPos );
  74.         }
  75.     }
  76.  
  77.     private function createCaptions()
  78.     {
  79.         /* Store the line coordinates to this array */
  80.         $this->polylineCoordinates = array();
  81.  
  82.         $xStep = 500 / count( $this->values );
  83.    
  84.         /* Temporary imagick object to use to get font metrics */
  85.         $im = new Imagick();
  86.  
  87.         /* Loop trough values.. */
  88.         foreach ( $this->values as $key => $value )
  89.         {
  90.             /* The position for the vertical lines */
  91.             $pos = 50 + ( $xStep * $key ) + 30;
  92.            
  93.             /* Query the text properties for the caption */
  94.             $properties = $im->queryFontMetrics( $this, $value['caption'] );
  95.            
  96.             /* Place the text in the middle of the vertical line */
  97.             $tPos = $pos - ( $properties['textWidth'] / 2 );
  98.            
  99.             /* Write the caption, one step below the actual graph */
  100.             $this->annotation( $tPos,
  101.                         ( $this->amount * $this->yStepPixels ) + $this->yStepPixels,
  102.                         $value['caption'] );
  103.  
  104.             /* Draw the vertical line for this value */
  105.             $this->line( $pos, $this->amount * $this->yStepPixels, $pos, 0 );
  106.            
  107.             /* Set the polyline coordinate for this line */
  108.             $this->polylineCoordinates[$key]['x'] = $pos ;
  109.  
  110.             /* And the Y coordinate */
  111.             $this->polylineCoordinates[$key]['y'] =
  112.                                 $this->viewPortSize - $value['value'];
  113.         }
  114.  
  115.     }
  116.  
  117.     function createGraphLine()
  118.     {
  119.         /* Use transparent fill, otherwise polyline is filled with black */
  120.         $this->setFillColor( new ImagickPixel( "transparent" ) );
  121.  
  122.         /* Stroke the polyline with red color*/
  123.         $this->setStrokeColor( new ImagickPixel( "red" ) );
  124.  
  125.         /* Antialias the line */
  126.         $this->setStrokeAntialias( true );
  127.  
  128.         /* Draw the polyline from the coordinates */
  129.         $this->polyLine( $this->polylineCoordinates );
  130.     }
  131. }
  132. /* values to show in the graph */
  133. $values = array (
  134.                 array( "caption" => "Random", "value" => 202.54 ),
  135.                 array( "caption" => "Random again", "value" => 34.2 ),
  136.                 array( "caption" => "Something", "value" => 59.5 ),
  137.                 array( "caption" => "Test", "value" => 210.34 ),
  138.                 array( "caption" => "Sporks", "value" => 110.34 ),
  139.                 );
  140. try
  141. {
  142.     /* Create a new graph */
  143.     $graph = new ImagickLineGraph( $values );
  144.  
  145.     /* Initialize the canvas */
  146.     $canvas = new Imagick();
  147.  
  148.     /* Create empty image */
  149.     $canvas->newImage( 500, $graph->maxValue + 150
  150.                           new ImagickPixel( "white" ) );
  151.    
  152.     /* Set the image format to png */
  153.     $canvas->setImageFormat( "png" );
  154.  
  155.     /* Render the graph on the canvas */
  156.     $canvas->drawImage( $graph );
  157.  
  158. }
  159. catch ( Exception $e )
  160. {
  161.     echo $e->getMessage();
  162.     die();
  163. }
  164. header( "Content-Type: image/png" );
  165. echo $canvas;
  166.  
  167. ?>

Here is the generated graph:

graph

And here is another graph generated using these values:

PHP:
  1. /* values to show in the graph */
  2. $values = array (
  3.                 array( "caption" => "ABC 123", "value" => 100 ),
  4.                 array( "caption" => "123 ABC", "value" => 200 ),
  5.                 array( "caption" => "TEST DATA", "value" => 30 ),
  6.                 array( "caption" => "SOMETHING", "value" => 50 ),
  7.                 );

P.S. If you're looking for a complete graphing suite take a look at
eZ Components Graph.

Choosing watermark color based on the background luminosity

Usually images are watermarked using a predefined color (let's say for example "white"). How well does this actually work when you're doing the watermark on a light or even a white image? The answer is simple: not very well.

This example illustrates how to use ImagickPixelIterator to get the average luminosity of the background and chosing text color according to it. I tested this algorithm very briefly and the results seemed positive. There's four example images posted at the end of this post to show why background matters when doing a watermark.

Here is the code:

PHP:
  1. <?php
  2.  
  3. /* Allowed images */
  4. $allowed = array( "strawberry.png", "Image32.png" );
  5. $img = ( isset( $_GET['img'] ) && in_array( $_GET['img'], $allowed ) )
  6.           ? $_GET['img'] : 'strawberry.png';
  7.  
  8. /* Read the image in. This image will be watermarked. */
  9. $image = new Imagick( $img  );
  10. $image->setImageFormat( "png" );
  11.  
  12. /* The text to write on the mark */
  13. $text = "www.valokuva.org";
  14.  
  15. /* This object will hold the font properties */
  16. $draw = new ImagickDraw();
  17.  
  18. /* Setting gravity to the center changes the origo
  19.         where annotation coordinates are relative to */
  20. $draw->setGravity( Imagick::GRAVITY_CENTER );
  21.  
  22. /* Use a custom truetype font */
  23. $draw->setFont( "./WCManoNegraBta.ttf" );
  24.  
  25. /* Set the font size */
  26. $draw->setFontSize( 26 );
  27.  
  28. /* Create a new imagick object */
  29. $im = new imagick();
  30.  
  31. /* Get the text properties */
  32. $properties = $im->queryFontMetrics( $draw, $text );
  33.  
  34. /* Region size for the watermark.
  35.         Add some extra space on the sides  */
  36. $watermark['w'] = intval( $properties["textWidth"] + 5 );
  37. $watermark['h'] = intval( $properties["textHeight"] + 5 );
  38.  
  39. /* Create a canvas using the font properties.
  40.         Add some extra space on width and height */
  41. $im->newImage( $watermark['w'], $watermark['h'],
  42.                     new ImagickPixel( "transparent" ) );
  43.  
  44. /* Get a region pixel iterator to get the pixels in the watermark area */
  45. $it = $image->getPixelRegionIterator( 0, 0, $watermark['w'], $watermark['h'] );
  46.  
  47. $luminosity = 0;
  48. $i = 0;
  49.  
  50. /* Loop trough rows */
  51. while( $row = $it->getNextIteratorRow() )
  52. {
  53.         /* Loop trough each column on the row */
  54.         foreach ( $row as $pixel )
  55.         {
  56.                 /* Get HSL values */
  57.                 $hsl = $pixel->getHSL();
  58.                 $luminosity += $hsl['luminosity'];
  59.                 $i++;
  60.         }
  61. }
  62.  
  63. /* If we are closer to white, then use black font and
  64.         the other way around */
  65. $textColor = ( ( $luminosity / $i )> 0.5 ) ?
  66.                         new ImagickPixel( "black" ) :
  67.                         new ImagickPixel( "white" );
  68.  
  69. /* Use the same color for the shadow */
  70. $draw->setFillColor( $textColor );
  71.  
  72. /* Use png format */
  73. $im->setImageFormat( "png" );
  74.  
  75. /* Annotate some text on the image */
  76. $im->annotateImage( $draw, 0, 0, 0, $text );
  77.  
  78. /* Clone the canvas to create the shadow */
  79. $watermark = $im->clone();
  80.  
  81. /* Set the image bg color to black. (The color of the shadow) */
  82. $watermark->setImageBackgroundColor( $textColor );
  83.  
  84. /* Create the shadow (You can tweak the parameters
  85.         to produce "different" kind of shadows */
  86. $watermark->shadowImage( 80, 2, 2, 2 );
  87.  
  88. /* Composite the text on the background */
  89. $watermark->compositeImage( $im, Imagick::COMPOSITE_OVER, 0, 0 );
  90.  
  91. /* Composite the watermark on the image to the top left corner */
  92. $image->compositeImage( $watermark, Imagick::COMPOSITE_OVER, 0, 0 );
  93.  
  94. /* Display the results */
  95. header( "Content-Type: image/png" );
  96. echo $image;
  97.  
  98. ?>

Here is strawberry.png with black watermark (this was chosen by the script):
black

And here is strawberry.png with white watermark:
white

Image32.png with white watermark (this was chosen by the script):
white

Image32.png with black watermark:
black

P.S. I opened a new page for the Windows Imagick builds. Seems like my dynamic builds work pretty well, so I will keep providing them thru this site.

Experimental Imagick/Windows builds updated

I updated the experimental Windows builds. You can find the new Q8 and Q16 versions in here

This build should fix the "E:" issue.

Requested examples: Animating GIF images

Today's example was requested by a user called Devo. This example illustrates making an animated GIF image by creating the frames from scratch. It's been so long since I've really drawn anything so I decided to use text as the base for this animation.

You could of course use existing images to simulate motion or even strip frames from videos to create GIF animations. The example is pretty straightforward but please post any questions you might have.

PHP:
  1. <?php
  2.  
  3. /* This object will hold the animation */
  4. $animation = new Imagick();
  5.  
  6. /* Set the format to gif. All created images will be gifs*/
  7. $animation->setFormat( "gif" );
  8.  
  9. /* Create a pixel to handle colors */
  10. $color = new ImagickPixel( "white" );
  11.  
  12. /* The first frame */
  13. $color->setColor( "white" );
  14.  
  15. /* This is the string we print on the image
  16.     Character by character */
  17. $string = "HELLO WORLD!";
  18.  
  19. /* Create a new ImagickDraw object and set the font
  20.     Text color defaults to black */
  21. $draw = new ImagickDraw();
  22.  
  23. /* You can get configured fonts with $im->queryFonts
  24.     or use /path/to/file.ttf */
  25. $draw->setFont( "Helvetica" );
  26.  
  27. /* Loop trough the string.. */
  28. for ( $i = 0; $i <= strlen( $string ); $i++ )
  29. {
  30.     /* Take some characters for this frame */
  31.     $part = substr( $string, 0, $i );
  32.  
  33.     /* Create a new frame */
  34.     $animation->newImage( 100, 50, $color );
  35.    
  36.     /* Annotate the characters on the image */
  37.     $animation->annotateImage( $draw, 10, 10, 0, $part );
  38.  
  39.     /* Set a little higher delay for the frame*/
  40.     $animation->setImageDelay( 30 );
  41. }
  42.  
  43. /* The last frame contains the same text with bold */
  44. $draw->setFont( "Helvetica-Bold" );
  45.  
  46. /* One more frame */
  47. $animation->newImage( 100, 50, $color );
  48.  
  49. /* Annotate the bold text on the image */
  50. $animation->annotateImage( $draw, 10, 10, 0, $string );
  51.  
  52. /* The bold can be visible a little longer time */
  53. $animation->setImageDelay( 60 );
  54.  
  55. /* Display the output as an animation.
  56.     Use getImagesBlob to get all images combined */
  57. header( "Content-Type: image/gif" );
  58. echo $animation->getImagesBlob();
  59.  
  60. ?>

This is how the resulting image looks like:

animation

Pretty thumbnails

Today's example is about making pretty thumbnails by combining a drop-in shadow with round corners.

The image is first scaled down to create a thumbnail. Imagick::thumbnailImage also strips all the associated profiles to make the image optimal to use in web applications.

The next step is to round the corners of the image. This is pretty trivial task but you should keep in mind that usually larger x and y rounding values give the optimal results when rounding the corners of a larger image. Generally it is pretty easy to find the suitable values by poking around with the parameters and making a formula out of it.

The final step is to create the drop-in shadow and composite the thumbnail over it. I use compositeImage in this example because Imagick::shadowImage creates only the shadow.

PHP:
  1. <?php
  2.  
  3. /* Read the image into the object */
  4. $im = new Imagick( 'strawberry.png' );
  5.  
  6. /* Make the image a little smaller, maintain aspect ratio */
  7. $im->thumbnailImage( 200, null );
  8.  
  9. /* Round corners, web 2.0! */
  10. $im->roundCorners( 5, 5 );
  11.  
  12. /* Clone the current object */
  13. $shadow = $im->clone();
  14.  
  15. /* Set image background color to black
  16.         (this is the color of the shadow) */
  17. $shadow->setImageBackgroundColor( new ImagickPixel( 'black' ) );
  18.  
  19. /* Create the shadow */
  20. $shadow->shadowImage( 80, 3, 5, 5 );
  21.  
  22. /* Imagick::shadowImage only creates the shadow.
  23.         That is why the original image is composited over it */
  24. $shadow->compositeImage( $im, Imagick::COMPOSITE_OVER, 0, 0 );
  25.  
  26. /* Display the image */
  27. header( "Content-Type: image/png" );
  28. echo $shadow;
  29.  
  30. ?>

Once again, the original image:

strawberry

And the results:

shadow

P.S. Leave your ideas for new examples here.

I had these polaroids laying on my desk..

Earlier I wrote about creating a polaroid effect. Now it's time to take this example a little further by creating an effect which looks like you have a stack of polaroids laying on your desk. The texture used as the background can be found here here.

The original images are normal JPEG images provided by Kevin Waterson and they can be found here.

Here is the code used to create the effect:

PHP:
  1. <?php
  2.  
  3. /* The object used as a canvas */
  4. $canvas = new imagick( "fake_wood_plastic_formica_4131428.JPG" );
  5. $canvas->adaptiveResizeImage( 300, 300 );
  6. $canvas->setImageFormat( "png" );
  7.  
  8. /* paths to the images */
  9. $paths = array( "chev7.jpg", "myfrog.jpg", "strawberry.jpg", "Image32.jpg" );
  10.  
  11. /* Create an empty ImagickDraw object
  12.     (Use the defaults for the polaroid) */
  13. $bg = new ImagickDraw();
  14.  
  15. /* Create a few random images */
  16. $images = new Imagick( $paths );
  17.  
  18. /* Loop trough images, overlay on canvas and remove the image */
  19. foreach ( $images as $key => $image )
  20. {
  21.     /* Thumbnail to 100x width and set background to black.
  22.             It looks like an drop-in shadow :) */
  23.     $image->thumbnailImage( 100, null );
  24.     $image->setImageBackgroundColor( new ImagickPixel( "black" ) );
  25.    
  26.     /* Use a random angle */
  27.     $angle = mt_rand( 1, 45 );
  28.  
  29.     if ( mt_rand( 1, 2 ) % 2 === 0 )
  30.     {   
  31.         $angle = $angle * -1;
  32.     }
  33.  
  34.     /* Create the polaroid */
  35.     $image->polaroidImage( $bg, $angle );
  36.  
  37.     /* Composite the polaroid over the canvas */
  38.  
  39.         /* Composite to a random location */
  40.     $canvas->compositeImage( $image, Imagick::COMPOSITE_OVER,
  41.                                mt_rand( 10, 150 ), mt_rand( 10, 150 ) );
  42.    
  43.     /* Free some resources */
  44.     $image->removeImage();
  45. }
  46.  
  47. /* Display the image */
  48. header( "Content-Type: image/png" );
  49. echo $canvas;
  50.  
  51. ?>

The resulting image looks something like this:

polaroid stack

Microbenchmarks: exec convert vs imagick

I know for a fact that quite a lot of people are using the convert command line utility from PHP using exec or similar execution function. Here is some (micro)benchmarks of using exec convert vs imagick extension. My test directory had 13 jpg images in it.

Here is the script i used with Imagick:

PHP:
  1. <?php
  2. foreach ( new imagick( glob( "/var/www/testimages/new/im/*.jpg" ) )  as $image )
  3. {
  4.     $image->thumbnailImage( 200, null );
  5.     $image->writeImage( "/tmp/th/" . basename( $image->getImageFilename() ) );
  6.     $image->removeImage();
  7. }
  8. ?>

And the script I used with convert:

PHP:
  1. <?php
  2.  
  3. foreach ( glob( "/var/www/testimages/new/im/*.jpg" ) as $image )
  4. {
  5.     exec( "convert -thumbnail 200 ${image} /tmp/th/". basename( $image ) );
  6. }
  7.  
  8. ?>

The scripts produce about identical result images. But the difference is the execution time:

Here is the execution time of the imagick script:

real 0m1.292s
user 0m1.010s
sys 0m0.260s

And the execution time of the convert script:

real 0m2.695s
user 0m2.160s
sys 0m0.510s

I used time php script.php to run these scripts and linux time utility to get the times. The times are ten run averages. It looks like if used from PHP the Imagick extension is about twice as fast as using the convert utility via exec.

THESE ARE MICROBENCHMARKS AND MIGHT NOT REFLECT A REAL WORLD SITUATION!

Enhancing an image

The most dramatic effect on images can be usually achieved with very simple tricks. The original image in this example has been scaled down a few times so you can see it is getting a bit blurred.

PHP:
  1. <?php
  2.  
  3. /* Read the image*/
  4. $im = new imagick( "chev7_orig.png" );
  5.  
  6. /* Add some contrast */
  7. $im->contrastImage( 1 );
  8.  
  9. /* Add some adaptive blur */
  10. $im->adaptiveBlurImage( 1, 1 );
  11.  
  12. /* Output the image */
  13. header( "Content-Type: image/png" );
  14. echo $im;
  15.  
  16. ?>

The original image (provided by Kevin Waterson)

orig

And here is the enhanced version:

contrast