Add Imagick native versions of all ImageMagick dependent effects
authorMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 28 May 2009 00:26:39 +0000 (20:26 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 28 May 2009 00:26:39 +0000 (20:26 -0400)
framework/Image/lib/Horde/Image/Effect/Imagick/Border.php [new file with mode: 0755]
framework/Image/lib/Horde/Image/Effect/Imagick/Composite.php [new file with mode: 0755]
framework/Image/lib/Horde/Image/Effect/Imagick/DropShadow.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Effect/Imagick/PhotoStack.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Effect/Imagick/PolaroidImage.php [new file with mode: 0755]
framework/Image/lib/Horde/Image/Effect/Imagick/RoundCorners.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Effect/Imagick/TextWatermark.php [new file with mode: 0644]
framework/Image/lib/Horde/Image/Imagick.php
framework/Image/package.xml

diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/Border.php b/framework/Image/lib/Horde/Image/Effect/Imagick/Border.php
new file mode 100755 (executable)
index 0000000..aa47ac1
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Image border Effect for the Horde_Image package.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_Border extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters for border effects:
+     *
+     *   bordercolor     - Border color. Defaults to black.
+     *   borderwidth     - Border thickness, defaults to 1 pixel.
+     *   preserve        - Preserves the alpha transparency layer (if present)
+     *
+     * @var array
+     */
+    protected $_params = array('bordercolor' => 'black',
+                               'borderwidth' => 1,
+                               'preserve' => true);
+
+    /**
+     * Draw the border.
+     *
+     * This draws the configured border to the provided image. Beware,
+     * that every pixel inside the border clipping will be overwritten
+     * with the background color.
+     */
+    public function apply()
+    {
+        Horde_Image_Imagick::borderImage($this->_image->imagick,
+                                         $this->_params['bordercolor'],
+                                         $this->_params['borderwidth'],
+                                         $this->_params['borderwidth']);
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/Composite.php b/framework/Image/lib/Horde/Image/Effect/Imagick/Composite.php
new file mode 100755 (executable)
index 0000000..e0f63dd
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Simple composite effect for composing multiple images. This effect assumes
+ * that all images being passed in are already the desired size.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org)
+ *
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_Composite extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters for border effects:
+     *
+     * 'images'  - an array of Horde_Image objects to overlay.
+     *
+     *  ...and ONE of the following. If both are provided, the behaviour is
+     *  undefined.
+     *
+     * 'gravity'    - the ImageMagick gravity constant describing placement
+     *                (IM driver only so far, not imagick)
+     *
+     * 'x' and 'y'  - coordinates for the overlay placement.
+     *
+     * @var array
+     */
+    protected $_params = array();
+
+    /**
+     * Draw the border.
+     *
+     * This draws the configured border to the provided image. Beware,
+     * that every pixel inside the border clipping will be overwritten
+     * with the background color.
+     */
+    public function apply()
+    {
+        foreach ($this->_params['images'] as $image) {
+            $topimg = new Imagick();
+            $topimg->clear();
+            $topimg->readImageBlob($image->raw());
+
+            /* Calculate center for composite (gravity center)*/
+            $geometry = $this->_image->imagick->getImageGeometry();
+            $x = $geometry['width'] / 2;
+            $y = $geometry['height'] / 2;
+
+            if (isset($this->_params['x']) && isset($this->_params['y'])) {
+                $x = $this->_params['x'];
+                $y = $this->_params['y'];
+            }
+            $this->_image->_imagick->compositeImage($topimg, Imagick::COMPOSITE_OVER, $x, $y);
+        }
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/DropShadow.php b/framework/Image/lib/Horde/Image/Effect/Imagick/DropShadow.php
new file mode 100644 (file)
index 0000000..5dec7a3
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Image effect for adding a drop shadow.
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_DropShadow extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters: Most are currently ignored for the im version
+     * of this effect.
+     *
+     * @TODO
+     *
+     * @var array
+     */
+    protected $_params = array('distance' => 5, // This is used as the x and y offset
+                               'width' => 2,
+                               'hexcolor' => '000000',
+                               'angle' => 215,
+                               'fade' => 3, // Sigma value
+                               'padding' => 0,
+                               'background' => 'none');
+
+    /**
+     * Apply the effect.
+     *
+     * @return mixed true | PEAR_Error
+     */
+    public function apply()
+    {
+        $shadow = $this->_image->imagick->clone();
+        $shadow->setImageBackgroundColor(new ImagickPixel('black'));
+        $shadow->shadowImage(80, $this->_params['fade'],
+                             $this->_params['distance'],
+                             $this->_params['distance']);
+
+
+        // If we have an actual background color, we need to explicitly
+        // create a new background image with that color to be sure there
+        // *is* a background color.
+        if ($this->_params['background'] != 'none') {
+            $size = $shadow->getImageGeometry();
+            $new = new Imagick();
+            $new->newImage($size['width'], $size['height'], new ImagickPixel($this->_params['background']));
+            $new->setImageFormat($this->_image->getType());
+
+            $new->compositeImage($shadow, Imagick::COMPOSITE_OVER, 0, 0);
+            $shadow->clear();
+            $shadow->addImage($new);
+            $new->destroy();
+        }
+
+        if ($this->_params['padding']) {
+            Horde_Image_Imagick::borderImage($shadow,
+                                             $this->_params['background'],
+                                             $this->_params['padding'],
+                                             $this->_params['padding']);
+        }
+
+        $shadow->compositeImage($this->_image->imagick, Imagick::COMPOSITE_OVER, 0, 0);
+        $this->_image->imagick->clear();
+        $this->_image->imagick->addImage($shadow);
+        $shadow->destroy();
+
+        $this->_image->clearGeometry();
+
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/PhotoStack.php b/framework/Image/lib/Horde/Image/Effect/Imagick/PhotoStack.php
new file mode 100644 (file)
index 0000000..f46cc93
--- /dev/null
@@ -0,0 +1,230 @@
+<?php
+/**
+ * Effect for composing multiple images into a single image.
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * The technique for the Polaroid-like stack using the Imagick extension is
+ * credited to Mikko Koppanen and is documented at http://valokuva.org
+ *
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_PhotoStack extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters for the stack effect
+     *
+     * images           -    An array of Horde_Image objects to stack. Images
+     *                       are stacked in a FIFO manner, so that the top-most
+     *                       image is the last one in this array.
+     *
+     * type             -    Determines the style for the composition.
+     *                       'plain' or 'polaroid' are supported.
+     *
+     * resize_height    -    The height that each individual thumbnail
+     *                       should be resized to before composing on the image.
+     *
+     * padding          -    How much padding should we ensure is left around
+     *                       the active image area?
+     *
+     * background       -    The background canvas color - this is used as the
+     *                       color to set any padding to.
+     *
+     * bordercolor      -    If using type 'plain' this sets the color of the
+     *                       border that each individual thumbnail gets.
+     *
+     * borderwidth      -    If using type 'plain' this sets the width of the
+     *                       border on each individual thumbnail.
+     *
+     * offset           -    If using type 'plain' this determines the amount of
+     *                       x and y offset to give each successive image when
+     *                       it is placed on the top of the stack.
+     *
+     * @var array
+     */
+    protected $_params = array('type' => 'plain',
+                               'resize_height' => '150',
+                               'padding' => 0,
+                               'background' => 'none',
+                               'bordercolor' => '#333',
+                               'borderwidth' => 1,
+                               'borderrounding' => 10,
+                               'offset' => 5);
+
+    /**
+     * Create the photo_stack
+     *
+     */
+    public function apply()
+    {
+        $i = 1;
+        $cnt = count($this->_params['images']);
+        if ($cnt <=0) {
+            throw new Horde_Image_Exception('No Images provided.');
+        }
+        if (!method_exists($this->_image->imagick, 'polaroidImage') ||
+            !method_exists($this->_image->imagick, 'trimImage')) {
+                throw new Horde_Image_Exception('Your version of Imagick is not compiled against a recent enough ImageMagick library to use the PhotoStack effect.');
+        }
+
+        $imgs = array();
+        $length = 0;
+
+        switch ($this->_params['type']) {
+        case 'plain':
+        case 'rounded':
+            $haveBottom = false;
+            // First, we need to resize the top image to get the dimensions
+            // for the rest of the stack.
+            $topimg = new Imagick();
+            $topimg->clear();
+            $topimg->readImageBlob($this->_params['images'][$cnt - 1]->raw());
+            $topimg->thumbnailImage(
+                $this->_params['resize_height'],
+                $this->_params['resize_height'],
+                true);
+            if ($this->_params['type'] == 'rounded') {
+                $topimg = $this->_roundBorder($topimg);
+            }
+
+            $size = $topimg->getImageGeometry();
+            foreach ($this->_params['images'] as $image) {
+                $imgk= new Imagick();
+                $imgk->clear();
+                $imgk->readImageBlob($image->raw());
+                if ($i++ <= $cnt) {
+                    $imgk->thumbnailImage($size['width'], $size['height'],
+                                          false);
+                } else {
+                    $imgk->destroy();
+                    $imgk = $topimg->clone();
+                }
+
+                if ($this->_params['type'] == 'rounded') {
+                    $imgk = $this->_roundBorder($imgk);
+                } else {
+                    Horde_Image_Imagick::borderImage($imgk, $this->_params['bordercolor'], 1, 1);
+                }
+                // Only shadow the bottom image for 'plain' stacks
+                if (!$haveBottom) {
+                    $shad = $imgk->clone();
+                    $shad->setImageBackgroundColor(new ImagickPixel('black'));
+                    $shad->shadowImage(80, 4, 0, 0);
+                    $shad->compositeImage($imgk, Imagick::COMPOSITE_OVER, 0, 0);
+                    $imgk->clear();
+                    $imgk->addImage($shad);
+                    $shad->destroy();
+                    $haveBottom = true;
+                }
+                // Get the geometry of the image and remember the largest.
+                $geo = $imgk->getImageGeometry();
+                $length = max(
+                    $length,
+                    sqrt(pow($geo['height'], 2) + pow($geo['width'], 2)));
+
+                $imgs[] = $imgk;
+            }
+            break;
+        case 'polaroid':
+            foreach ($this->_params['images'] as $image) {
+                //@TODO: instead of doing $image->raw(), we might be able to clone
+                //         the imagick object if we can do it cleanly might
+                //         be faster, less memory intensive?
+                $imgk= new Imagick();
+                $imgk->clear();
+                $imgk->readImageBlob($image->raw());
+                $imgk->thumbnailImage($this->_params['resize_height'],
+                                      $this->_params['resize_height'],
+                                      true);
+                $imgk->setImageBackgroundColor('black');
+                if ($i++ == $cnt) {
+                    $angle = 0;
+                } else {
+                    $angle = mt_rand(1, 45);
+                    if (mt_rand(1, 2) % 2 === 0) {
+                        $angle = $angle * -1;
+                    }
+                }
+                $result = $imgk->polaroidImage(new ImagickDraw(), $angle);
+                if (is_a($result, 'PEAR_Error')) {
+                    return $result;
+                }
+                 // Get the geometry of the image and remember the largest.
+                $geo = $imgk->getImageGeometry();
+                $length = max(
+                    $length,
+                    sqrt(pow($geo['height'], 2) + pow($geo['width'], 2)));
+
+                $imgs[] = $imgk;
+            }
+            break;
+        }
+
+        // Make sure the background canvas is large enough to hold it all.
+        $this->_image->imagick->thumbnailImage($length * 1.5 + 20,
+                                               $length * 1.5 + 20);
+
+        // x and y offsets.
+        $xo = $yo = (count($imgs) + 1) * $this->_params['offset'];
+        foreach ($imgs as $image) {
+            if ($this->_params['type'] == 'polaroid') {
+                $xo = mt_rand(1, $this->_params['resize_height'] / 2);
+                $yo = mt_rand(1, $this->_params['resize_height'] / 2);
+            } elseif ($this->_params['type'] == 'plain' ||
+                      $this->_params['type'] == 'rounded') {
+                $xo -= $this->_params['offset'];
+                $yo -= $this->_params['offset'];
+            }
+            $this->_image->imagick->compositeImage($image, Imagick::COMPOSITE_OVER, $xo, $yo);
+            $image->removeImage();
+            $image->destroy();
+        }
+
+        // If we have a background other than 'none' we need to
+        // compose two images together to make sure we *have* a background.
+        if ($this->_params['background'] != 'none') {
+            $size = $this->_image->getDimensions();
+            $new = new Imagick();
+            $new->newImage($length * 1.5 + 20, $length * 1.5 + 20, new ImagickPixel($this->_params['background']));
+            $new->setImageFormat($this->_image->getType());
+            $new->compositeImage($this->_image->imagick, Imagick::COMPOSITE_OVER, 0, 0);
+            $this->_image->imagick->clear();
+            $this->_image->imagick->addImage($new);
+            $new->destroy();
+        }
+
+        // Trim the canvas before resizing to keep the thumbnails as large
+        // as possible.
+        $this->_image->imagick->trimImage(0);
+        if ($this->_params['padding']) {
+            Horde_Image_Imagick::borderImage($this->_image->imagick,
+                                             $this->_params['background'],
+                                             $this->_params['padding'],
+                                             $this->_params['padding']);
+        }
+
+        return true;
+    }
+
+    private function _roundBorder($image)
+    {
+        $context = array('tmpdir' => $this->_image->getTmpDir());
+        $size = $image->getImageGeometry();
+        $new = Horde_Image::factory('Imagick', array('context' => $context));
+        $new->loadString('somestring', $image->getImageBlob());
+        $image->destroy();
+        $new->addEffect('RoundCorners', array('border' => 2, 'bordercolor' => '#111'));
+        $new->applyEffects();
+        $return = new Imagick();
+        $return->newImage($size['width'] + $this->_params['borderwidth'],
+                          $size['height'] + $this->_params['borderwidth'],
+                          $this->_params['bordercolor']);
+        $return->setImageFormat($this->_image->getType());
+        $return->clear();
+        $return->readImageBlob($new->raw());
+
+        return $return;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/PolaroidImage.php b/framework/Image/lib/Horde/Image/Effect/Imagick/PolaroidImage.php
new file mode 100755 (executable)
index 0000000..08fd647
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Effect for creating a polaroid looking image.
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_PolaroidImage extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters for the polaroid effect
+     *
+     * resize_height    -    The height that each individual thumbnail
+     *                       should be resized to before composing on the image.
+     *
+     * background       -    The color of the image background.
+     *
+     * angle            -    Angle to rotate the image.
+     *
+     * shadowcolor      -    The color of the image shadow.
+     */
+
+    /**
+     * @var array
+     */
+    protected $_params = array('background' => 'none',
+                               'angle' => 0,
+                               'shadowcolor' => 'black');
+
+    /**
+     * Create the effect
+     *
+     */
+    public function apply()
+    {
+        if (!method_exists($this->_image->imagick, 'polaroidImage') ||
+            !method_exists($this->_image->imagick, 'trimImage')) {
+                throw new Horde_Image_Exception('Your version of Imagick is not compiled against a recent enough ImageMagick library to use the PolaroidImage effect.');
+        }
+
+        // This determines the color of the underlying shadow.
+        $this->_image->imagick->setImageBackgroundColor(new ImagickPixel($this->_params['shadowcolor']));
+        $this->_image->imagick->polaroidImage(new ImagickDraw(), $this->_params['angle']);
+
+
+        // We need to create a new image to composite the polaroid over.
+        // (yes, even if it's a transparent background evidently)
+        $size = $this->_image->getDimensions();
+        $imk = new Imagick();
+        $imk->newImage($size['width'], $size['height'], $this->_params['background']);
+        $imk->setImageFormat($this->_image->getType());
+        $result = $imk->compositeImage($this->_image->imagick, Imagick::COMPOSITE_OVER, 0, 0);
+        $this->_image->imagick->clear();
+        $this->_image->imagick->addImage($imk);
+        $imk->destroy();
+
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/RoundCorners.php b/framework/Image/lib/Horde/Image/Effect/Imagick/RoundCorners.php
new file mode 100644 (file)
index 0000000..f5b76ef
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Image effect for rounding image corners.
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_RoundCorners extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters:
+     *
+     *  radius - Radius of rounded corners.
+     *
+     * @var array
+     */
+    protected $_params = array('radius' => 10,
+                               'background' => 'none',
+                               'border' => 0,
+                               'bordercolor' => 'none');
+
+    public function apply()
+    {
+        if (!method_exists($this->_image->imagick, 'roundCorners')) {
+                throw new Horde_Image_Exception('Your version of Imagick is not compiled against a recent enough ImageMagick library (> 6.2.8) to use the RoundCorners effect.');
+        }
+
+        $round = $this->_params['radius'];
+        $result = $this->_image->imagick->roundCorners($round, $round);
+
+        // Using a border?
+        if ($this->_params['bordercolor'] != 'none' &&
+            $this->_params['border'] > 0) {
+
+            $size = $this->_image->getDimensions();
+
+            $new = new Imagick();
+            $new->newImage($size['width'] + $this->_params['border'],
+                           $size['height'] + $this->_params['border'],
+                           $this->_params['bordercolor']);
+            $new->setImageFormat($this->_image->getType());
+
+            $new->roundCorners($round, $round);
+            $new->compositeImage($this->_image->imagick, Imagick::COMPOSITE_OVER, 1, 1);
+            $this->_image->imagick->clear();
+            $this->_image->imagick->addImage($new);
+            $new->destroy();
+        }
+
+        // If we have a background other than 'none' we need to
+        // compose two images together to make sure we *have* a background.
+        if ($this->_params['background'] != 'none') {
+            $size = $this->_image->getDimensions();
+            $new = new Imagick();
+            $new->newImage($size['width'],
+                           $size['height'],
+                           $this->_params['background']);
+            $new->setImageFormat($this->_image->getType());
+            $new->compositeImage($this->_image->imagick, Imagick::COMPOSITE_OVER, 0, 0);
+            $this->_image->imagick->clear();
+            $this->_image->imagick->addImage($new);
+            $new->destroy();
+        }
+
+        // Reset width/height since these might have changed
+        $this->_image->clearGeometry();
+
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/TextWatermark.php b/framework/Image/lib/Horde/Image/Effect/Imagick/TextWatermark.php
new file mode 100644 (file)
index 0000000..eff9b23
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Image effect for watermarking images with text for the im driver..
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_TextWatermark extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters for watermark effects:
+     *
+     *   text (required)  - The text of the watermark.
+     *   halign           - The horizontal placement
+     *   valign           - The vertical placement
+     *   font             - The font name or family to use
+     *   fontsize         - The size of the font to use
+     *                      (small, medium, large, giant)
+     *
+     * @var array
+     */
+    protected $_params = array('halign' => 'right',
+                               'valign' => 'bottom',
+                               'font' => 'courier',
+                               'fontsize' => 'small');
+
+    /**
+     * Add the watermark
+     *
+     */
+    public function apply()
+    {
+        /* Determine placement on image */
+        switch ($this->_params['valign']) {
+        case 'bottom':
+            $v = 'south';
+            break;
+        case 'center':
+            $v = 'center';
+            break;
+        default:
+            $v = 'north';
+        }
+
+        switch ($this->_params['halign']) {
+        case 'right':
+            $h = 'east';
+            break;
+        case 'center':
+            $h = 'center';
+            break;
+        default:
+            $h = 'west';
+
+        }
+        if (($v == 'center' && $h != 'center') ||
+            ($v == 'center' && $h == 'center')) {
+            $gravity = $h;
+        } elseif ($h == 'center' && $v != 'center') {
+            $gravity = $v;
+        } else {
+            $gravity = $v . $h;
+        }
+        /* Determine font point size */
+        $point = $this->_image->getFontSize($this->_params['fontsize']);
+
+        //@TODO:
+        throw new Horde_Image_Exception('Not Yet Implemented.');
+    }
+
+}
\ No newline at end of file
index 94fd3e8..2202774 100644 (file)
@@ -20,15 +20,14 @@ class Horde_Image_Imagick extends Horde_Image
         if (Util::loadExtension('imagick')) {
             ini_set('imagick.locale_fix', 1);
             $this->_imagick = new Imagick();
-            $this->_width = max(array($this->_width, 1));
-            $this->_height = max(array($this->_height, 1));
             if (!empty($params['filename'])) {
                 $this->loadFile($params['filename']);
             } elseif(!empty($params['data'])) {
                 $this->loadString(md5($params['data']), $params['data']);
             } else {
+                $this->_width = max(array($this->_width, 1));
+                $this->_height = max(array($this->_height, 1));
                 $this->_imagick->newImage($this->_width, $this->_height, $this->_background);
-                $this->_data = $this->_imagick->getImageBlob();
             }
             $this->_imagick->setImageFormat($this->_type);
         }
@@ -65,9 +64,12 @@ class Horde_Image_Imagick extends Horde_Image
     public function loadFile($filename)
     {
         // parent function loads image data into $this->_data
-        // @TODO: Can we clear the _data variable to save memory?
         parent::loadFile($filename);
-        $this->loadFile($this->_data);
+        $this->_imagick->clear();
+        $this->_imagick->readImageBlob($this->_data);
+        $this->_imagick->setFormat($this->_type);
+        $this->_imagick->setIteratorIndex(0);
+        unset($this->_data);
     }
 
     /*
@@ -102,8 +104,7 @@ class Horde_Image_Imagick extends Horde_Image
         } else {
             $this->_imagick->thumbnailImage($width, $height, $ratio);
         }
-        $this->_width = 0;
-        $this->_height = 0;
+        $this->clearGeometry();
     }
 
     /**
@@ -111,13 +112,14 @@ class Horde_Image_Imagick extends Horde_Image
      * variables only cache geometry until it changes, then they go
      * to zero.
      *
+     * @return array of geometry information.
      */
     public function getDimensions()
     {
         if ($this->_height == 0 && $this->_width == 0) {
             try {
                 $size = $this->_imagick->getImageGeometry();
-            catch (ImagickException $e) {
+            catch (ImagickException $e) {
                 //@TODO - Rethrow as Horde_Image_Exception
             }
 
@@ -130,4 +132,289 @@ class Horde_Image_Imagick extends Horde_Image
 
     }
 
-}
\ No newline at end of file
+    /**
+     * Crop the current image.
+     *
+     * @param integer $x1  x for the top left corner
+     * @param integer $y1  y for the top left corner
+     * @param integer $x2  x for the bottom right corner of the cropped image.
+     * @param integer $y2  y for the bottom right corner of the cropped image.
+     */
+    public function crop($x1, $y1, $x2, $y2)
+    {
+        $result = $this->_imagick->cropImage($x2 - $x1, $y2 - $y1, $x1, $y1);
+        $this->_imagick->setImagePage(0, 0, 0, 0);
+        $this->clearGeometry();
+    }
+
+    /**
+     * Rotate the current image.
+     *
+     * @param integer $angle       The angle to rotate the image by,
+     *                             in the clockwise direction.
+     * @param integer $background  The background color to fill any triangles.
+     */
+    public function rotate($angle, $background = 'white')
+    {
+        $this->_imagick->rotateImage($background, $angle);
+        $this->clearGeometry();
+    }
+
+    /**
+     * Flip the current image.
+     */
+    public function flip()
+    {
+        $this->_imagick->flipImage();
+    }
+
+    /**
+     * Mirror the current image.
+     */
+    public function mirror()
+    {
+        $this->_imagick->flopImage();
+    }
+
+    /**
+     * Convert the current image to grayscale.
+     */
+    public function grayscale()
+    {
+        $this->_imagick->setImageColorSpace(Imagick::COLORSPACE_GRAY);
+    }
+
+    /**
+     * Sepia filter.
+     *
+     * @param integer $threshold  Extent of sepia effect.
+     */
+    public function sepia($threshold =  85)
+    {
+        $this->_imagick->sepiaToneImage($threshold);
+    }
+
+    /**
+     * Draws a text string on the image in a specified location, with
+     * the specified style information.
+     *
+     * @TODO: Need to differentiate between the stroke (border) and the fill color,
+     *        but this is a BC break, since we were just not providing a border.
+     *
+     * @param string  $text       The text to draw.
+     * @param integer $x          The left x coordinate of the start of the text string.
+     * @param integer $y          The top y coordinate of the start of the text string.
+     * @param string  $font       The font identifier you want to use for the text.
+     * @param string  $color      The color that you want the text displayed in.
+     * @param integer $direction  An integer that specifies the orientation of the text.
+     * @param string  $fontsize   Size of the font (small, medium, large, giant)
+     */
+    public function text($string, $x, $y, $font = '', $color = 'black', $direction = 0, $fontsize = 'small')
+    {
+        $fontsize = self::getFontSize($fontsize);
+        $pixel = new ImagickPixel($color);
+        $draw = new ImagickDraw();
+        $draw->setFillColor($pixel);
+        if (!empty($font)) {
+            $draw->setFont($font);
+        }
+        $draw->setFontSize($fontsize);
+        $draw->setGravity(Imagick::GRAVITY_NORTHWEST);
+        $res = $this->_imagick->annotateImage($draw, $x, $y, $direction, $string);
+        $draw->destroy();
+    }
+
+    /**
+     * Draw a circle.
+     *
+     * @param integer $x     The x coordinate of the centre.
+     * @param integer $y     The y coordinate of the centre.
+     * @param integer $r     The radius of the circle.
+     * @param string $color  The line color of the circle.
+     * @param string $fill   The color to fill the circle.
+     */
+    public function circle($x, $y, $r, $color, $fill = 'none')
+    {
+        $draw = new ImagickDraw();
+        $draw->setFillColor(new ImagickPixel($fill));
+        $draw->setStrokeColor(new ImagickPixel($color));
+        $draw->circle($x, $y, $r + $x, $y);
+        $res = $this->_imagick->drawImage($draw);
+        $draw->destroy();
+    }
+
+    /**
+     * Draw a polygon based on a set of vertices.
+     *
+     * @param array $vertices  An array of x and y labeled arrays
+     *                         (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
+     * @param string $color    The color you want to draw the polygon with.
+     * @param string $fill     The color to fill the polygon.
+     */
+    public function polygon($verts, $color, $fill = 'none')
+    {
+        $draw = new ImagickDraw();
+        $draw->setFillColor(new ImagickPixel($fill));
+        $draw->setStrokeColor(new ImagickPixel($color));
+        $draw->polygon($verts);
+        $res = $this->_imagick->drawImage($draw);
+        $draw->destroy();
+    }
+
+    /**
+     * Draw a rectangle.
+     *
+     * @param integer $x       The left x-coordinate of the rectangle.
+     * @param integer $y       The top y-coordinate of the rectangle.
+     * @param integer $width   The width of the rectangle.
+     * @param integer $height  The height of the rectangle.
+     * @param string $color    The line color of the rectangle.
+     * @param string $fill     The color to fill the rectangle.
+     */
+    public function rectangle($x, $y, $width, $height, $color, $fill = 'none')
+    {
+        $draw = new ImagickDraw();
+        $draw->setStrokeColor(new ImagickPixel($color));
+        $draw->setFillColor(new ImagickPixel($fill));
+        $draw->rectangle($x, $y, $x + $width, $y + $height);
+        $res = $this->_imagick->drawImage($draw);
+        $draw->destroy();
+    }
+
+    /**
+     * Draw a rounded rectangle.
+     *
+     * @param integer $x       The left x-coordinate of the rectangle.
+     * @param integer $y       The top y-coordinate of the rectangle.
+     * @param integer $width   The width of the rectangle.
+     * @param integer $height  The height of the rectangle.
+     * @param integer $round   The width of the corner rounding.
+     * @param string  $color   The line color of the rectangle.
+     * @param string  $fill    The color to fill the rounded rectangle with.
+     */
+    public function roundedRectangle($x, $y, $width, $height, $round, $color, $fill)
+    {
+        $draw = new ImagickDraw();
+        $draw->setStrokeColor(new ImagickPixel($color));
+        $draw->setFillColor(new ImagickPixel($fill));
+        $draw->roundRectangle($x, $y, $x + $width, $y + $height, $round, $round);
+        $res = $this->_imagick->drawImage($draw);
+    }
+
+    /**
+     * Draw a line.
+     *
+     * @param integer $x0     The x coordinate of the start.
+     * @param integer $y0     The y coordinate of the start.
+     * @param integer $x1     The x coordinate of the end.
+     * @param integer $y1     The y coordinate of the end.
+     * @param string $color   The line color.
+     * @param string $width   The width of the line.
+     */
+    public function line($x0, $y0, $x1, $y1, $color = 'black', $width = 1)
+    {
+        $draw = new ImagickDraw();
+        $draw->setStrokeColor(new ImagickPixel($color));
+        $draw->setStrokeWidth($width);
+        $draw->line($x0, $y0, $x1, $y1);
+        $res = $this->_imagick->drawImage($draw);
+        $draw->destroy();
+    }
+
+    /**
+     * Draw a dashed line.
+     *
+     * @param integer $x0           The x co-ordinate of the start.
+     * @param integer $y0           The y co-ordinate of the start.
+     * @param integer $x1           The x co-ordinate of the end.
+     * @param integer $y1           The y co-ordinate of the end.
+     * @param string $color         The line color.
+     * @param string $width         The width of the line.
+     * @param integer $dash_length  The length of a dash on the dashed line
+     * @param integer $dash_space   The length of a space in the dashed line
+     */
+    public function dashedLine($x0, $y0, $x1, $y1, $color = 'black', $width = 1, $dash_length = 2, $dash_space = 2)
+    {
+        $draw = new ImagickDraw();
+        $draw->setStrokeColor(new ImagickPixel($color));
+        $draw->setStrokeWidth($width);
+        $draw->setStrokeDashArray(array($dash_length, $dash_space));
+        $draw->line($x0, $y0, $x1, $y1);
+        $res = $this->_imagick->drawImage($draw);
+        $draw->destroy();
+    }
+
+    /**
+     * Draw a polyline (a non-closed, non-filled polygon) based on a
+     * set of vertices.
+     *
+     * @param array $vertices  An array of x and y labeled arrays
+     *                         (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
+     * @param string $color    The color you want to draw the line with.
+     * @param string $width    The width of the line.
+     */
+    public function polyline($verts, $color, $width = 1)
+    {
+        $draw = new ImagickDraw();
+        $draw->setStrokeColor(new ImagickPixel($color));
+        $draw->setStrokeWidth($width);
+        $draw->setFillColor(new ImagickPixel('none'));
+        $draw->polyline($verts);
+        $res = $this->_imagick->drawImage($draw);
+        $draw->destroy();
+    }
+
+    /**
+     * Draw an arc.
+     *
+     * @TODO
+     *
+     * @param integer $x      The x coordinate of the centre.
+     * @param integer $y      The y coordinate of the centre.
+     * @param integer $r      The radius of the arc.
+     * @param integer $start  The start angle of the arc.
+     * @param integer $end    The end angle of the arc.
+     * @param string  $color  The line color of the arc.
+     * @param string  $fill   The fill color of the arc (defaults to none).
+     */
+    public function arc($x, $y, $r, $start, $end, $color = 'black', $fill = 'none')
+    {
+        throw new Horde_Image_Exception('Not Yet Implemented.');
+    }
+
+    public function applyEffects()
+    {
+        // noop for this driver.
+    }
+
+    public function __get($property)
+    {
+        switch ($property) {
+        case "imagick":
+            return $this->_imagick;
+        }
+    }
+
+    /**
+     * Utility function to wrap Imagick::borderImage so we can preserve any
+     * transparency in the image.
+     *
+     * @param Imagick &$image  The Imagick object to border.
+     * @param integer $width
+     * @param integer $height
+     *
+     * @return void
+     */
+    static public function borderImage(&$image, $color, $width, $height)
+    {
+         // Need to jump through these hoops in order to preserve any
+        // transparency.
+        $border = $image->clone();
+        $border->borderImage(new ImagickPixel($color), $width, $height);
+        $border->compositeImage($image, Imagick::COMPOSITE_COPY, $width, $height);
+        $image->clear();
+        $image->addImage($border);
+        $border->destroy();
+    }
+
+ }
\ No newline at end of file
index fc98652..08a6b2d 100644 (file)
@@ -55,6 +55,15 @@ http://pear.php.net/dtd/package-2.0.xsd">
         <file name="Border.php" role="php" />
         <file name="Composite.php" role="php" />
        </dir> <!-- /Horde/Image/Effect/im -->
+       <dir name="Imagick">
+        <file name="DropShadow.php" role="php" />
+        <file name="RoundCorners.php" role="php" />
+        <file name="TextWatermark.php" role="php" />
+        <file name="PhotoStack.php" role="php" />
+        <file name="PolaroidImage.php" role="php" />
+        <file name="Border.php" role="php" />
+        <file name="Composite.php" role="php" />
+       </dir> <!-- /Horde/Image/Effect/Imagick -->
        <dir name="Gd">
         <file name="DropShadow.php" role="php" />
         <file name="RoundCorners.php" role="php" />
@@ -71,6 +80,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
       <file name="rgb.php" role="php" />
       <file name="Svg.php" role="php" />
       <file name="Swf.php" role="php" />
+      <file name="Exception.php" role="php" />
      </dir> <!-- /Horde/Image -->
      <dir name="tests">
       <file name="gd.php" role="test" />
@@ -79,7 +89,6 @@ http://pear.php.net/dtd/package-2.0.xsd">
       <file name="swf.php" role="test" />
      </dir> <!-- /tests -->
      <file name="Image.php" role="php" />
-     <file name="Exception.php" role="php" />
     </dir> <!-- /Horde -->
    </dir> <!-- /lib -->
   </dir> <!-- / -->
@@ -114,6 +123,13 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <install name="lib/Horde/Image/Effect/Im/PolaroidImage.php" as="Horde/Image/Effect/Im/PolaroidImage.php" />
    <install name="lib/Horde/Image/Effect/Im/Border.php" as="Horde/Image/Effect/Im/Border.php" />
    <install name="lib/Horde/Image/Effect/Im/Composite.php" as="Horde/Image/Effect/Im/Composite.php" />
+   <install name="lib/Horde/Image/Effect/Imagick/DropShadow.php" as="Horde/Image/Effect/Imagick/DropShadow.php" />
+   <install name="lib/Horde/Image/Effect/Imagick/RoundCorners.php" as="Horde/Image/Effect/Imagick/RoundCorners.php" />
+   <install name="lib/Horde/Image/Effect/Imagick/TextWatermark.php" as="Horde/Image/Effect/Imagick/TextWatermark.php" />
+   <install name="lib/Horde/Image/Effect/Imagick/PhotoStack.php" as="Horde/Image/Effect/Imagick/PhotoStack.php" />
+   <install name="lib/Horde/Image/Effect/Imagick/PolaroidImage.php" as="Horde/Image/Effect/Imagick/PolaroidImage.php" />
+   <install name="lib/Horde/Image/Effect/Imagick/Border.php" as="Horde/Image/Effect/Imagick/Border.php" />
+   <install name="lib/Horde/Image/Effect/Imagick/Composite.php" as="Horde/Image/Effect/Imagick/Composite.php" />
    <install name="lib/Horde/Image/Effect/Gd/DropShadow.php" as="Horde/Image/Effect/Gd/DropShadow.php" />
    <install name="lib/Horde/Image/Effect/Gd/RoundCorners.php" as="Horde/Image/Effect/Gd/RoundCorners.php" />
    <install name="lib/Horde/Image/Effect/Gd/TextWatermark.php" as="Horde/Image/Effect/Gd/TextWatermark.php" />
@@ -127,6 +143,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <install name="lib/Horde/Image/rgb.php" as="Horde/Image/rgb.php" />
    <install name="lib/Horde/Image/Svg.php" as="Horde/Image/Svg.php" />
    <install name="lib/Horde/Image/Swf.php" as="Horde/Image/Swf.php" />
+   <install name="lib/Horde/Image/Exception.php" as="Horde/Image/Exception.php" />
    <install name="lib/Horde/Image.php" as="Horde/Image.php" />
   </filelist>
  </phprelease>