Complete implementation of Iterator support in Horde_Image.
authorMichael J. Rubinsky <mrubinsk@horde.org>
Sun, 14 Feb 2010 21:49:13 +0000 (16:49 -0500)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Sun, 14 Feb 2010 21:49:13 +0000 (16:49 -0500)
This works in Imagemagick and Imagick only. GD will always return a clone of itself;
(GD doesn't support TIFF or PDF, and has no native methods for obtaining individual pages from GIF)

Multipage images (TIFF, GIF, PDF etc...) can now be either iterated as so:

<code>
// $original Image contains a multipage image
foreach($original as $page) {
  // $page is a Horde_Image object representing the single page
}
</code>

or individually select the page as so:

<code>
$page = $original->getImageAtIndex($pageNumber);
</code>

Horde_Image_Base#getImagePageCount returns the total number of pages.

framework/Image/lib/Horde/Image/Base.php
framework/Image/lib/Horde/Image/Gd.php
framework/Image/lib/Horde/Image/Im.php
framework/Image/lib/Horde/Image/Imagick.php

index 3313dc1..419a9fb 100644 (file)
@@ -16,7 +16,7 @@
  * @TODO: - Can we depend on the Horde_Util:: class or some other solution needed?
  *        - Exceptions
  */
-class Horde_Image_Base Implements Iterator
+abstract class Horde_Image_Base Implements Iterator
 {
     /**
      * Background color.
@@ -91,13 +91,22 @@ class Horde_Image_Base Implements Iterator
     protected $_type = 'png';
 
     /**
+     * Cache the context
+     *
+     * @param array
+     */
+     protected $_context;
+     
+    /**
      * Constructor.
      *
      * @param string $rgb  The base color for generated pixels/images.
      */
     protected function __construct($params, $context = array())
     {
-
+        $this->_params = $params;
+        $this->_context = $context;
+        
         if (empty($context['tmpdir'])) {
             throw new InvalidArgumentException('A path to a temporary directory is required.');
         }
@@ -309,7 +318,7 @@ class Horde_Image_Base Implements Iterator
     public function display()
     {
         $this->headers();
-        echo $this->raw();
+        echo $this->raw(true);
     }
 
     /**
@@ -419,32 +428,19 @@ class Horde_Image_Base Implements Iterator
     }
 
     /**
-     * Iterator interface
+     * Request a specific image from the collection of images.
+     *
+     * @param integer $index  The index to return
+     *
+     * @return Horde_Image_Base
      */
-    public function rewind()
-    {
-
-    }
-
-    public function current()
-
-    {
-
-    }
-
-    public function key()
-    {
-
-    }
-
-    public function next()
-    {
-
-    }
-
-    public function valid()
-    {
-
-    }
+    abstract function getImageAtIndex($index);
 
+    /**
+     * Return the number of image pages available in the image object.
+     *
+     * @return integer
+     */
+    abstract function getImagePageCount();
+    
 }
index 7d6e441..7e6bd80 100644 (file)
@@ -789,4 +789,68 @@ class Horde_Image_Gd extends Horde_Image_Base
         return $result;
     }
 
+    /**
+     * Return the current image from the internal iterator.
+     *
+     * @return Horde_Image_Gd
+     */
+    public function current()
+    {
+        return clone($this);
+    }
+
+    /**
+     * Get the index of the internal iterator.
+     *
+     * @return integer
+     */
+    public function key()
+    {
+        return 0;
+    }
+
+    /**
+     * Advance the iterator
+     *
+     * @return Horde_Image_Imagick
+     */
+    public function next()
+    {
+        return null;
+    }
+
+    /**
+     * Deterimines if the current iterator item is valid.
+     *
+     * @return boolean
+     */
+    public function valid()
+    {
+        return false;
+    }
+
+    /**
+     * Request a specific image from the collection of images.
+     *
+     * @param integer $index  The index to return
+     *
+     * @return Horde_Image_Base
+     */
+    public function getImageAtIndex($index)
+    {
+        if ($index > 0) {
+            throw new Horde_Image_Exception('Image index out of bounds.');
+        }
+    }
+
+    /**
+     * Return the number of image pages available in the image object.
+     *
+     * @return integer
+     */
+    public function getImagePageCount()
+    {
+        return 1;
+    }
+
 }
index eae3262..09367ce 100644 (file)
@@ -61,6 +61,27 @@ class Horde_Image_Im extends Horde_Image_Base
     protected $_convert = '';
 
     /**
+     * Path to the identify binary
+     *
+     * @string
+     */
+    protected $_identify;
+    
+    /**
+     * Cache the number of image pages
+     *
+     * @var integer
+     */
+    private $_pages;
+
+    /**
+     * Track current page for the iterator
+     *
+     * @var integer
+     */
+    private $_currentPage = 0;
+
+    /**
      * Constructor.
      */
     public function __construct($params, $context = array())
@@ -71,6 +92,10 @@ class Horde_Image_Im extends Horde_Image_Base
             throw new InvalidArgumentException('A path to the convert binary is required.');
         }
         $this->_convert = $context['convert'];
+
+        if (!empty($context['identify'])) {
+            $this->_identify = $context['identify'];
+        }
         if (!empty($params['filename'])) {
             $this->loadFile($params['filename']);
         } elseif (!empty($params['data'])) {
@@ -82,6 +107,17 @@ class Horde_Image_Im extends Horde_Image_Base
     }
 
     /**
+     * Publically visible raw method. Hides the extra parameters from client
+     * code.
+     *
+     * @see self::_raw
+     */
+    public function raw($convert = false)
+    {
+        return $this->_raw($convert);
+    }
+
+    /**
      * Returns the raw data for this image.
      *
      * @param boolean $convert  If true, the image data will be returned in the
@@ -92,7 +128,7 @@ class Horde_Image_Im extends Horde_Image_Base
      *
      * @return string  The raw image data.
      */
-    public function raw($convert = false)
+    private function _raw($convert = false, $index = 0, $preserve_data = false)
     {
         if (empty($this->_data) ||
             // If there are no operations, and we already have data, don't
@@ -109,7 +145,7 @@ class Horde_Image_Im extends Horde_Image_Base
         if (count($this->_operations) || count($this->_postSrcOperations) || $convert) {
             $tmpout = Horde_Util::getTempFile('img', false, $this->_tmpdir);
             $command = $this->_convert . ' ' . implode(' ', $this->_operations)
-                . ' "' . $tmpin . '"\'[0]\' '
+                . ' "' . $tmpin . '"\'[' . $index . ']\' '
                 . implode(' ', $this->_postSrcOperations)
                 . ' +profile "*" ' . $this->_type . ':"' . $tmpout . '" 2>&1';
             $this->_logDebug(sprintf("convert command executed by Horde_Image_im::raw(): %s", $command));
@@ -123,12 +159,15 @@ class Horde_Image_Im extends Horde_Image_Base
             $this->_postSrcOperations = array();
 
             /* Load the result */
-            $this->_data = file_get_contents($tmpout);
+            $return = file_get_contents($tmpout);
+            if (!$preserve_data) {
+                $this->_data = $return;
+            }
         }
         @unlink($tmpin);
         @unlink($tmpout);
 
-        return $this->_data;
+        return $return;
     }
 
     /**
@@ -536,4 +575,110 @@ class Horde_Image_Im extends Horde_Image_Base
         return $this->_convert;
     }
 
+    /**
+     * Reset the imagick iterator to the first image in the set.
+     *
+     * @return void
+     */
+    public function rewind()
+    {
+        $this->_logDebug('Horde_Image_Im#rewind');
+        $this->_currentPage = 0;
+    }
+
+    /**
+     * Return the current image from the internal iterator.
+     *
+     * @return Horde_Image_Imagick
+     */
+    public function current()
+    {
+        $this->_logDebug('Horde_Image_Im#current');
+        return $this->getImageAtIndex($this->_currentPage);
+    }
+
+    /**
+     * Get the index of the internal iterator.
+     *
+     * @return integer
+     */
+    public function key()
+    {
+        $this->_logDebug('Horde_Image_Im#key');
+        return $this->_currentPage;
+    }
+
+    /**
+     * Advance the iterator
+     *
+     * @return Horde_Image_Im
+     */
+    public function next()
+    {
+        $this->_logDebug('Horde_Image_Im#next');
+        $this->_currentPage++;
+        if ($this->valid()) {
+            return $this->getImageAtIndex($this->_currentPage);
+        }
+    }
+
+    /**
+     * Deterimines if the current iterator item is valid.
+     *
+     * @return boolean
+     */
+    public function valid()
+    {
+        return $this->_currentPage < $this->getImagePageCount();
+    }
+
+    /**
+     * Request a specific image from the collection of images.
+     *
+     * @param integer $index  The index to return
+     *
+     * @return Horde_Image_Base
+     */
+    public function getImageAtIndex($index)
+    {
+        $this->_logDebug('Horde_Image_Im#getImageAtIndex: ' . $index);
+        if ($index >= $this->getImagePageCount()) {
+            throw new Horde_Image_Exception('Image index out of bounds.');
+        }
+        $rawImage = $this->_raw(true, $index, true);
+        $image = new Horde_Image_Im(array('data' => $rawImage), $this->_context);
+        
+        return $image;
+    }
+
+    /**
+     * Return the number of image pages available in the image object.
+     *
+     * @return integer
+     */
+    public function getImagePageCount()
+    {
+        if (is_null($this->_pages)) {
+            $pages = $this->_getImagePages();
+            $this->_pages = array_pop($pages);
+        }
+        $this->_logDebug('Horde_Image_Im#getImagePageCount: ' . $this->_pages);
+
+        return $this->_pages;
+
+    }
+
+    private function _getImagePages()
+    {
+        $this->_logDebug('Horde_Image_Im#_getImagePages');
+        $filename = $this->toFile();
+        $cmd = $this->_identify . ' -format "%n" ' . $filename;
+        exec($cmd, $output, $retval);
+        if ($retval) {
+            $this->_logErr(sprintf("Error running command: %s", $cmd . "\n" . implode("\n", $output)));
+        }
+        unlink($filename);
+        
+        return $output;
+    }
 }
index 082d8cb..5524dbe 100644 (file)
@@ -114,7 +114,7 @@ class Horde_Image_Imagick extends Horde_Image_Base
             throw new Horde_Image_Exception($e);
         }
         $this->_imagick->setFormat($this->_type);
-        $this->_imagick->setIteratorIndex(0);
+        //$this->_imagick->setIteratorIndex(0);
         unset($this->_data);
     }
 
@@ -555,10 +555,8 @@ class Horde_Image_Imagick extends Horde_Image_Base
     {
         $this->_logDebug('Horde_Image_Imagick#current');
         $params = array('data' => $this->raw());
-        $context = array('tmpdir' => $this->_tmpdir,
-                         'logger' => $this->_logger);
-        $image = new Horde_Image_Imagick($params, $context);
-        $this->_logDebug(print_r($image, true));
+        $image = new Horde_Image_Imagick($params, $this->_context);
+        
         return $image;
     }
 
@@ -589,9 +587,45 @@ class Horde_Image_Imagick extends Horde_Image_Base
         }
     }
 
+    /**
+     * Deterimines if the current iterator item is valid.
+     *
+     * @return boolean
+     */
     public function valid()
     {
         $this->_logDebug('Horde_Image_Imagick#valid:' . print_r(!$this->moreImages, true));
         return !$this->_noMoreImages;
     }
+
+    /**
+     * Request a specific image from the collection of images.
+     *
+     * @param integer $index  The index to return
+     *
+     * @return Horde_Image_Base
+     */
+    public function getImageAtIndex($index)
+    {
+        if ($index >= $this->_imagick->getNumberImages()) {
+            throw Horde_Image_Exception('Image index out of bounds.');
+        }
+
+        $currentIndex = $this->_imagick->getIteratorIndex();
+        $this->_imagick->setIteratorIndex($index);
+        $image = $this->current();
+        $this->_imagick->setIteratorIndex($currentIndex);
+
+        return $image;
+    }
+
+    /**
+     * Return the number of image pages available in the image object.
+     *
+     * @return integer
+     */
+    public function getImagePageCount()
+    {
+        return $this->_imagick->getNumberImages();
+    }
  }