source: branches/2.3/admin/include/image.class.php @ 15576

Last change on this file since 15576 was 15576, checked in by patdenice, 12 years ago

merge r15575 from trunk to branch 23
bug:2647
External ImageMagick does not work anymore on 1and1 servers

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