1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4: */
4
5/**
6 * NetPBM implementation for Image_Transform package
7 *
8 * PHP versions 4 and 5
9 *
10 * LICENSE: This source file is subject to version 3.0 of the PHP license
11 * that is available through the world-wide-web at the following URI:
12 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
13 * the PHP License and are unable to obtain it through the web, please
14 * send a note to license@php.net so we can mail you a copy immediately.
15 *
16 * @category   Image
17 * @package    Image_Transform
18 * @author     Peter Bowyer <peter@mapledesign.co.uk>
19 * @author     Philippe Jausions <Philippe.Jausions@11abacus.com>
20 * @copyright  2002-2005 The PHP Group
21 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
22 * @version    CVS: $Id: NetPBM.php 236527 2007-05-28 16:36:09Z dufuz $
23 * @link       http://pear.php.net/package/Image_Transform
24 */
25
26require_once 'Image/Transform.php';
27require_once 'System.php';
28
29/**
30 * NetPBM implementation for Image_Transform package
31 *
32 * @category   Image
33 * @package    Image_Transform
34 * @subpackage Image_Transform_Driver_NetPBM
35 * @author     Peter Bowyer <peter@mapledesign.co.uk>
36 * @author     Philippe Jausions <Philippe.Jausions@11abacus.com>
37 * @copyright  2002-2005 The PHP Group
38 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
39 * @version    Release: @package_version@
40 * @link       http://pear.php.net/package/Image_Transform
41 * @link       http://netpbm.sourceforge.net/
42 */
43class Image_Transform_Driver_NetPBM extends Image_Transform
44{
45    /**
46     * associative array commands to be executed
47     * @var array
48     */
49    var $command = array();
50
51    /**
52     * Class Constructor
53     */
54    function Image_Transform_Driver_NetPBM()
55    {
56        $this->__construct();
57
58    } // End function Image_NetPBM
59
60    /**
61     * Class Constructor
62     */
63    function __construct()
64    {
65        if (!defined('IMAGE_TRANSFORM_NETPBM_PATH')) {
66            $path = dirname(System::which('pnmscale'))
67                    . DIRECTORY_SEPARATOR;
68            define('IMAGE_TRANSFORM_NETPBM_PATH', $path);
69        }
70        if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 'pnmscale'
71                             . ((OS_WINDOWS) ? '.exe' : ''))) {
72            $this->isError(PEAR::raiseError('Couldn\'t find "pnmscale" binary',
73                IMAGE_TRANSFORM_ERROR_UNSUPPORTED));
74        }
75    } // End function Image_NetPBM
76
77    /**
78     * Load image
79     *
80     * @param string filename
81     * @return bool|PEAR_Error TRUE or a PEAR_Error object on error
82     * @access public
83     */
84    function load($image)
85    {
86        $this->image = $image;
87        $result = $this->_get_image_details($image);
88        if (PEAR::isError($result)) {
89            return $result;
90        }
91        return true;
92
93    } // End load
94
95    /**
96     * Resize the image.
97     *
98     * @access private
99     *
100     * @param int   $new_x   New width
101     * @param int   $new_y   New height
102     * @param mixed $options Optional parameters
103     *
104     * @return true on success or PEAR Error object on error
105     * @see PEAR::isError()
106     */
107    function _resize($new_x, $new_y, $options = null)
108    {
109        // there's no technical reason why resize can't be called multiple
110        // times...it's just silly to do so
111        $scaleMethod = $this->_getOption('scaleMethod', $options, 'smooth');
112        switch ($scaleMethod) {
113            case 'pixel':
114                $scale_x = $new_x / $this->img_x;
115                if ($scale_x == $new_y / $this->img_x
116                    && $scale_x > 1
117                    && floor($scale_x) == $scale_x) {
118                    if (System::which(IMAGE_TRANSFORM_NETPBM_PATH .
119                                           'pnmenlarge'
120                                           . ((OS_WINDOWS) ? '.exe' : ''))) {
121                        $this->command[] = $this->_prepare_cmd(
122                            IMAGE_TRANSFORM_NETPBM_PATH,
123                            'pnmenlarge',
124                            $scale_x);
125                    } else {
126                        return PEAR::raiseError('Couldn\'t find "pnmenlarge" binary',
127                            IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
128                    }
129                } else {
130                    $this->command[] = $this->_prepare_cmd(
131                        IMAGE_TRANSFORM_NETPBM_PATH,
132                        'pnmscale',
133                        '-nomix -width ' . ((int) $new_x)
134                            . ' -height ' . ((int) $new_y));
135                }
136                break;
137
138            case 'smooth':
139            default:
140                $this->command[] = $this->_prepare_cmd(
141                    IMAGE_TRANSFORM_NETPBM_PATH,
142                    'pnmscale',
143                    '-width ' . ((int) $new_x) . ' -height '
144                        . ((int) $new_y));
145                // Smooth things if scaling by a factor more than 3
146                // (see pnmscale man page)
147                if ($new_x / $this->img_x > 3
148                    || $new_y / $this->img_y > 3) {
149                    if (System::which(IMAGE_TRANSFORM_NETPBM_PATH .
150                                           'pnmsmooth' . ((OS_WINDOWS) ? '.exe' : ''))) {
151                        $this->command[] = $this->_prepare_cmd(
152                            IMAGE_TRANSFORM_NETPBM_PATH,
153                            'pnmsmooth');
154                    } else {
155                        return PEAR::raiseError('Couldn\'t find "pnmsmooth" binary',
156                            IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
157                    }
158                }
159        } // End [SWITCH]
160
161        $this->_set_new_x($new_x);
162        $this->_set_new_y($new_y);
163        return true;
164
165    } // End resize
166
167    /**
168     * Rotates the image
169     *
170     * @param int $angle The angle to rotate the image through
171     * @param array $options
172     * @return bool|PEAR_Error TRUE on success, PEAR_Error object on error
173     */
174    function rotate($angle, $options = null)
175    {
176        if (!($angle == $this->_rotation_angle($angle))) {
177            // No rotation needed
178            return true;
179        }
180
181        // For pnmrotate, we want to limit rotations from -45 to +45 degrees
182        // even if acceptable range is -90 to +90 (see pnmrotate man page)
183        // Bring image to that range by using pamflip
184        if ($angle > 45 && $angle < 315) {
185            if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
186                                   'pamflip' . ((OS_WINDOWS) ? '.exe' : ''))) {
187                return PEAR::raiseError('Couldn\'t find "pamflip" binary',
188                    IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
189            }
190
191            $quarters = floor(ceil($angle / 45) / 2);
192            $this->command[] = $this->_prepare_cmd(
193                IMAGE_TRANSFORM_NETPBM_PATH,
194                'pamflip',
195                '-rotate' . (360 - $quarters * 90));
196            $angle -= $quarters * 90;
197        }
198
199        if ($angle != 0) {
200            if ($angle > 45) {
201                $angle -= 360;
202            }
203
204            if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
205                                   'pnmrotate' . ((OS_WINDOWS) ? '.exe' : ''))) {
206                return PEAR::raiseError('Couldn\'t find "pnmrotate" binary',
207                    IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
208            }
209
210            $bgcolor = $this->_getColor('canvasColor', $options,
211                                            array(255, 255, 255));
212            $bgcolor = $this->colorarray2colorhex($bgcolor);
213
214            $scaleMethod = $this->_getOption('scaleMethod', $options, 'smooth');
215            if ($scaleMethod != 'pixel') {
216                $this->command[] = $this->_prepare_cmd(
217                    IMAGE_TRANSFORM_NETPBM_PATH,
218                    'pnmrotate',
219                    '-background=' . $bgcolor . ' -' . (float) $angle);
220            } else {
221                $this->command[] = $this->_prepare_cmd(
222                    IMAGE_TRANSFORM_NETPBM_PATH,
223                    'pnmrotate',
224                    '-background=' . $bgcolor . ' -noantialias -' . (float) $angle);
225            }
226        }
227        return true;
228    } // End rotate
229
230    /**
231     * Crop an image
232     *
233     * @param int $width Cropped image width
234     * @param int $height Cropped image height
235     * @param int $x positive X-coordinate to crop at
236     * @param int $y positive Y-coordinate to crop at
237     *
238     * @return mixed TRUE or a PEAR error object on error
239     * @todo keep track of the new cropped size
240     **/
241    function crop($width, $height, $x = 0, $y = 0)
242    {
243        // Sanity check
244        if (!$this->intersects($width, $height, $x, $y)) {
245            return PEAR::raiseError('Nothing to crop', IMAGE_TRANSFORM_ERROR_OUTOFBOUND);
246        }
247        if ($x != 0 || $y != 0
248            || $width != $this->img_x
249            || $height != $this->img_y) {
250            if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
251                                   'pnmcut' . ((OS_WINDOWS) ? '.exe' : ''))) {
252                return PEAR::raiseError('Couldn\'t find "pnmcut" binary',
253                    IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
254            }
255
256            $this->command[] = $this->_prepare_cmd(
257                IMAGE_TRANSFORM_NETPBM_PATH,
258                'pnmcut',
259                '-left ' . ((int) $x)
260                    . ' -top ' . ((int) $y)
261                    . ' -width ' . ((int) $width)
262                    . ' -height ' . ((int) $height));
263        }
264        return true;
265    } // End crop
266
267    /**
268     * Adjust the image gamma
269     *
270     * @param float $outputgamma
271     *
272     * @return mixed TRUE or a PEAR error object on error
273     */
274    function gamma($outputgamma = 1.0) {
275        if ($outputgamme != 1.0) {
276            if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
277                                   'pnmgamma' . ((OS_WINDOWS) ? '.exe' : ''))) {
278                return PEAR::raiseError('Couldn\'t find "pnmgamma" binary',
279                    IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
280            }
281            $this->command[] = $this->_prepare_cmd(
282                IMAGE_TRANSFORM_NETPBM_PATH,
283                'pnmgamma',
284                (float) $outputgamma);
285        }
286        return true;
287    }
288
289    /**
290     * Vertical mirroring
291     *
292     * @see mirror()
293     * @return TRUE or PEAR Error object on error
294     **/
295    function flip()
296    {
297        if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
298                               'pamflip' . ((OS_WINDOWS) ? '.exe' : ''))) {
299            return PEAR::raiseError('Couldn\'t find "pamflip" binary',
300                IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
301        }
302        $this->command[] = $this->_prepare_cmd(
303            IMAGE_TRANSFORM_NETPBM_PATH,
304            'pamflip',
305            '-topbottom');
306        return true;
307    }
308
309    /**
310     * Horizontal mirroring
311     *
312     * @see flip()
313     * @return TRUE or PEAR Error object on error
314     **/
315    function mirror()
316    {
317        if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
318                               'pamflip' . ((OS_WINDOWS) ? '.exe' : ''))) {
319            return PEAR::raiseError('Couldn\'t find "pamflip" binary',
320                IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
321        }
322        $this->command[] = $this->_prepare_cmd(
323            IMAGE_TRANSFORM_NETPBM_PATH,
324            'pamflip',
325            '-leftright');
326        return true;
327    }
328
329    /**
330     * Converts an image into greyscale colors
331     *
332     * @access public
333     * @return mixed TRUE or a PEAR error object on error
334     **/
335    function greyscale()
336    {
337        if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
338                               'ppmtopgm' . ((OS_WINDOWS) ? '.exe' : ''))) {
339            return PEAR::raiseError('Couldn\'t find "ppmtopgm" binary',
340                IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
341        }
342        $this->command[] = $this->_prepare_cmd(
343            IMAGE_TRANSFORM_NETPBM_PATH,
344            'ppmtopgm');
345        return true;
346    }
347
348    /**
349     * adds text to an image
350     *
351     * @param   array   options     Array contains options
352     *             array(
353     *                  'text'          // The string to draw
354     *                  'x'             // Horizontal position
355     *                  'y'             // Vertical Position
356     *                  'color'         // Font color
357     *                  'font'          // Font to be used
358     *                  'size'          // Size of the fonts in pixel
359     *                  'resize_first'  // Tell if the image has to be resized
360     *                                  // before drawing the text
361     *                   )
362     *
363     * @return void
364     */
365    function addText($params)
366    {
367        if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH .
368                               'ppmlabel' . ((OS_WINDOWS) ? '.exe' : ''))) {
369            return PEAR::raiseError('Couldn\'t find "ppmlabel" binary',
370                IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
371        }
372
373        // we ignore 'resize_first' since the more logical approach would be
374        // for the user to just call $this->_resize() _first_ ;)
375        extract(array_merge($this->_get_default_text_params(), $params));
376
377        $options = array('colorFont' => $color);
378        $color = $this->_getColor('colorFont', $options, array(0, 0, 0));
379        $color = $this->colorarray2colorhex($color);
380
381        $this->command[] = $this->_prepare_cmd(
382            IMAGE_TRANSFORM_NETPBM_PATH,
383            'ppmlabel',
384            '-angle ' . ((int) $angle)
385                . ' -colour ' . escapeshellarg($color)
386                . ' -size ' . ((float) $size)
387                . ' -x ' . ((int) $x)
388                . ' -y ' . ((int) ($y + $size))
389                . ' -text ' . escapeshellarg($text));
390
391    } // End addText
392
393    /**
394     * Image_Transform_Driver_NetPBM::_postProcess()
395     *
396     * @param $type
397     * @param $quality
398     * @return string A chain of shell command
399     * @link http://netpbm.sourceforge.net/doc/directory.html
400     */
401    function _postProcess($type, $quality)
402    {
403        array_unshift($this->command, $this->_prepare_cmd(
404            IMAGE_TRANSFORM_NETPBM_PATH,
405            strtolower($this->type) . 'topnm',
406            escapeshellarg($this->image)));
407        $arg = '';
408        $type = strtolower($type);
409        $program = '';
410        switch ($type) {
411            // ppmto* converters
412            case 'gif':
413                if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . 'ppmquant'
414                                    . ((OS_WINDOWS) ? '.exe' : ''))) {
415                    return PEAR::raiseError('Couldn\'t find "ppmquant" binary',
416                        IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
417                }
418                $this->command[] = $this->_prepare_cmd(
419                    IMAGE_TRANSFORM_NETPBM_PATH,
420                    'ppmquant',
421                    256);
422            case 'acad':
423            case 'bmp':
424            case 'eyuv':
425            case 'ilbm':
426            case 'leaf':
427            case 'lj':
428            case 'mitsu':
429            case 'mpeg':
430            case 'neo':
431            case 'pcx':
432            case 'pi1':
433            case 'pict':
434            case 'pj':
435            case 'pjxl':
436            case 'puzz':
437            case 'sixel':
438            case 'tga':
439            case 'uil':
440            case 'xpm':
441            case 'yuv':
442                $program = 'ppmto' . $type;
443                break;
444
445            // Windows icon
446            case 'winicon':
447            case 'ico':
448                $type = 'winicon';
449                $program = 'ppmto' . $type;
450                break;
451
452            // pbmto* converters
453            case 'ascii':
454            case 'text':
455            case 'txt':
456                $type = 'ascii';
457            case 'atk':
458            case 'bbubg':
459            case 'epsi':
460            case 'epson':
461            case 'escp2':
462            case 'icon':    // Sun icon
463            case 'gem':
464            case 'go':
465            case 'lj':
466            case 'ln03':
467            case 'lps':
468            case 'macp':
469            case 'mda':
470            case 'mgr':
471            case 'pi3':
472            case 'pk':
473            case 'plot':
474            case 'ptx':
475            case 'wbp':
476            case 'xbm':
477            case 'x10bm':
478            case 'ybm':
479            case 'zinc':
480            case '10x':
481                $program = 'pbmto' . $type;
482                break;
483
484            // pamto* converters
485            case 'jpc':
486                $type = 'jpeg2k';
487            case 'html':
488            case 'pfm':
489            case 'tga':
490                $program = 'pamto' . $type;
491                break;
492
493            // pnmto* converters
494            case 'jpc':
495                $type = 'jpeg2k';
496                break;
497            case 'wfa':
498                $type = 'fiasco';
499                break;
500            case 'jpg':
501                $type = 'jpeg';
502            case 'jpeg':
503                $arg = '--quality=' . $quality;
504            case 'jbig':
505            case 'fits':
506            case 'palm':
507            case 'pclxl':
508            case 'png':
509            case 'ps':
510            case 'rast':
511            case 'rle':
512            case 'sgi':
513            case 'sir':
514            case 'tiff':
515            case 'xwd':
516                $program = 'pnmto' . $type;
517                break;
518
519        } // switch
520
521        if ($program == '') {
522            $program = 'pnmto' . $type;
523        }
524
525        if (!System::which(IMAGE_TRANSFORM_NETPBM_PATH . $program
526                            . ((OS_WINDOWS) ? '.exe' : ''))) {
527            return PEAR::raiseError("Couldn't find \"$program\" binary",
528                IMAGE_TRANSFORM_ERROR_UNSUPPORTED);
529        }
530        $this->command[] = $this->_prepare_cmd(
531            IMAGE_TRANSFORM_NETPBM_PATH,
532            $program);
533        return implode('|', $this->command);
534    }
535
536    /**
537     * Save the image file
538     *
539     * @param $filename string the name of the file to write to
540     * @param string $type (jpeg,png...);
541     * @param int $quality 75
542     * @return TRUE or PEAR Error object on error
543     */
544    function save($filename, $type = null, $quality = 75)
545    {
546        $type    = (is_null($type)) ? $this->type : $type;
547        $options = array();
548        if (!is_null($quality)) {
549            $options['quality'] = $quality;
550        }
551        $quality = $this->_getOption('quality', $options, $quality);
552
553        $nullDevice = (OS_WINDOWS) ? 'nul' : '/dev/null';
554
555        $cmd = $this->_postProcess($type, $quality) . '> "' . $filename . '"';
556        exec($cmd . ' 2>  ' . $nullDevice, $res, $exit);
557        if (!$this->keep_settings_on_save) {
558            $this->free();
559        }
560
561        return ($exit == 0) ? true : PEAR::raiseError(implode('. ', $res),
562            IMAGE_TRANSFORM_ERROR_IO);
563    } // End save
564
565    /**
566     * Display image without saving and lose changes
567     *
568     * @param string $type (jpeg,png...);
569     * @param int $quality 75
570     * @return TRUE or PEAR Error object on error
571     */
572    function display($type = null, $quality = null)
573    {
574        $type    = (is_null($type)) ? $this->type : $type;
575        $options = array();
576        if (!is_null($quality)) {
577            $options['quality'] = $quality;
578        }
579        $quality = $this->_getOption('quality', $options, 75);
580
581        header('Content-type: ' . $this->getMimeType($type));
582        $cmd = $this->_postProcess($type, $quality);
583        passthru($cmd . ' 2>&1');
584        if (!$this->keep_settings_on_save) {
585            $this->free();
586        }
587
588        return true;
589    }
590
591    /**
592     * Destroy image handle
593     *
594     * @return void
595     */
596    function free()
597    {
598        $this->command = array();
599    }
600} // End class ImageIM