source: trunk/admin/include/image.class.php @ 10641

Last change on this file since 10641 was 10641, checked in by patdenice, 13 years ago

feature:2284
Create a class to manipulate images.

File size: 16.9 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2011 Piwigo Team                  http://piwigo.org |
6// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
7// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
8// +-----------------------------------------------------------------------+
9// | This program is free software; you can redistribute it and/or modify  |
10// | it under the terms of the GNU General Public License as published by  |
11// | the Free Software Foundation                                          |
12// |                                                                       |
13// | This program is distributed in the hope that it will be useful, but   |
14// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
15// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
16// | General Public License for more details.                              |
17// |                                                                       |
18// | You should have received a copy of the GNU General Public License     |
19// | along with this program; if not, write to the Free Software           |
20// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
21// | USA.                                                                  |
22// +-----------------------------------------------------------------------+
23
24// +-----------------------------------------------------------------------+
25// |                           Image Interface                             |
26// +-----------------------------------------------------------------------+
27
28// Define all needed methods for image class
29interface imageInterface
30{
31  function get_width();
32
33  function get_height();
34
35  function set_compression_quality($quality);
36
37  function crop($width, $height, $x, $y);
38
39  function strip();
40
41  function rotate($rotation);
42
43  function resize($width, $height);
44
45  function write($destination_filepath);
46}
47
48// +-----------------------------------------------------------------------+
49// |                          Main Image Class                             |
50// +-----------------------------------------------------------------------+
51
52class pwg_image
53{
54  var $image;
55  var $library = '';
56  var $source_filepath = '';
57
58  function __construct($source_filepath, $library=null)
59  {
60    global $conf;
61
62    $this->source_filepath = $source_filepath;
63
64    trigger_action('load_image_library', array(&$this) );
65
66    if (is_object($this->image))
67    {
68      return; // A plugin may have load its own library
69    }
70
71    $extension = strtolower(get_extension($source_filepath));
72
73    if (!in_array($extension, array('jpg', 'jpeg', 'png', 'gif')))
74    {
75      die('[Image] unsupported file extension');
76    }
77
78    if (is_null($library))
79    {
80      $library = $conf['image_library'];
81    }
82
83    // Choose image library
84    switch (strtolower($library))
85    {
86      case 'auto':
87      case 'imagick':
88        if ($extension != 'gif' and self::is_imagick())
89        {
90          $this->library = 'imagick';
91          break;
92        }
93      case 'ext_imagick':
94        if ($extension != 'gif' and self::is_ext_imagick())
95        {
96          $this->library = 'ext_imagick';
97          break;
98        }
99      case 'gd':
100        if (self::is_gd())
101        {
102          $this->library = 'gd';
103          break;
104        }
105      default:
106        if ($library != 'auto')
107        {
108          // Requested library not available. Try another library
109          return self::__construct($source_filepath, 'auto');
110        }
111        die('No image library available on your server.');
112    }
113
114    $class = 'image_'.$this->library;
115    $this->image = new $class($source_filepath);
116  }
117
118  // Unknow methods will be redirected to image object
119  function __call($method, $arguments)
120  {
121    return call_user_func_array(array($this->image, $method), $arguments);
122  }
123
124  // Piwigo resize function
125  function pwg_resize($destination_filepath, $max_width, $max_height, $quality, $automatic_rotation=true, $strip_metadata=false, $crop=false, $follow_orientation=true)
126  {
127    $starttime = get_moment();
128   
129    // width/height
130    $source_width  = $this->image->get_width();
131    $source_height = $this->image->get_height();
132
133    // Crop image
134    if ($crop)
135    {
136      $x = 0;
137      $y = 0;
138
139      if ($source_width < $source_height and $follow_orientation)
140      {
141        list($max_width, $max_height) = array($max_height, $max_width);
142      }
143
144      $img_ratio = $source_width / $source_height;
145      $dest_ratio = $max_width / $max_height;
146
147      if($dest_ratio > $img_ratio)
148      {
149        $destHeight = round($source_width * $max_height / $max_width);
150        $y = round(($source_height - $destHeight) / 2 );
151        $source_height = $destHeight;
152      }
153      elseif ($dest_ratio < $img_ratio)
154      {
155        $destWidth = round($source_height * $max_width / $max_height);
156        $x = round(($source_width - $destWidth) / 2 );
157        $source_width = $destWidth;
158      }
159
160      $this->image->crop($source_width, $source_height, $x, $y);
161    }
162
163    $rotation = null;
164    if ($automatic_rotation)
165    {
166      $rotation = self::get_rotation_angle($this->source_filepath);
167    }
168    $resize_dimensions = self::get_resize_dimensions($source_width, $source_height, $max_width, $max_height, $rotation);
169
170    // testing on height is useless in theory: if width is unchanged, there
171    // should be no resize, because width/height ratio is not modified.
172    if ($resize_dimensions['width'] == $source_width and $resize_dimensions['height'] == $source_height)
173    {
174      // the image doesn't need any resize! We just copy it to the destination
175      copy($this->source_filepath, $destination_filepath);
176      return $this->get_resize_result($destination_filepath, $resize_dimensions['width'], $resize_dimensions['height'], $starttime);
177    }
178
179    $this->image->set_compression_quality($quality);
180   
181    if ($strip_metadata)
182    {
183      // we save a few kilobytes. For example a thumbnail with metadata weights 25KB, without metadata 7KB.
184      $this->image->strip();
185    }
186   
187    $this->image->resize($resize_dimensions['width'], $resize_dimensions['height']);
188
189    if (isset($rotation))
190    {
191      $this->image->rotate($rotation);
192    }
193
194    $this->image->write($destination_filepath);
195
196    // everything should be OK if we are here!
197    return $this->get_resize_result($destination_filepath, $resize_dimensions['width'], $resize_dimensions['height'], $starttime);
198  }
199
200  static function get_resize_dimensions($width, $height, $max_width, $max_height, $rotation=null)
201  {
202    $rotate_for_dimensions = false;
203    if (isset($rotation) and in_array(abs($rotation), array(90, 270)))
204    {
205      $rotate_for_dimensions = true;
206    }
207
208    if ($rotate_for_dimensions)
209    {
210      list($width, $height) = array($height, $width);
211    }
212   
213    $ratio_width  = $width / $max_width;
214    $ratio_height = $height / $max_height;
215    $destination_width = $width; 
216    $destination_height = $height;
217   
218    // maximal size exceeded ?
219    if ($ratio_width > 1 or $ratio_height > 1)
220    {
221      if ($ratio_width < $ratio_height)
222      { 
223        $destination_width = round($width / $ratio_height);
224        $destination_height = $max_height;
225      }
226      else
227      { 
228        $destination_width = $max_width; 
229        $destination_height = round($height / $ratio_width);
230      }
231    }
232
233    if ($rotate_for_dimensions)
234    {
235      list($destination_width, $destination_height) = array($destination_height, $destination_width);
236    }
237
238    return array(
239      'width' => $destination_width,
240      'height'=> $destination_height,
241      );
242  }
243
244  static function get_rotation_angle($source_filepath)
245  {
246    list($width, $height, $type) = getimagesize($source_filepath);
247    if (IMAGETYPE_JPEG != $type)
248    {
249      return null;
250    }
251   
252    if (!function_exists('exif_read_data'))
253    {
254      return null;
255    }
256
257    $rotation = null;
258   
259    $exif = exif_read_data($source_filepath);
260   
261    if (isset($exif['Orientation']) and preg_match('/^\s*(\d)/', $exif['Orientation'], $matches))
262    {
263      $orientation = $matches[1];
264      if (in_array($orientation, array(3, 4)))
265      {
266        $rotation = 180;
267      }
268      elseif (in_array($orientation, array(5, 6)))
269      {
270        $rotation = 270;
271      }
272      elseif (in_array($orientation, array(7, 8)))
273      {
274        $rotation = 90;
275      }
276    }
277
278    return $rotation;
279  }
280
281  private function get_resize_result($destination_filepath, $width, $height, $time)
282  {
283    return array(
284      'source'      => $this->source_filepath,
285      'destination' => $destination_filepath,
286      'width'       => $width,
287      'height'      => $height,
288      'size'        => floor(filesize($destination_filepath) / 1024).' KB',
289      'time'          => number_format((get_moment() - $time) * 1000, 2, '.', ' ').' ms',
290      'library'     => $this->library,
291    );
292  }
293
294  static function is_imagick()
295  {
296    return extension_loaded('imagick');
297  }
298
299  static function is_ext_imagick()
300  {
301    global $conf;
302
303    if (!function_exists('exec'))
304    {
305      return false;
306    }
307    @exec($conf['ext_imagick_dir'].'convert', $returnarray, $returnvalue);
308    if (!$returnvalue and !empty($returnarray[0]) and preg_match('/ImageMagick/i', $returnarray[0]))
309    {
310      return true;
311    }
312    return false;
313  }
314
315  static function is_gd()
316  {
317    return function_exists('gd_info');
318  }
319
320  function destroy()
321  {
322    if (method_exists($this->image, 'destroy'))
323    {
324      return $this->image->destroy();
325    }
326    return true;
327  }
328}
329
330// +-----------------------------------------------------------------------+
331// |                   Class for Imagick extension                         |
332// +-----------------------------------------------------------------------+
333
334class image_imagick implements imageInterface
335{
336  var $image;
337
338  function __construct($source_filepath)
339  {
340    // A bug cause that Imagick class can not be extended
341    $this->image = new Imagick($source_filepath);
342  }
343
344  function get_width()
345  {
346    return $this->image->getImageWidth();
347  }
348
349  function get_height()
350  {
351    return $this->image->getImageHeight();
352  }
353
354  function set_compression_quality($quality)
355  {
356    return $this->image->setImageCompressionQuality($quality);
357  }
358
359  function crop($width, $height, $x, $y)
360  {
361    return $this->image->cropImage($width, $height, $x, $y);
362  }
363
364  function strip()
365  {
366    return $this->image->stripImage();
367  }
368
369  function rotate($rotation)
370  {
371    $this->image->rotateImage(new ImagickPixel(), -$rotation);
372    $this->image->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
373    return true;
374  }
375
376  function resize($width, $height)
377  {
378    $this->image->setInterlaceScheme(Imagick::INTERLACE_LINE);
379    return $this->image->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 0.9);
380  }
381
382  function write($destination_filepath)
383  {
384    return $this->image->writeImage($destination_filepath);
385  }
386}
387
388// +-----------------------------------------------------------------------+
389// |            Class for ImageMagick external installation                |
390// +-----------------------------------------------------------------------+
391
392class image_ext_imagick implements imageInterface
393{
394  var $imagickdir = '';
395  var $source_filepath = '';
396  var $image_data = array();
397  var $commands = array();
398
399  function __construct($source_filepath, $imagickdir='')
400  {
401    $this->source_filepath = $source_filepath;
402    $this->imagickdir = $imagickdir;
403
404    $command = $imagickdir."identify -verbose ".realpath($source_filepath);
405    @exec($command, $returnarray, $returnvalue);
406    if($returnvalue)
407    {
408      die("[External ImageMagick] Corrupt image");
409    }
410
411    foreach($returnarray as $value)
412    {
413      $arr = explode(':', $value, 2);
414      if (count($arr) == 2)
415      {
416        $this->image_data[trim($arr[0])] = trim($arr[1]);
417      }
418    }
419  }
420
421  function add_command($command, $params=null)
422  {
423    $this->commands[$command] = $params;
424  }
425
426  function get_width()
427  {
428    preg_match('#^(\d+)x#', $this->image_data['Geometry'], $match);
429    return isset($match[1]) ? $match[1] : false;
430  }
431
432  function get_height()
433  {
434    preg_match('#^\d+x(\d+)(?:\+|$)#', $this->image_data['Geometry'], $match);
435    return isset($match[1]) ? $match[1] : false;
436  }
437
438  function crop($width, $height, $x, $y)
439  {
440    $this->add_command('crop', $width.'x'.$height.'+'.$x.'+'.$y);
441    return true;
442  }
443
444  function strip()
445  {
446    $this->add_command('strip');
447    return true;
448  }
449
450  function rotate($rotation)
451  {
452    $this->add_command('rotate', -$rotation);
453    $this->add_command('orient', 'top-left');
454    return true;
455  }
456
457  function set_compression_quality($quality)
458  {
459    $this->add_command('quality', $quality);
460    return true;
461  }
462
463  function resize($width, $height)
464  {
465    $this->add_command('interlace', 'line');
466    $this->add_command('filter', 'Lanczos');
467    $this->add_command('resize', $width.'x'.$height.'!');
468    return true;
469  }
470
471  function write($destination_filepath)
472  {
473    $exec = $this->imagickdir.'convert';
474    $exec .= ' '.realpath($this->source_filepath);
475
476    foreach ($this->commands as $command => $params)
477    {
478      $exec .= ' -'.$command;
479      if (!empty($params))
480      {
481        $exec .= ' '.$params;
482      }
483    }
484
485    $dest = pathinfo($destination_filepath);
486    $exec .= ' '.realpath($dest['dirname']).'/'.$dest['basename'];
487    @exec($exec, $returnarray, $returnvalue);
488    return !$returnvalue;
489  }
490}
491
492// +-----------------------------------------------------------------------+
493// |                       Class for GD library                            |
494// +-----------------------------------------------------------------------+
495
496class image_gd implements imageInterface
497{
498  var $image;
499  var $quality = 95;
500
501  function __construct($source_filepath)
502  {
503    $gd_info = gd_info();
504    $extension = strtolower(get_extension($source_filepath));
505
506    if (in_array($extension, array('jpg', 'jpeg')))
507    {
508      $this->image = imagecreatefromjpeg($source_filepath);
509    }
510    else if ($extension == 'png')
511    {
512      $this->image = imagecreatefrompng($source_filepath);
513    }
514    elseif ($extension == 'gif' and $gd_info['GIF Read Support'] and $gd_info['GIF Create Support'])
515    {
516      $this->image = imagecreatefromgif($source_filepath);
517    }
518    else
519    {
520      die('[Image GD] unsupported file extension');
521    }
522  }
523
524  function get_width()
525  {
526    return imagesx($this->image);
527  }
528
529  function get_height()
530  {
531    return imagesy($this->image);
532  }
533
534  function crop($width, $height, $x, $y)
535  {
536    $dest = imagecreatetruecolor($width, $height);
537
538    imagealphablending($dest, false);
539    imagesavealpha($dest, true);
540    if (function_exists('imageantialias'))
541    {
542      imageantialias($dest, true);
543    }
544
545    $result = imagecopymerge($dest, $this->image, 0, 0, $x, $y, $width, $height, 100);
546
547    if ($result !== false)
548    {
549      imagedestroy($this->image);
550      $this->image = $dest;
551    }
552    else
553    {
554      imagedestroy($dest);
555    }
556    return $result;
557  }
558
559  function strip()
560  {
561    return true;
562  }
563
564  function rotate($rotation)
565  {
566    $dest = imagerotate($this->image, $rotation, 0);
567    imagedestroy($this->image);
568    $this->image = $dest;
569    return true;
570  }
571
572  function set_compression_quality($quality)
573  {
574    $this->quality = $quality;
575    return true;
576  }
577
578  function resize($width, $height)
579  {
580    $dest = imagecreatetruecolor($width, $height);
581
582    imagealphablending($dest, false);
583    imagesavealpha($dest, true);
584    if (function_exists('imageantialias'))
585    {
586      imageantialias($dest, true);
587    }
588
589    $result = imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $width, $height, $this->get_width(), $this->get_height());
590
591    if ($result !== false)
592    {
593      imagedestroy($this->image);
594      $this->image = $dest;
595    }
596    else
597    {
598      imagedestroy($dest);
599    }
600    return $result;
601  }
602
603  function write($destination_filepath)
604  {
605    $extension = strtolower(get_extension($destination_filepath));
606
607    if ($extension == 'png')
608    {
609      imagepng($this->image, $destination_filepath);
610    }
611    elseif ($extension == 'gif')
612    {
613      imagegif($this->image, $destination_filepath);
614    }
615    else
616    {
617      imagejpeg($this->image, $destination_filepath, $this->quality);
618    }
619  }
620
621  function destroy()
622  {
623    imagedestroy($this->image);
624  }
625}
626
627?>
Note: See TracBrowser for help on using the repository browser.