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

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

feature:2284
Rename $confimage_library into $confgraphics_library
Display library used in admin intro page.

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