From: Michael J. Rubinsky Date: Thu, 28 May 2009 00:26:39 +0000 (-0400) Subject: Add Imagick native versions of all ImageMagick dependent effects X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=56761dbf8dc0dc86deb9b4752a6dec70eeada8e8;p=horde.git Add Imagick native versions of all ImageMagick dependent effects --- 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 index 000000000..aa47ac162 --- /dev/null +++ b/framework/Image/lib/Horde/Image/Effect/Imagick/Border.php @@ -0,0 +1,39 @@ + + * @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 index 000000000..e0f63ddd8 --- /dev/null +++ b/framework/Image/lib/Horde/Image/Effect/Imagick/Composite.php @@ -0,0 +1,58 @@ + + * @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 index 000000000..5dec7a362 --- /dev/null +++ b/framework/Image/lib/Horde/Image/Effect/Imagick/DropShadow.php @@ -0,0 +1,74 @@ + + * @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 index 000000000..f46cc93ed --- /dev/null +++ b/framework/Image/lib/Horde/Image/Effect/Imagick/PhotoStack.php @@ -0,0 +1,230 @@ + + * @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 index 000000000..08fd6476e --- /dev/null +++ b/framework/Image/lib/Horde/Image/Effect/Imagick/PolaroidImage.php @@ -0,0 +1,62 @@ + + * @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 index 000000000..f5b76efed --- /dev/null +++ b/framework/Image/lib/Horde/Image/Effect/Imagick/RoundCorners.php @@ -0,0 +1,73 @@ + + * @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 index 000000000..eff9b233c --- /dev/null +++ b/framework/Image/lib/Horde/Image/Effect/Imagick/TextWatermark.php @@ -0,0 +1,73 @@ + + * @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 diff --git a/framework/Image/lib/Horde/Image/Imagick.php b/framework/Image/lib/Horde/Image/Imagick.php index 94fd3e89b..220277400 100644 --- a/framework/Image/lib/Horde/Image/Imagick.php +++ b/framework/Image/lib/Horde/Image/Imagick.php @@ -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 diff --git a/framework/Image/package.xml b/framework/Image/package.xml index fc9865238..08a6b2dbd 100644 --- a/framework/Image/package.xml +++ b/framework/Image/package.xml @@ -55,6 +55,15 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + @@ -71,6 +80,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> + @@ -79,7 +89,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> - @@ -114,6 +123,13 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + @@ -127,6 +143,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> +