Overview

Packages

  • Image
  • None

Classes

  • Horde_Image
  • Horde_Image_Base
  • Horde_Image_Effect
  • Horde_Image_Effect_Border
  • Horde_Image_Effect_Gd_DropShadow
  • Horde_Image_Effect_Gd_RoundCorners
  • Horde_Image_Effect_Gd_TextWatermark
  • Horde_Image_Effect_Gd_Unsharpmask
  • Horde_Image_Effect_Im_Border
  • Horde_Image_Effect_Im_CenterCrop
  • Horde_Image_Effect_Im_Composite
  • Horde_Image_Effect_Im_DropShadow
  • Horde_Image_Effect_Im_LiquidResize
  • Horde_Image_Effect_Im_PhotoStack
  • Horde_Image_Effect_Im_PolaroidImage
  • Horde_Image_Effect_Im_RoundCorners
  • Horde_Image_Effect_Im_TextWatermark
  • Horde_Image_Effect_Im_Unsharpmask
  • Horde_Image_Effect_Imagick_Border
  • Horde_Image_Effect_Imagick_CenterCrop
  • Horde_Image_Effect_Imagick_Composite
  • Horde_Image_Effect_Imagick_DropShadow
  • Horde_Image_Effect_Imagick_LiquidResize
  • Horde_Image_Effect_Imagick_PhotoStack
  • Horde_Image_Effect_Imagick_PolaroidImage
  • Horde_Image_Effect_Imagick_RoundCorners
  • Horde_Image_Effect_Imagick_SmartCrop
  • Horde_Image_Effect_Imagick_TextWatermark
  • Horde_Image_Effect_Imagick_Unsharpmask
  • Horde_Image_Exception
  • Horde_Image_Exif
  • Horde_Image_Exif_Base
  • Horde_Image_Exif_Bundled
  • Horde_Image_Exif_Exiftool
  • Horde_Image_Exif_Parser_Base
  • Horde_Image_Exif_Php
  • Horde_Image_Gd
  • Horde_Image_Im
  • Horde_Image_Imagick
  • Horde_Image_Png
  • Horde_Image_Svg
  • Horde_Image_Swf
  • Horde_Image_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * This class implements the Horde_Image:: API for ImageMagick.
  4:  *
  5:  * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (LGPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  9:  *
 10:  * @author  Chuck Hagenbuch <chuck@horde.org>
 11:  * @author  Mike Cochrane <mike@graftonhall.co.nz>
 12:  * @author  Michael J. Rubinsky <mrubinsk@horde.org>
 13:  * @package Image
 14:  */
 15: class Horde_Image_Im extends Horde_Image_Base
 16: {
 17:     /**
 18:      * Capabilites of this driver.
 19:      *
 20:      * @var array
 21:      */
 22:     protected $_capabilities = array(
 23:         'resize',
 24:         'crop',
 25:         'rotate',
 26:         'grayscale',
 27:         'flip',
 28:         'mirror',
 29:         'sepia',
 30:         'canvas',
 31:         'multipage',
 32:         'pdf');
 33: 
 34:     /**
 35:      * Operations to be performed before the source filename is specified on the
 36:      * command line.
 37:      *
 38:      * @var array
 39:      */
 40:     protected $_operations = array();
 41: 
 42:     /**
 43:      * Operations to be added after the source filename is specified on the
 44:      * command line.
 45:      *
 46:      * @var array
 47:      */
 48:     protected $_postSrcOperations = array();
 49: 
 50:     /**
 51:      * An array of temporary filenames that need to be unlinked at the end of
 52:      * processing. Use addFileToClean() from client code (Effects) to add files
 53:      * to this array.
 54:      *
 55:      * @var array
 56:      */
 57:     protected $_toClean = array();
 58: 
 59:     /**
 60:      * Path to the convert binary
 61:      *
 62:      * @string
 63:      */
 64:     protected $_convert = '';
 65: 
 66:     /**
 67:      * Path to the identify binary
 68:      *
 69:      * @string
 70:      */
 71:     protected $_identify;
 72: 
 73:     /**
 74:      * Cache the number of image pages
 75:      *
 76:      * @var integer
 77:      */
 78:     private $_pages;
 79: 
 80:     /**
 81:      * Track current page for the iterator
 82:      *
 83:      * @var integer
 84:      */
 85:     private $_currentPage = 0;
 86: 
 87:     /**
 88:      * Constructor.
 89:      *
 90:      * @see Horde_Image_Base::_construct
 91:      */
 92:     public function __construct($params, $context = array())
 93:     {
 94:         parent::__construct($params, $context);
 95: 
 96:         if (empty($context['convert'])) {
 97:             throw new InvalidArgumentException('A path to the convert binary is required.');
 98:         }
 99:         $this->_convert = $context['convert'];
100: 
101:         if (!empty($context['identify'])) {
102:             $this->_identify = $context['identify'];
103:         }
104:         if (!empty($params['filename'])) {
105:             $this->loadFile($params['filename']);
106:         } elseif (!empty($params['data'])) {
107:             $this->loadString($params['data']);
108:         } else {
109:             $cmd = "-size {$this->_width}x{$this->_height} xc:{$this->_background} +profile \"*\" {$this->_type}:__FILEOUT__";
110:             $this->executeConvertCmd($cmd);
111:         }
112:     }
113: 
114:     /**
115:      * Publically visible raw method.
116:      *
117:      * @see self::_raw
118:      */
119:     public function raw($convert = false)
120:     {
121:         return $this->_raw($convert);
122:     }
123: 
124:     /**
125:      * Returns the raw data for this image.
126:      *
127:      * @param boolean $convert  If true, the image data will be returned in the
128:      *                          target format, even if no other image operations
129:      *                          are present, otherwise, if no operations are
130:      *                          present, the current raw data is returned
131:      *                          unmodified.
132:      *
133:      * @return string  The raw image data.
134:      */
135:     private function _raw($convert = false, $index = 0, $preserve_data = false)
136:     {
137:         if (empty($this->_data) ||
138:             // If there are no operations, and we already have data, don't
139:             // bother writing out files, just return the current data.
140:             (!$convert &&
141:              !count($this->_operations) &&
142:              !count($this->_postSrcOperations))) {
143:             return $this->_data;
144:         }
145: 
146:         $tmpin = $this->toFile($this->_data);
147: 
148:         // Perform convert command if needed
149:         if (count($this->_operations) || count($this->_postSrcOperations) || $convert) {
150:             $tmpout = Horde_Util::getTempFile('img', false, $this->_tmpdir);
151:             $command = $this->_convert . ' ' . implode(' ', $this->_operations)
152:                 . ' "' . $tmpin . '"\'[' . $index . ']\' '
153:                 . implode(' ', $this->_postSrcOperations)
154:                 . ' +profile "*" ' . $this->_type . ':"' . $tmpout . '" 2>&1';
155:             $this->_logDebug(sprintf("convert command executed by Horde_Image_im::raw(): %s", $command));
156:             exec($command, $output, $retval);
157:             if ($retval) {
158:                 $error = sprintf("Error running command: %s", $command . "\n" . implode("\n", $output));
159:                 $this->_logErr($error);
160:                 throw new Horde_Image_Exception($error);
161:             }
162: 
163:             /* Empty the operations queue */
164:             $this->_operations = array();
165:             $this->_postSrcOperations = array();
166: 
167:             /* Load the result */
168:             $return = file_get_contents($tmpout);
169:             if (!$preserve_data) {
170:                 $this->_data = $return;
171:             }
172:         }
173:         @unlink($tmpin);
174:         @unlink($tmpout);
175: 
176:         return $return;
177:     }
178: 
179:     /**
180:      * Reset the image data.
181:      */
182:     public function reset()
183:     {
184:         parent::reset();
185:         $this->_operations = array();
186:         $this->_postSrcOperations = array();
187:         $this->clearGeometry();
188:     }
189: 
190:     /**
191:      * Resize the current image. This operation takes place immediately.
192:      *
193:      * @param integer $width        The new width.
194:      * @param integer $height       The new height.
195:      * @param boolean $ratio        Maintain original aspect ratio.
196:      * @param boolean $keepProfile  Keep the image meta data.
197:      */
198:     public function resize($width, $height, $ratio = true, $keepProfile = false)
199:     {
200:         $resWidth = $width * 2;
201:         $resHeight = $height * 2;
202:         $this->_operations[] = "-size {$resWidth}x{$resHeight}";
203:         if ($ratio) {
204:             $this->_postSrcOperations[] = (($keepProfile) ? "-resize" : "-thumbnail") . " {$width}x{$height}";
205:         } else {
206:             $this->_postSrcOperations[] = (($keepProfile) ? "-resize" : "-thumbnail") . " {$width}x{$height}!";
207:         }
208:         // Reset the width and height instance variables since after resize
209:         // we don't know the *exact* dimensions yet (especially if we maintained
210:         // aspect ratio.
211:         // Refresh the data
212:         $this->raw();
213:         $this->clearGeometry();
214:     }
215: 
216:     /**
217:      * Crop the current image.
218:      *
219:      * @param integer $x1  x for the top left corner
220:      * @param integer $y1  y for the top left corner
221:      * @param integer $x2  x for the bottom right corner of the cropped image.
222:      * @param integer $y2  y for the bottom right corner of the cropped image.
223:      */
224:     public function crop($x1, $y1, $x2, $y2)
225:     {
226:         $line = ($x2 - $x1) . 'x' . ($y2 - $y1) . '+' . $x1 . '+' . $y1;
227:         $this->_operations[] = '-crop ' . $line . ' +repage';
228: 
229:         // Reset width/height since these might change
230:         $this->raw();
231:         $this->clearGeometry();
232:     }
233: 
234:     /**
235:      * Rotate the current image. This is an atomic operation.
236:      *
237:      * @param integer $angle       The angle to rotate the image by,
238:      *                             in the clockwise direction.
239:      * @param integer $background  The background color to fill any triangles.
240:      */
241:     public function rotate($angle, $background = 'white')
242:     {
243:         $this->raw();
244:         $this->_operations[] = "-background $background -rotate {$angle}";
245:         $this->raw();
246: 
247:         // Reset width/height since these might have changed
248:         $this->clearGeometry();
249:     }
250: 
251:     /**
252:      * Flip the current image.
253:      */
254:     public function flip()
255:     {
256:         $this->_operations[] = '-flip';
257:     }
258: 
259:     /**
260:      * Mirror the current image.
261:      */
262:     public function mirror()
263:     {
264:         $this->_operations[] = '-flop';
265:     }
266: 
267:     /**
268:      * Convert the current image to grayscale.
269:      */
270:     public function grayscale()
271:     {
272:         $this->_postSrcOperations[] = '-colorspace GRAY';
273:     }
274: 
275:     /**
276:      * Sepia filter.
277:      *
278:      * @param integer $threshold  Extent of sepia effect.
279:      */
280:     public function sepia($threshold =  85)
281:     {
282:         $this->_operations[] = '-sepia-tone ' . $threshold . '%';
283:     }
284: 
285:     /**
286:      * Draws a text string on the image in a specified location, with
287:      * the specified style information.
288:      *
289:      * @TODO: Need to differentiate between the stroke (border) and the fill color,
290:      *        but this is a BC break, since we were just not providing a border.
291:      *
292:      * @param string  $text       The text to draw.
293:      * @param integer $x          The left x coordinate of the start of the text string.
294:      * @param integer $y          The top y coordinate of the start of the text string.
295:      * @param string  $font       The font identifier you want to use for the text.
296:      * @param string  $color      The color that you want the text displayed in.
297:      * @param integer $direction  An integer that specifies the orientation of the text.
298:      * @param string  $fontsize   Size of the font (small, medium, large, giant)
299:      */
300:     public function text($string, $x, $y, $font = '', $color = 'black', $direction = 0, $fontsize = 'small')
301:     {
302:         $string = addslashes('"' . $string . '"');
303:         $fontsize = Horde_Image::getFontSize($fontsize);
304:         $this->_postSrcOperations[] = "-fill $color " . (!empty($font) ? "-font $font" : '') . " -pointsize $fontsize -gravity northwest -draw \"text $x,$y $string\" -fill none";
305:     }
306: 
307:     /**
308:      * Draw a circle.
309:      *
310:      * @param integer $x     The x coordinate of the centre.
311:      * @param integer $y     The y coordinate of the centre.
312:      * @param integer $r     The radius of the circle.
313:      * @param string $color  The line color of the circle.
314:      * @param string $fill   The color to fill the circle.
315:      */
316:     public function circle($x, $y, $r, $color, $fill = 'none')
317:     {
318:         $xMax = $x + $r;
319:         $this->_postSrcOperations[] = "-stroke $color -fill $fill -draw \"circle $x,$y $xMax,$y\" -stroke none -fill none";
320:     }
321: 
322:     /**
323:      * Draw a polygon based on a set of vertices.
324:      *
325:      * @param array $vertices  An array of x and y labeled arrays
326:      *                         (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
327:      * @param string $color    The color you want to draw the polygon with.
328:      * @param string $fill     The color to fill the polygon.
329:      */
330:     public function polygon($verts, $color, $fill = 'none')
331:     {
332:         $command = '';
333:         foreach ($verts as $vert) {
334:             $command .= sprintf(' %d,%d', $vert['x'], $vert['y']);
335:         }
336:         $this->_postSrcOperations[] = "-stroke $color -fill $fill -draw \"polygon $command\" -stroke none -fill none";
337:     }
338: 
339:     /**
340:      * Draw a rectangle.
341:      *
342:      * @param integer $x       The left x-coordinate of the rectangle.
343:      * @param integer $y       The top y-coordinate of the rectangle.
344:      * @param integer $width   The width of the rectangle.
345:      * @param integer $height  The height of the rectangle.
346:      * @param string $color    The line color of the rectangle.
347:      * @param string $fill     The color to fill the rectangle.
348:      */
349:     public function rectangle($x, $y, $width, $height, $color, $fill = 'none')
350:     {
351:         $xMax = $x + $width;
352:         $yMax = $y + $height;
353:         $this->_postSrcOperations[] = "-stroke $color -fill $fill -draw \"rectangle $x,$y $xMax,$yMax\" -stroke none -fill none";
354:     }
355: 
356:     /**
357:      * Draw a rounded rectangle.
358:      *
359:      * @param integer $x       The left x-coordinate of the rectangle.
360:      * @param integer $y       The top y-coordinate of the rectangle.
361:      * @param integer $width   The width of the rectangle.
362:      * @param integer $height  The height of the rectangle.
363:      * @param integer $round   The width of the corner rounding.
364:      * @param string  $color   The line color of the rectangle.
365:      * @param string  $fill    The color to fill the rounded rectangle with.
366:      */
367:     public function roundedRectangle($x, $y, $width, $height, $round, $color, $fill)
368:     {
369:         $x1 = $x + $width;
370:         $y1 = $y + $height;
371:         $this->_postSrcOperations[] = "-stroke $color -fill $fill -draw \"roundRectangle $x,$y $x1,$y1 $round,$round\" -stroke none -fill none";
372:     }
373: 
374:     /**
375:      * Draw a line.
376:      *
377:      * @param integer $x0     The x coordinate of the start.
378:      * @param integer $y0     The y coordinate of the start.
379:      * @param integer $x1     The x coordinate of the end.
380:      * @param integer $y1     The y coordinate of the end.
381:      * @param string $color   The line color.
382:      * @param string $width   The width of the line.
383:      */
384:     public function line($x0, $y0, $x1, $y1, $color = 'black', $width = 1)
385:     {
386:         $this->_operations[] = "-stroke $color -strokewidth $width -draw \"line $x0,$y0 $x1,$y1\"";
387:     }
388: 
389:     /**
390:      * Draw a dashed line.
391:      *
392:      * @param integer $x0           The x co-ordinate of the start.
393:      * @param integer $y0           The y co-ordinate of the start.
394:      * @param integer $x1           The x co-ordinate of the end.
395:      * @param integer $y1           The y co-ordinate of the end.
396:      * @param string $color         The line color.
397:      * @param string $width         The width of the line.
398:      * @param integer $dash_length  The length of a dash on the dashed line
399:      * @param integer $dash_space   The length of a space in the dashed line
400:      */
401:     public function dashedLine($x0, $y0, $x1, $y1, $color = 'black', $width = 1, $dash_length = 2, $dash_space = 2)
402:     {
403:        $this->_operations[] = "-stroke $color -strokewidth $width -draw \"line $x0,$y0 $x1,$y1\"";
404:     }
405: 
406:     /**
407:      * Draw a polyline (a non-closed, non-filled polygon) based on a
408:      * set of vertices.
409:      *
410:      * @param array $vertices  An array of x and y labeled arrays
411:      *                         (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
412:      * @param string $color    The color you want to draw the line with.
413:      * @param string $width    The width of the line.
414:      */
415:     public function polyline($verts, $color, $width = 1)
416:     {
417:         $command = '';
418:         foreach ($verts as $vert) {
419:             $command .= sprintf(' %d,%d', $vert['x'], $vert['y']);
420:         }
421:         $this->_operations[] = "-stroke $color -strokewidth $width -fill none -draw \"polyline $command\" -strokewidth 1 -stroke none -fill none";
422:     }
423: 
424:     /**
425:      * Draw an arc.
426:      *
427:      * @param integer $x      The x coordinate of the centre.
428:      * @param integer $y      The y coordinate of the centre.
429:      * @param integer $r      The radius of the arc.
430:      * @param integer $start  The start angle of the arc.
431:      * @param integer $end    The end angle of the arc.
432:      * @param string  $color  The line color of the arc.
433:      * @param string  $fill   The fill color of the arc (defaults to none).
434:      */
435:     public function arc($x, $y, $r, $start, $end, $color = 'black', $fill = 'none')
436:     {
437:         // Split up arcs greater than 180 degrees into two pieces.
438:         $this->_postSrcOperations[] = "-stroke $color -fill $fill";
439:         $mid = round(($start + $end) / 2);
440:         $x = round($x);
441:         $y = round($y);
442:         $r = round($r);
443:         if ($mid > 90) {
444:             $this->_postSrcOperations[] = "-draw \"ellipse $x,$y $r,$r $start,$mid\"";
445:             $this->_postSrcOperations[] = "-draw \"ellipse $x,$y $r,$r $mid,$end\"";
446:         } else {
447:             $this->_postSrcOperations[] = "-draw \"ellipse $x,$y $r,$r $start,$end\"";
448:         }
449: 
450:         // If filled, draw the outline.
451:         if (!empty($fill)) {
452:             list($x1, $y1) = Horde_Image::circlePoint($start, $r * 2);
453:             list($x2, $y2) = Horde_Image::circlePoint($mid, $r * 2);
454:             list($x3, $y3) = Horde_Image::circlePoint($end, $r * 2);
455: 
456:             // This seems to result in slightly better placement of
457:             // pie slices.
458:             $x++;
459:             $y++;
460: 
461:             $verts = array(array('x' => $x + $x3, 'y' => $y + $y3),
462:                            array('x' => $x, 'y' => $y),
463:                            array('x' => $x + $x1, 'y' => $y + $y1));
464: 
465:             if ($mid > 90) {
466:                 $verts1 = array(array('x' => $x + $x2, 'y' => $y + $y2),
467:                                 array('x' => $x, 'y' => $y),
468:                                 array('x' => $x + $x1, 'y' => $y + $y1));
469:                 $verts2 = array(array('x' => $x + $x3, 'y' => $y + $y3),
470:                                 array('x' => $x, 'y' => $y),
471:                                 array('x' => $x + $x2, 'y' => $y + $y2));
472: 
473:                 $this->polygon($verts1, $fill, $fill);
474:                 $this->polygon($verts2, $fill, $fill);
475:             } else {
476:                 $this->polygon($verts, $fill, $fill);
477:             }
478: 
479:             $this->polyline($verts, $color);
480: 
481:             $this->_postSrcOperations[] = '-stroke none -fill none';
482:         }
483:     }
484: 
485:     public function applyEffects()
486:     {
487:         $this->raw();
488:         foreach ($this->_toClean as $tempfile) {
489:             @unlink($tempfile);
490:         }
491:     }
492: 
493:     /**
494:      * Method to execute a raw command directly in convert. Useful for executing
495:      * more involved operations that may require multiple convert commands
496:      * piped into each other for example. Really designed for use by im based
497:      * Horde_Image_Effect objects..
498:      *
499:      * The input and output files are quoted and substituted for __FILEIN__ and
500:      * __FILEOUT__ respectfully. In order to support piped convert commands, the
501:      * path to the convert command is substitued for __CONVERT__ (but the
502:      * initial convert command is added automatically).
503:      *
504:      * @param string $cmd    The command string, with substitutable tokens
505:      * @param array $values  Any values that should be substituted for tokens.
506:      *
507:      * @return
508:      */
509:     public function executeConvertCmd($cmd, $values = array())
510:     {
511:         // First, get a temporary file for the input
512:         if (strpos($cmd, '__FILEIN__') !== false) {
513:             $tmpin = $this->toFile($this->_data);
514:         } else {
515:             $tmpin = '';
516:         }
517: 
518:         // Now an output file
519:         $tmpout = Horde_Util::getTempFile('img', false, $this->_tmpdir);
520: 
521:         // Substitue them in the cmd string
522:         $cmd = str_replace(array('__FILEIN__', '__FILEOUT__', '__CONVERT__'),
523:                            array('"' . $tmpin . '"', '"' . $tmpout . '"', $this->_convert),
524:                            $cmd);
525: 
526:         //TODO: See what else needs to be replaced.
527:         $cmd = $this->_convert . ' ' . $cmd . ' 2>&1';
528: 
529:         // Log it
530:         $this->_logDebug(sprintf("convert command executed by Horde_Image_im::executeConvertCmd(): %s", $cmd));
531:         exec($cmd, $output, $retval);
532:         if ($retval) {
533:             $this->_logErr(sprintf("Error running command: %s", $cmd . "\n" . implode("\n", $output)));
534:         }
535:         $this->_data = file_get_contents($tmpout);
536: 
537:         @unlink($tmpin);
538:         @unlink($tmpout);
539:     }
540: 
541:     /**
542:      * Get the version of the convert command available. This needs to be
543:      * publicly visable since it's used by various Effects.
544:      *
545:      * @return A version string suitable for using in version_compare()
546:      */
547:     public function getIMVersion()
548:     {
549:         static $version = null;
550:         if (!is_array($version)) {
551:             $commandline = $this->_convert . ' --version';
552:             exec($commandline, $output, $retval);
553:             if (preg_match('/([0-9])\.([0-9])\.([0-9])/', $output[0], $matches)) {
554:                 $version = $matches;
555:                 return $matches;
556:             } else {
557:                return false;
558:             }
559:         }
560: 
561:         return $version;
562:     }
563: 
564:     public function addPostSrcOperation($operation)
565:     {
566:         $this->_postSrcOperations[] = $operation;
567:     }
568: 
569:     public function addOperation($operation)
570:     {
571:         $this->_operations[] = $operation;
572:     }
573: 
574:     public function addFileToClean($filename)
575:     {
576:         $this->_toClean[] = $filename;
577:     }
578: 
579:     public function getConvertPath()
580:     {
581:         return $this->_convert;
582:     }
583: 
584:     /**
585:      * Reset the imagick iterator to the first image in the set.
586:      *
587:      * @return void
588:      */
589:     public function rewind()
590:     {
591:         $this->_logDebug('Horde_Image_Im#rewind');
592:         $this->_currentPage = 0;
593:     }
594: 
595:     /**
596:      * Return the current image from the internal iterator.
597:      *
598:      * @return Horde_Image_Imagick
599:      */
600:     public function current()
601:     {
602:         $this->_logDebug('Horde_Image_Im#current');
603:         return $this->getImageAtIndex($this->_currentPage);
604:     }
605: 
606:     /**
607:      * Get the index of the internal iterator.
608:      *
609:      * @return integer
610:      */
611:     public function key()
612:     {
613:         $this->_logDebug('Horde_Image_Im#key');
614:         return $this->_currentPage;
615:     }
616: 
617:     /**
618:      * Advance the iterator
619:      *
620:      * @return Horde_Image_Im
621:      */
622:     public function next()
623:     {
624:         $this->_logDebug('Horde_Image_Im#next');
625:         $this->_currentPage++;
626:         if ($this->valid()) {
627:             return $this->getImageAtIndex($this->_currentPage);
628:         }
629:     }
630: 
631:     /**
632:      * Deterimines if the current iterator item is valid.
633:      *
634:      * @return boolean
635:      */
636:     public function valid()
637:     {
638:         return $this->_currentPage < $this->getImagePageCount();
639:     }
640: 
641:     /**
642:      * Request a specific image from the collection of images.
643:      *
644:      * @param integer $index  The index to return
645:      *
646:      * @return Horde_Image_Base
647:      */
648:     public function getImageAtIndex($index)
649:     {
650:         $this->_logDebug('Horde_Image_Im#getImageAtIndex: ' . $index);
651:         if ($index >= $this->getImagePageCount()) {
652:             throw new Horde_Image_Exception('Image index out of bounds.');
653:         }
654:         $rawImage = $this->_raw(true, $index, true);
655:         $image = new Horde_Image_Im(array('data' => $rawImage), $this->_context);
656: 
657:         return $image;
658:     }
659: 
660:     /**
661:      * Return the number of image pages available in the image object.
662:      *
663:      * @return integer
664:      */
665:     public function getImagePageCount()
666:     {
667:         if (is_null($this->_pages)) {
668:             $pages = $this->_getImagePages();
669:             $this->_pages = array_pop($pages);
670:         }
671:         $this->_logDebug('Horde_Image_Im#getImagePageCount: ' . $this->_pages);
672: 
673:         return $this->_pages;
674: 
675:     }
676: 
677:     private function _getImagePages()
678:     {
679:         $this->_logDebug('Horde_Image_Im#_getImagePages');
680:         $filename = $this->toFile();
681:         $cmd = $this->_identify . ' -format "%n" ' . $filename;
682:         exec($cmd, $output, $retval);
683:         if ($retval) {
684:             $this->_logErr(sprintf("Error running command: %s", $cmd . "\n" . implode("\n", $output)));
685:         }
686:         unlink($filename);
687: 
688:         return $output;
689:     }
690: }
691: 
API documentation generated by ApiGen