rounded_corners.inc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. /**
  3. * @file Routines for rounded corners
  4. */
  5. /**
  6. * Set radius for corner rounding
  7. *
  8. * Implementation of imagecache_hook_form()
  9. *
  10. * @param $action array of settings for this action
  11. * @return a form definition
  12. */
  13. function canvasactions_roundedcorners_form($action) {
  14. if (image_get_toolkit() != 'gd') {
  15. drupal_set_message('Rounded corners are not currently supported on all versions of imagemagick. This effect works best with GD image toolkit only.', 'warning');
  16. }
  17. drupal_add_js(drupal_get_path('module', 'imagecache_actions') . '/imagecache_actions.jquery.js');
  18. $defaults = array(
  19. 'radius' => '16',
  20. #'antialias' => TRUE,
  21. 'independent_corners_set' => array(
  22. 'independent_corners' => FALSE,
  23. 'radii' => array(
  24. 'tl' => 0,
  25. 'tr' => 0,
  26. 'bl' => 0,
  27. 'br' => 0,
  28. ),
  29. ),
  30. );
  31. $action = array_merge($defaults, (array) $action);
  32. $form['radius'] = array(
  33. '#type' => 'textfield',
  34. '#title' => t('radius'),
  35. '#default_value' => $action['radius'],
  36. '#size' => 2,
  37. );
  38. $form['independent_corners_set'] = array(
  39. '#type' => 'fieldset',
  40. '#title' => t('Individual Corner Values'),
  41. '#collapsible' => TRUE,
  42. '#collapsed' => (! $action['independent_corners_set']['independent_corners']),
  43. );
  44. $form['independent_corners_set']['independent_corners'] = array(
  45. '#type' => 'checkbox',
  46. '#title' => t('Set Corners Independently'),
  47. '#default_value' => $action['independent_corners_set']['independent_corners'],
  48. );
  49. $corners = array(
  50. 'tl' => t("Top Left Radius"),
  51. 'tr' => t("Top Right Radius"),
  52. 'bl' => t("Bottom Left Radius"),
  53. 'br' => t("Bottom Right Radius"),
  54. );
  55. // Loop over the four corners and create field elements for them.
  56. $form['independent_corners_set']['radii'] = array(
  57. '#type' => 'item',
  58. '#id' => 'independent-corners-set',
  59. '#states' => array(
  60. 'visible' => array(
  61. ':input[name="data[independent_corners_set][independent_corners]"]' => array('checked' => TRUE),
  62. ),
  63. ),
  64. );
  65. foreach ($corners as $attribute => $label) {
  66. $form['independent_corners_set']['radii'][$attribute] = array(
  67. '#type' => 'textfield',
  68. '#title' => $label,
  69. '#default_value' => 0 + $action['independent_corners_set']['radii'][$attribute],
  70. '#size' => 2,
  71. );
  72. }
  73. /*
  74. $form['antialias'] = array(
  75. '#type' => 'checkbox',
  76. '#title' => t('antialias'),
  77. '#return_value' => TRUE,
  78. '#default_value' => $action['antialias'],
  79. '#description' => t('Attempt antialias smoothing when drawing the corners'),
  80. );
  81. */
  82. $form['notes'] = array(
  83. '#markup' => t('
  84. Note: the rounded corners effect uses true alpha transparency masking.
  85. This means that this effect <b>will fail to be saved</b> on jpegs
  86. <em>unless</em> you either <ul>
  87. <li>convert the image to PNG (using the coloractions filter for that),</li>
  88. <li>define a canvas underneath it (using canvasactions-define-canvas) or</li>
  89. <li>underlay a solid color (using coloractions-alpha-flatten) or</li>
  90. <li>underlay a background image (canvasactions-underlay)</li>
  91. </ul>
  92. as a later part of this imagecache pipeline.
  93. <br/>
  94. '),
  95. );
  96. return $form;
  97. }
  98. function canvasactions_roundedcorners_effect($image, $action) {
  99. $independent_corners = !empty($action['independent_corners_set']['independent_corners']);
  100. if (!$independent_corners) {
  101. // set the independant corners to all be the same.
  102. $corners = array('tl', 'tr', 'bl', 'br');
  103. foreach ($corners as $key) {
  104. // Use the all-the-same radius setting.
  105. $action['independent_corners_set']['radii'][$key] = $action['radius'];
  106. }
  107. }
  108. return image_toolkit_invoke('roundedcorners', $image, array($action));
  109. }
  110. /**
  111. * Trim rounded corners off an image, using an anti-aliasing algorithm.
  112. *
  113. * Implementation of hook_image()
  114. *
  115. * Note, this is not image toolkit-agnostic yet! It just assumes GD.
  116. * We can abstract it out once we have something else to abstract to.
  117. * In the meantime just don't.
  118. *
  119. * 'handcoded' rounded corners logic contributed by donquixote 2009-08-31
  120. *
  121. * @param $image
  122. * @param $action
  123. */
  124. function image_gd_roundedcorners($image, $action) {
  125. // Read settings.
  126. $width = $image->info['width'];
  127. $height = $image->info['height'];
  128. $radius = $action['radius'];
  129. $independent_corners = !empty($action['independent_corners_set']['independent_corners']);
  130. $corners = array('tl', 'tr', 'bl', 'br');
  131. $im = &$image->resource;
  132. // Prepare drawing on the alpha channel.
  133. imagesavealpha($im, TRUE);
  134. imagealphablending($im, FALSE);
  135. foreach ($corners as $key) {
  136. if ($independent_corners && isset($action['independent_corners_set']['radii'][$key])) {
  137. $r = $action['independent_corners_set']['radii'][$key];
  138. }
  139. else {
  140. // Use the all-the-same radius setting.
  141. $r = $radius;
  142. }
  143. // key can be 'tl', 'tr', 'bl', 'br'.
  144. $is_bottom = ($key{0} == 'b');
  145. $is_right = ($key{1} == 'r');
  146. // dx and dy are in "continuous coordinates",
  147. // and mark the distance of the pixel middle to the image border.
  148. for ($dx = .5; $dx < $r; ++$dx) {
  149. for ($dy = .5; $dy < $r; ++$dy) {
  150. // ix and iy are in discrete pixel indices,
  151. // counting from the top left
  152. $ix = floor($is_right ? $width -$dx : $dx);
  153. $iy = floor($is_bottom ? $height -$dy : $dy);
  154. // Color lookup at ($ix, $iy).
  155. $color_ix = imagecolorat($im, $ix, $iy);
  156. $color = imagecolorsforindex($im, $color_ix);
  157. // Do not process opacity if transparency is 100%. Just jump...
  158. // Opacity is always 0 on a transparent source pixel.
  159. if ($color['alpha'] != 127) {
  160. $opacity = _canvasactions_roundedcorners_pixel_opacity($dx, $dy, $r);
  161. if ($opacity >= 1) {
  162. // we can finish this row,
  163. // all following pixels will be fully opaque.
  164. break;
  165. }
  166. if (isset($color['alpha'])) {
  167. $color['alpha'] = 127 - round($opacity * (127 - $color['alpha']));
  168. }
  169. else {
  170. $color['alpha'] = 127 - round($opacity * 127);
  171. }
  172. // Value should not be more than 127, and not less than 0.
  173. $color['alpha'] = ($color['alpha'] > 127) ? 127 : (($color['alpha'] < 0) ? 0 : $color['alpha']);
  174. }
  175. $color_ix = imagecolorallocatealpha($im, $color['red'], $color['green'], $color['blue'], $color['alpha']);
  176. imagesetpixel($im, $ix, $iy, $color_ix);
  177. }
  178. }
  179. }
  180. return TRUE;
  181. }
  182. /**
  183. * Calculate the transparency value for a rounded corner pixel
  184. *
  185. * @param $x
  186. * distance from pixel center to image border (left or right)
  187. * should be an integer + 0.5
  188. *
  189. * @param $y
  190. * distance from pixel center to image border (top or bottom)
  191. * should be an integer + 0.5
  192. *
  193. * @param $r
  194. * radius of the rounded corner
  195. * should be an integer
  196. *
  197. * @return float
  198. * opacity value between 0 (fully transparent) and 1 (fully opaque).
  199. *
  200. * OPTIMIZE HERE! This is a really tight loop, potentially getting called
  201. * thousands of times
  202. */
  203. function _canvasactions_roundedcorners_pixel_opacity($x, $y, $r) {
  204. if ($x < 0 || $y < 0) {
  205. return 0;
  206. }
  207. else if ($x > $r || $y > $r) {
  208. return 1;
  209. }
  210. $dist_2 = ($r -$x) * ($r -$x) + ($r -$y) * ($r -$y);
  211. $r_2 = $r * $r;
  212. if ($dist_2 > ($r + 0.8) * ($r + 0.8)) {
  213. return 0;
  214. }
  215. else if ($dist_2 < ($r -0.8) * ($r -0.8)) {
  216. return 1;
  217. }
  218. else {
  219. // this pixel needs special analysis.
  220. // thanks to a quite efficient algorithm, we can afford 10x antialiasing :)
  221. $opacity = 0.5;
  222. if ($x > $y) {
  223. // cut the pixel into 10 vertical "stripes"
  224. for ($dx = -0.45; $dx < 0.5; $dx += 0.1) {
  225. // find out where the rounded corner edge intersects with the stripe
  226. // this is plain triangle geometry.
  227. $dy = $r - $y - sqrt($r_2 - ($r -$x -$dx) * ($r -$x -$dx));
  228. $dy = ($dy > 0.5) ? 0.5 : (($dy < -0.5) ? -0.5 : $dy);
  229. // count the opaque part of the stripe.
  230. $opacity -= 0.1 * $dy;
  231. }
  232. }
  233. else {
  234. // cut the pixel into 10 horizontal "stripes"
  235. for ($dy = -0.45; $dy < 0.5; $dy += 0.1) {
  236. // this is the math:
  237. // ($r-$x-$dx)^2 + ($r-$y-$dy)^2 = $r^2
  238. // $dx = $r - $x - sqrt($r^2 - ($r-$y-$dy)^2)
  239. $dx = $r - $x - sqrt($r_2 - ($r -$y -$dy) * ($r -$y -$dy));
  240. $dx = ($dx > 0.5) ? 0.5 : (($dx < -0.5) ? -0.5 : $dx);
  241. $opacity -= 0.1 * $dx;
  242. }
  243. }
  244. return ($opacity < 0) ? 0 : (($opacity > 1) ? 1 : $opacity);
  245. }
  246. }
  247. /**
  248. * imageapi_roundedcorners
  249. */
  250. function image_imagemagick_roundedcorners($image, $action) {
  251. // Based on the imagemagick documentation.
  252. // http://www.imagemagick.org/Usage/thumbnails/#rounded
  253. // Create arc cut-outs, then mask them.
  254. // Draw black triangles and white circles.
  255. // draw circle is center: x,y, and a point on the perimeter
  256. $corners = array('tl', 'tr', 'bl', 'br');
  257. $radii = $action['independent_corners_set']['radii'];
  258. $width = $image->info['width'];
  259. $height = $image->info['height'];
  260. $tl = $radii['tl'];
  261. $tr = $radii['tr'];
  262. $bl = $radii['bl'];
  263. $br = $radii['br'];
  264. $drawmask = '';
  265. if ($tl) {
  266. $drawmask .= " fill black polygon 0,0 0,{$tl} {$tl},0";
  267. $drawmask .= " fill white circle {$tl},{$tl} {$tl},0";
  268. }
  269. if ($tr) {
  270. $right = $width -$tr;
  271. $drawmask .= " fill black polygon {$right},0 {$width},0 {$width},{$tr}";
  272. $drawmask .= " fill white circle {$right},{$tr} {$right},0";
  273. }
  274. if ($bl) {
  275. $bottom = $height -$bl;
  276. $drawmask .= " fill black polygon 0,{$bottom} 0,{$height} {$bl},{$height}";
  277. $drawmask .= " fill white circle {$bl},{$bottom} 0,{$bottom}";
  278. }
  279. if ($br) {
  280. $bottom = $height -$br;
  281. $right = $width -$br;
  282. $drawmask .= " fill black polygon {$right},{$height} {$width},{$bottom} {$width},{$height}";
  283. $drawmask .= " fill white circle {$right},{$bottom} {$width},{$bottom}";
  284. }
  285. $draw = ' -draw ' . escapeshellarg($drawmask);
  286. $compose = ' ' . escapeshellcmd('(') . " +clone -threshold -1 $draw " . escapeshellcmd(')') . ' +matte -compose CopyOpacity -composite ';
  287. $image->ops[] = $compose;
  288. return TRUE;
  289. }
  290. /**
  291. * Implementation of theme_hook() for imagecache_ui.module
  292. */
  293. function theme_canvasactions_roundedcorners($variables) {
  294. $element = $variables['element'];
  295. $data = $element['#value'];
  296. if (!empty($data['independent_corners_set']['independent_corners'])) {
  297. $dimens = join('px | ', $data['independent_corners_set']['radii']) . 'px';
  298. }
  299. else {
  300. $dimens = "Radius: {$data['radius']}px";
  301. }
  302. return $dimens;
  303. }