PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/Image/lib/Horde/Image/Effect/Imagick/PhotoStack.php

https://github.com/sgtcarneiro/horde
PHP | 217 lines | 143 code | 14 blank | 60 comment | 22 complexity | f48e1c92a88fc6f441896df53f47f38c MD5 | raw file
  1. <?php
  2. /**
  3. * Effect for composing multiple images into a single image.
  4. *
  5. * Copyright 2007-2011 The Horde Project (http://www.horde.org/)
  6. *
  7. * The technique for the Polaroid-like stack using the Imagick extension is
  8. * credited to Mikko Koppanen and is documented at http://valokuva.org
  9. *
  10. * @author Michael J. Rubinsky <mrubinsk@horde.org>
  11. * @package Image
  12. */
  13. class Horde_Image_Effect_Imagick_PhotoStack extends Horde_Image_Effect
  14. {
  15. /**
  16. * Valid parameters for the stack effect
  17. *
  18. * images - An array of Horde_Image objects to stack. Images
  19. * are stacked in a FIFO manner, so that the top-most
  20. * image is the last one in this array.
  21. *
  22. * type - Determines the style for the composition.
  23. * 'plain' or 'polaroid' are supported.
  24. *
  25. * resize_height - The height that each individual thumbnail
  26. * should be resized to before composing on the image.
  27. *
  28. * padding - How much padding should we ensure is left around
  29. * the active image area?
  30. *
  31. * background - The background canvas color - this is used as the
  32. * color to set any padding to.
  33. *
  34. * bordercolor - If using type 'plain' this sets the color of the
  35. * border that each individual thumbnail gets.
  36. *
  37. * borderwidth - If using type 'plain' this sets the width of the
  38. * border on each individual thumbnail.
  39. *
  40. * offset - If using type 'plain' this determines the amount of
  41. * x and y offset to give each successive image when
  42. * it is placed on the top of the stack.
  43. *
  44. * @var array
  45. */
  46. protected $_params = array('type' => 'plain',
  47. 'resize_height' => '150',
  48. 'padding' => 0,
  49. 'background' => 'none',
  50. 'bordercolor' => '#333',
  51. 'borderwidth' => 1,
  52. 'borderrounding' => 10,
  53. 'offset' => 5);
  54. /**
  55. * Create the photo_stack
  56. *
  57. */
  58. public function apply()
  59. {
  60. $i = 1;
  61. $cnt = count($this->_params['images']);
  62. if ($cnt <=0) {
  63. throw new Horde_Image_Exception('No Images provided.');
  64. }
  65. if (!method_exists($this->_image->imagick, 'polaroidImage') ||
  66. !method_exists($this->_image->imagick, 'trimImage')) {
  67. throw new Horde_Image_Exception('Your version of Imagick is not compiled against a recent enough ImageMagick library to use the PhotoStack effect.');
  68. }
  69. $imgs = array();
  70. $length = 0;
  71. switch ($this->_params['type']) {
  72. case 'plain':
  73. case 'rounded':
  74. $haveBottom = false;
  75. // First, we need to resize the top image to get the dimensions
  76. // for the rest of the stack.
  77. $topimg = new Imagick();
  78. $topimg->clear();
  79. $topimg->readImageBlob($this->_params['images'][$cnt - 1]->raw());
  80. $topimg->thumbnailImage(
  81. $this->_params['resize_height'],
  82. $this->_params['resize_height'],
  83. true);
  84. if ($this->_params['type'] == 'rounded') {
  85. $topimg = $this->_roundBorder($topimg);
  86. }
  87. $size = $topimg->getImageGeometry();
  88. foreach ($this->_params['images'] as $image) {
  89. $imgk= new Imagick();
  90. $imgk->clear();
  91. $imgk->readImageBlob($image->raw());
  92. // Either resize the thumbnail to match the top image or we *are*
  93. // the top image already.
  94. if ($i++ <= $cnt) {
  95. $imgk->thumbnailImage($size['width'], $size['height'], false);
  96. } else {
  97. $imgk->destroy();
  98. $imgk = $topimg->clone();
  99. }
  100. if ($this->_params['type'] == 'rounded') {
  101. $imgk = $this->_roundBorder($imgk);
  102. } else {
  103. $imgk->borderImage($this->_params['bordercolor'],
  104. $this->_params['borderwidth'],
  105. $this->_params['borderwidth']);
  106. }
  107. // Only shadow the bottom image for 'plain' stacks
  108. if (!$haveBottom) {
  109. $shad = $imgk->clone();
  110. $shad->setImageBackgroundColor(new ImagickPixel('black'));
  111. $shad->shadowImage(80, 4, 0, 0);
  112. $shad->compositeImage($imgk, Imagick::COMPOSITE_OVER, 0, 0);
  113. $imgk->clear();
  114. $imgk->addImage($shad);
  115. $shad->destroy();
  116. $haveBottom = true;
  117. }
  118. // Get the geometry of the image and remember the largest.
  119. $geo = $imgk->getImageGeometry();
  120. $length = max(
  121. $length,
  122. sqrt(pow($geo['height'], 2) + pow($geo['width'], 2)));
  123. $imgs[] = $imgk;
  124. }
  125. break;
  126. case 'polaroid':
  127. foreach ($this->_params['images'] as $image) {
  128. //@TODO: instead of doing $image->raw(), we might be able to clone
  129. // the imagick object if we can do it cleanly might
  130. // be faster, less memory intensive?
  131. $imgk= new Imagick();
  132. $imgk->clear();
  133. $imgk->readImageBlob($image->raw());
  134. $imgk->thumbnailImage($this->_params['resize_height'],
  135. $this->_params['resize_height'],
  136. true);
  137. $imgk->setImageBackgroundColor('black');
  138. if ($i++ == $cnt) {
  139. $angle = 0;
  140. } else {
  141. $angle = mt_rand(1, 45);
  142. if (mt_rand(1, 2) % 2 === 0) {
  143. $angle = $angle * -1;
  144. }
  145. }
  146. $result = $imgk->polaroidImage(new ImagickDraw(), $angle);
  147. // Get the geometry of the image and remember the largest.
  148. $geo = $imgk->getImageGeometry();
  149. $length = max(
  150. $length,
  151. sqrt(pow($geo['height'], 2) + pow($geo['width'], 2)));
  152. $imgs[] = $imgk;
  153. }
  154. break;
  155. }
  156. // Make sure the background canvas is large enough to hold it all.
  157. $this->_image->imagick->thumbnailImage($length * 1.5 + 20,
  158. $length * 1.5 + 20);
  159. // x and y offsets.
  160. $xo = $yo = (count($imgs) + 1) * $this->_params['offset'];
  161. foreach ($imgs as $image) {
  162. if ($this->_params['type'] == 'polaroid') {
  163. $xo = mt_rand(1, $this->_params['resize_height'] / 2);
  164. $yo = mt_rand(1, $this->_params['resize_height'] / 2);
  165. } elseif ($this->_params['type'] == 'plain' ||
  166. $this->_params['type'] == 'rounded') {
  167. $xo -= $this->_params['offset'];
  168. $yo -= $this->_params['offset'];
  169. }
  170. $this->_image->imagick->compositeImage($image, Imagick::COMPOSITE_OVER, $xo, $yo);
  171. $image->removeImage();
  172. $image->destroy();
  173. }
  174. // Trim the canvas before resizing to keep the thumbnails as large
  175. // as possible.
  176. $this->_image->imagick->trimImage(0);
  177. if ($this->_params['padding'] || $this->_params['background'] != 'none') {
  178. $this->_image->imagick->borderImage(
  179. new ImagickPixel($this->_params['background']),
  180. $this->_params['padding'],
  181. $this->_params['padding']);
  182. }
  183. return true;
  184. }
  185. private function _roundBorder($image)
  186. {
  187. $context = array('tmpdir' => $this->_image->getTmpDir());
  188. $size = $image->getImageGeometry();
  189. $new = new Horde_Image_Imagick(array(), $context);
  190. $new->loadString($image->getImageBlob());
  191. $image->destroy();
  192. $new->addEffect('RoundCorners', array('border' => 2, 'bordercolor' => '#111'));
  193. $new->applyEffects();
  194. $return = new Imagick();
  195. $return->newImage($size['width'] + $this->_params['borderwidth'],
  196. $size['height'] + $this->_params['borderwidth'],
  197. $this->_params['bordercolor']);
  198. $return->setImageFormat($this->_image->getType());
  199. $return->clear();
  200. $return->readImageBlob($new->raw());
  201. return $return;
  202. }
  203. }