canvasactions.inc 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693
  1. <?php
  2. /**
  3. * @file Helper functions for the canvas actions for imagecache
  4. *
  5. * @author Dan Morrison http://coders.co.nz
  6. *
  7. * Individually configurable rounded corners logic contributed by canaryMason
  8. * 2009 03 https://drupal.org/node/402112
  9. *
  10. * Better algorithm for trimming rounded corners from donquixote
  11. * 2009 09 https://drupal.org/node/564036
  12. *
  13. */
  14. if (!function_exists('image_overlay')) {
  15. module_load_include('inc', 'imagecache_actions', 'image_overlay');
  16. }
  17. if (!function_exists('imagecache_actions_pos_form')) {
  18. module_load_include('inc', 'imagecache_actions', 'utility-form');
  19. }
  20. if (!function_exists('imagecache_actions_keyword_filter')) {
  21. module_load_include('inc', 'imagecache_actions', 'utility');
  22. }
  23. /**
  24. * Image effect form callback for the image mask effect.
  25. *
  26. * @param array $data
  27. * The current configuration for this image effect.
  28. *
  29. * @return array
  30. * The form definition for this effect.
  31. */
  32. function canvasactions_imagemask_form(array $data) {
  33. // @todo: add offset/positioning/scaling support - currently the mask is applied to the supplied image without resizing and positioned at (0,0)
  34. $form = array();
  35. $form['effect_help_text'] = array(
  36. '#type' => 'item',
  37. '#title' => t('Image mask'),
  38. '#description' => t('This effect will add (or replace) a transparency channel to your image. The mask file should be a grayscale image where black is fully transparent and white is fully opaque. The referenced mask will be applied to the top left of the image.'),
  39. );
  40. $form['path'] = array(
  41. '#type' => 'textfield',
  42. '#title' => t('Mask file name'),
  43. '#default_value' => isset($data['path']) ? $data['path'] : '',
  44. '#description' => imagecache_actions_file_field_description(),
  45. '#element_validate' => array('imagecache_actions_validate_file'),
  46. );
  47. return $form;
  48. }
  49. /**
  50. * Implements theme_hook() for the image mask effect summary.
  51. *
  52. * @param array $variables
  53. * An associative array containing:
  54. * - data: The current configuration for this image effect.
  55. *
  56. * @return string
  57. * The HTML for the summary of this image effect.
  58. * @ingroup themeable
  59. */
  60. function theme_canvasactions_imagemask_summary(array $variables) {
  61. $data = $variables['data'];
  62. return 'file: ' . $data['path'];
  63. }
  64. /**
  65. * Image effect callback for the image mask effect.
  66. *
  67. * @param stdClass $image
  68. * @param array $data
  69. *
  70. * @return bool
  71. * true on success, false otherwise.
  72. */
  73. function canvasactions_imagemask_effect(stdClass $image, array $data) {
  74. $mask = imagecache_actions_image_load($data['path'], $image->toolkit);
  75. if ($mask) {
  76. // @todo: (sydneyshan) Consider best way to add offset support - I assume we
  77. // would position the mask somewhere (top/left/offset px etc) and choose if
  78. // the surrounding area is white or black (opaque or transparent) using an
  79. // extra form element (radio). Assess existing positioning code first to
  80. // reduce duplication of code. Pass the results to the following function as
  81. // array($mask, $data). Perhaps add a 'scale mask to fit image'/'scale image
  82. // to fit mask'/'no scale' radio group?
  83. return image_toolkit_invoke('imagemask', $image, array($mask));
  84. }
  85. return FALSE;
  86. }
  87. /**
  88. * GD toolkit specific implementation of the image mask effect.
  89. *
  90. * @param stdClass $image
  91. * Image object containing the GD image resource to operate on.
  92. * @param stdClass $mask
  93. * An image object containing the image to use as mask.
  94. *
  95. * @return bool
  96. * true on success, false otherwise.
  97. */
  98. function image_gd_imagemask(stdClass $image, stdClass $mask) {
  99. $newPicture = imagecreatetruecolor($image->info['width'], $image->info['height']);
  100. imagesavealpha($newPicture, TRUE);
  101. imagealphablending($newPicture, TRUE);
  102. $transparent = imagecolorallocatealpha($newPicture, 0, 0, 0, 127);
  103. imagefill($newPicture, 0, 0, $transparent);
  104. // Perform pixel-based alpha map application.
  105. for ($x = 0; $x < $image->info['width']; $x++) {
  106. for ($y = 0; $y < $image->info['height']; $y++) {
  107. // Deal with images with mismatched sizes
  108. if ($x >= $mask->info['width'] || $y >= $mask->info['height']) {
  109. imagesetpixel($newPicture, $x, $y, $transparent);
  110. }
  111. else {
  112. $alpha = imagecolorsforindex($mask->resource, imagecolorat($mask->resource, $x, $y));
  113. $alpha = 127 - floor($alpha['red'] / 2);
  114. $color = imagecolorsforindex($image->resource, imagecolorat($image->resource, $x, $y));
  115. imagesetpixel($newPicture, $x, $y, imagecolorallocatealpha($newPicture, $color['red'], $color['green'], $color['blue'], $alpha));
  116. }
  117. }
  118. }
  119. // Copy back to original picture.
  120. imagedestroy($image->resource);
  121. $image->resource = $newPicture;
  122. return TRUE;
  123. }
  124. /**
  125. * Imagemagick toolkit specific implementation of the image mask effect.
  126. *
  127. * @param stdClass $image
  128. * Image object containing the image resource to operate on.
  129. * @param stdClass $mask
  130. * An image object containing the image to use as mask.
  131. *
  132. * @return bool
  133. * true on success, false otherwise.
  134. */
  135. function image_imagemagick_imagemask(stdClass $image, stdClass $mask) {
  136. $image->ops[] = escapeshellarg($mask->source);
  137. $image->ops[] = '-alpha Off -compose CopyOpacity -composite';
  138. return TRUE;
  139. }
  140. /**
  141. * Image effect form callback for the define canvas effect.
  142. *
  143. * @param array $data
  144. * The current configuration for this image effect.
  145. *
  146. * @return array
  147. * The form definition for this effect.
  148. */
  149. function canvasactions_definecanvas_form(array $data) {
  150. module_load_include('inc', 'imagecache_actions', 'utility-color');
  151. $defaults = array(
  152. 'RGB' => array(
  153. 'HEX' => '#333333',
  154. ),
  155. 'under' => TRUE,
  156. 'exact' => array(
  157. 'width' => '',
  158. 'height' => '',
  159. 'xpos' => 'center',
  160. 'ypos' => 'center',
  161. ),
  162. 'relative' => array(
  163. 'leftdiff' => '',
  164. 'rightdiff' => '',
  165. 'topdiff' => '',
  166. 'bottomdiff' => '',
  167. ),
  168. );
  169. $data += $defaults;
  170. $form = array(
  171. 'RGB' => imagecache_rgb_form($data['RGB']),
  172. 'help' => array(
  173. '#markup' => t('Enter no color value for transparent. This will have the effect of adding clear margins around the image.'),
  174. '#prefix' => '<p>',
  175. '#suffix' => '</p>',
  176. ),
  177. 'under' => array(
  178. '#type' => 'checkbox',
  179. '#title' => t('Resize canvas <em>under</em> image (possibly cropping)'),
  180. '#default_value' => $data['under'],
  181. '#description' => t('If <em>not</em> set, this will create a solid flat layer, probably totally obscuring the source image'),
  182. ),
  183. );
  184. $form['info'] = array('#value' => t('Enter values in ONLY ONE of the below options. Either exact or relative. Most values are optional - you can adjust only one dimension as needed. If no useful values are set, the current base image size will be used.'));
  185. $form['exact'] = array(
  186. '#type' => 'fieldset',
  187. '#collapsible' => TRUE,
  188. '#title' => 'Exact size',
  189. 'help' => array(
  190. '#markup' => t('Set the canvas to a precise size, possibly cropping the image. Use to start with a known size.'),
  191. '#prefix' => '<p>',
  192. '#suffix' => '</p>',
  193. ),
  194. 'width' => array(
  195. '#type' => 'textfield',
  196. '#title' => t('Width'),
  197. '#default_value' => $data['exact']['width'],
  198. '#description' => t('Enter a value in pixels or percent'),
  199. '#size' => 5,
  200. ),
  201. 'height' => array(
  202. '#type' => 'textfield',
  203. '#title' => t('Height'),
  204. '#default_value' => $data['exact']['height'],
  205. '#description' => t('Enter a value in pixels or percent'),
  206. '#size' => 5,
  207. ),
  208. );
  209. $form['exact'] = array_merge($form['exact'], imagecache_actions_pos_form($data['exact']));
  210. if (!$data['exact']['width'] && !$data['exact']['height']) {
  211. $form['exact']['#collapsed'] = TRUE;
  212. }
  213. $form['relative'] = array(
  214. '#type' => 'fieldset',
  215. '#collapsible' => TRUE,
  216. '#title' => t('Relative size'),
  217. 'help' => array(
  218. '#markup' => t('Set the canvas to a relative size, based on the current image dimensions. Use to add simple borders or expand by a fixed amount. Negative values may crop the image.'),
  219. '#prefix' => '<p>',
  220. '#suffix' => '</p>',
  221. ),
  222. 'leftdiff' => array(
  223. '#type' => 'textfield',
  224. '#title' => t('left difference'),
  225. '#default_value' => $data['relative']['leftdiff'],
  226. '#size' => 6,
  227. '#description' => t('Enter an offset in pixels.'),
  228. ),
  229. 'rightdiff' => array(
  230. '#type' => 'textfield',
  231. '#title' => t('right difference'),
  232. '#default_value' => $data['relative']['rightdiff'],
  233. '#size' => 6,
  234. '#description' => t('Enter an offset in pixels.'),
  235. ),
  236. 'topdiff' => array(
  237. '#type' => 'textfield',
  238. '#title' => t('top difference'),
  239. '#default_value' => $data['relative']['topdiff'],
  240. '#size' => 6,
  241. '#description' => t('Enter an offset in pixels.'),
  242. ),
  243. 'bottomdiff' => array(
  244. '#type' => 'textfield',
  245. '#title' => t('bottom difference'),
  246. '#default_value' => $data['relative']['bottomdiff'],
  247. '#size' => 6,
  248. '#description' => t('Enter an offset in pixels.'),
  249. ),
  250. );
  251. if (!$data['relative']['leftdiff'] && !$data['relative']['rightdiff'] && !$data['relative']['topdiff'] && !$data['relative']['bottomdiff']) {
  252. $form['relative']['#collapsed'] = TRUE;
  253. }
  254. $form['#submit'][] = 'canvasactions_definecanvas_form_submit';
  255. return $form;
  256. }
  257. /** @noinspection PhpDocMissingThrowsInspection */
  258. /**
  259. * Implements theme_hook() for the define canvas effect summary.
  260. *
  261. * @param array $variables
  262. * An associative array containing:
  263. * - data: The current configuration for this image effect.
  264. *
  265. * @return string
  266. * The HTML for the summary of this image effect.
  267. * @ingroup themeable
  268. */
  269. function theme_canvasactions_definecanvas_summary(array $variables) {
  270. $data = $variables['data'];
  271. if ($data['exact']['width'] || $data['exact']['height']) {
  272. $w = !empty($data['exact']['width']) ? $data['exact']['width'] : '100%';
  273. $h = !empty($data['exact']['height']) ? $data['exact']['height'] : '100%';
  274. $x = !empty($data['exact']['xpos']) ? $data['exact']['xpos'] : '0';
  275. $y = !empty($data['exact']['ypos']) ? $data['exact']['ypos'] : '0';
  276. $output = "{$w}x{$h} ($x, $y)";
  277. }
  278. else {
  279. $output = ' left:' . $data['relative']['leftdiff'];
  280. $output .= ' right:' . $data['relative']['rightdiff'];
  281. $output .= ' top:' . $data['relative']['topdiff'];
  282. $output .= ' bottom:' . $data['relative']['bottomdiff'];
  283. }
  284. /** @noinspection PhpUnhandledExceptionInspection */
  285. $output .= theme('imagecacheactions_rgb', array('RGB' => $data['RGB']));
  286. $output .= ($data['under']) ? t(" <b>under</b> image ") : t(" <b>over</b> image ");
  287. return $output;
  288. }
  289. /**
  290. * Image effect callback for the define canvas effect.
  291. *
  292. * @param stdClass $image
  293. * @param array $data
  294. *
  295. * @return boolean
  296. * true on success, false otherwise.
  297. */
  298. function canvasactions_definecanvas_effect(stdClass $image, array $data) {
  299. // May be given either exact or relative dimensions.
  300. if ($data['exact']['width'] || $data['exact']['height']) {
  301. // Allows only one dimension to be used if the other is unset.
  302. if (!$data['exact']['width']) {
  303. $data['exact']['width'] = $image->info['width'];
  304. }
  305. if (!$data['exact']['height']) {
  306. $data['exact']['height'] = $image->info['height'];
  307. }
  308. $target_size['width'] = imagecache_actions_percent_filter($data['exact']['width'], $image->info['width']);
  309. $target_size['height'] = imagecache_actions_percent_filter($data['exact']['height'], $image->info['height']);
  310. $target_size['left'] = image_filter_keyword($data['exact']['xpos'], $target_size['width'], $image->info['width']);
  311. $target_size['top'] = image_filter_keyword($data['exact']['ypos'], $target_size['height'], $image->info['height']);
  312. }
  313. else {
  314. // Calculate relative size.
  315. $target_size['width'] = $image->info['width'] + ((int) $data['relative']['leftdiff']) + ((int) $data['relative']['rightdiff']);
  316. $target_size['height'] = $image->info['height'] + ((int) $data['relative']['topdiff']) + ((int) $data['relative']['bottomdiff']);
  317. $target_size['left'] = (int) $data['relative']['leftdiff'];
  318. $target_size['top'] = (int) $data['relative']['topdiff'];
  319. }
  320. // Convert from hex (as it is stored in the UI).
  321. if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) {
  322. $data['RGB'] = array_merge($data['RGB'], $deduced);
  323. }
  324. // All the math is done, now defer to the toolkit in use.
  325. $data['targetsize'] = $target_size;
  326. $success = image_toolkit_invoke('definecanvas', $image, array($data));
  327. if ($success) {
  328. $image->info['width'] = $target_size['width'];
  329. $image->info['height'] = $target_size['height'];
  330. }
  331. return $success;
  332. }
  333. /**
  334. * GD toolkit specific implementation of the define canvas effect.
  335. *
  336. * @param stdClass $image
  337. * @param array $data
  338. * The parameters for this effect. $data['targetsize'] is an array expected to
  339. * contain a width, height and a left, top.
  340. *
  341. * @return bool
  342. * true on success, false otherwise.
  343. */
  344. function image_gd_definecanvas(stdClass $image, array $data) {
  345. $target_size = $data['targetsize'];
  346. $RGB = $data['RGB'];
  347. $newcanvas = imagecreatetruecolor($target_size['width'], $target_size['height']);
  348. imagesavealpha($newcanvas, TRUE);
  349. imagealphablending($newcanvas, FALSE);
  350. imagesavealpha($image->resource, TRUE);
  351. if ($RGB['HEX']) {
  352. // Set color, allow it to define transparency, or assume opaque.
  353. $background = imagecolorallocatealpha($newcanvas, $RGB['red'], $RGB['green'], $RGB['blue'], $RGB['alpha']);
  354. }
  355. else {
  356. // No color, attempt transparency, assume white.
  357. $background = imagecolorallocatealpha($newcanvas, 255, 255, 255, 127);
  358. }
  359. imagefilledrectangle($newcanvas, 0, 0, $target_size['width'], $target_size['height'], $background);
  360. if ($data['under']) {
  361. $canvas_object = new stdClass();
  362. $canvas_object->resource = $newcanvas;
  363. $canvas_object->info = array(
  364. 'width' => $target_size['width'],
  365. 'height' => $target_size['height'],
  366. 'mime_type' => $image->info['mime_type'],
  367. 'extension' => $image->info['extension'],
  368. );
  369. $canvas_object->toolkit = $image->toolkit;
  370. image_overlay($image, $canvas_object, $target_size['left'], $target_size['top'], 100, TRUE);
  371. }
  372. else {
  373. $image->resource = $newcanvas;
  374. }
  375. return TRUE;
  376. }
  377. /**
  378. * Imagemagick toolkit specific implementation of the define canvas effect.
  379. *
  380. * @param stdClass $image
  381. * @param array $data
  382. * The parameters for this effect. $data['targetsize'] is an array expected to
  383. * contain a width, height and a left, top.
  384. *
  385. * @return bool
  386. * true on success, false otherwise.
  387. *
  388. * @see http://www.imagemagick.org/script/command-line-options.php#extent
  389. */
  390. function image_imagemagick_definecanvas(stdClass $image, $data) {
  391. // Reset any gravity settings from earlier effects.
  392. $image->ops[] = '-gravity None';
  393. $backgroundcolor = $data['RGB']['HEX'] != '' ? '#' . ltrim($data['RGB']['HEX'], '#') : 'None';
  394. $image->ops[] = '-background ' . escapeshellarg($backgroundcolor);
  395. $compose_operator = $data['under'] ? 'src-over' : 'dst-over';
  396. $image->ops[] = "-compose $compose_operator";
  397. $target_size = $data['targetsize'];
  398. $geometry = sprintf('%dx%d', $target_size['width'], $target_size['height']);
  399. if ($target_size['left'] || $target_size['top']) {
  400. $geometry .= sprintf('%+d%+d', -$target_size['left'], -$target_size['top']);
  401. }
  402. $image->ops[] = '-extent ' . escapeshellarg($geometry);
  403. return TRUE;
  404. }
  405. /**
  406. * Image dimensions callback for the define canvas effect.
  407. *
  408. * @param array $dimensions
  409. * Dimensions to be modified - an associative array containing the items
  410. * 'width' and 'height' (in pixels).
  411. * @param array $data
  412. * An associative array containing the effect data.
  413. */
  414. function canvasactions_definecanvas_dimensions(array &$dimensions, array $data) {
  415. // May be given either exact or relative dimensions.
  416. if ($data['exact']['width'] || $data['exact']['height']) {
  417. // Allows only one dimension to be used if the other is unset.
  418. if (!$data['exact']['width']) {
  419. $data['exact']['width'] = $dimensions['width'];
  420. }
  421. if (!$data['exact']['height']) {
  422. $data['exact']['height'] = $dimensions['height'];
  423. }
  424. $dimensions['width'] = imagecache_actions_percent_filter($data['exact']['width'], $dimensions['width']);
  425. $dimensions['height'] = imagecache_actions_percent_filter($data['exact']['height'], $dimensions['height']);
  426. }
  427. else {
  428. // Calculate relative sizes (only possible if we have the current size).
  429. if ($dimensions['width'] !== NULL) {
  430. $dimensions['width'] = $dimensions['width'] + (int) $data['relative']['leftdiff'] + (int) $data['relative']['rightdiff'];
  431. }
  432. if ($dimensions['height'] !== NULL) {
  433. $dimensions['height'] = $dimensions['height'] + (int) $data['relative']['topdiff'] + (int) $data['relative']['bottomdiff'];
  434. }
  435. }
  436. }
  437. /**
  438. * Image effect form callback for the underlay (background) effect.
  439. *
  440. * @param array $data
  441. * The current configuration for this image effect.
  442. *
  443. * @return array
  444. * The form definition for this effect.
  445. */
  446. function canvasactions_canvas2file_form(array $data) {
  447. $defaults = array(
  448. 'xpos' => '0',
  449. 'ypos' => '0',
  450. 'alpha' => '100',
  451. 'path' => '',
  452. 'dimensions' => 'original',
  453. );
  454. $data = array_merge($defaults, (array) $data);
  455. $form = imagecache_actions_pos_form($data);
  456. $form['alpha'] = array(
  457. '#type' => 'textfield',
  458. '#title' => t('opacity'),
  459. '#default_value' => $data['alpha'],
  460. '#size' => 6,
  461. '#description' => t('Opacity: 0-100. Be aware that values other than 100% may be slow to process.'),
  462. );
  463. $form['path'] = array(
  464. '#type' => 'textfield',
  465. '#title' => t('file name'),
  466. '#default_value' => $data['path'],
  467. '#description' => imagecache_actions_file_field_description(),
  468. '#element_validate' => array('imagecache_actions_validate_file'),
  469. );
  470. $form['dimensions'] = array(
  471. '#type' => 'radios',
  472. '#title' => t('final dimensions'),
  473. '#default_value' => $data['dimensions'],
  474. '#options' => array(
  475. 'original' => 'original (dimensions are retained)',
  476. 'background' => 'background (image will be forced to match the size of the background)',
  477. 'minimum' => 'minimum (image may be cropped)',
  478. 'maximum' => 'maximum (image may end up with gaps)',
  479. ),
  480. '#description' => t('What to do when the background image is a different size from the source image. Backgrounds are not tiled, but may be arbitrarily large.'),
  481. );
  482. return $form;
  483. }
  484. /**
  485. * Implements theme_hook() for the underlay (background) effect summary.
  486. *
  487. * @param array $variables
  488. * An associative array containing:
  489. * - data: The current configuration for this image effect.
  490. *
  491. * @return string
  492. * The HTML for the summary of this image effect.
  493. * @ingroup themeable
  494. */
  495. function theme_canvasactions_canvas2file_summary($variables) {
  496. $data = $variables['data'];
  497. $file = $data['path'];
  498. return "xpos:{$data['xpos']} , ypos:{$data['ypos']} alpha:{$data['alpha']}%. file: $file, dimensions:{$data['dimensions']}";
  499. }
  500. /**
  501. * Image effect callback for the underlay (background) effect.
  502. *
  503. * @param stdClass $image
  504. * @param array $data
  505. *
  506. * @return boolean
  507. * true on success, false otherwise.
  508. */
  509. function canvasactions_canvas2file_effect(stdClass $image, array $data) {
  510. $underlay = imagecache_actions_image_load($data['path'], $image->toolkit);
  511. if ($underlay) {
  512. // To handle odd sizes, we will resize/crop the background image to the
  513. // desired dimensions before starting the merge. The built-in
  514. // imagecopymerge, and the watermark library both do not allow overlays to
  515. // be bigger than the target.
  516. // Adjust size.
  517. $crop_rules = array(
  518. 'xoffset' => 0,
  519. 'yoffset' => 0,
  520. );
  521. if (empty($data['dimensions'])) {
  522. $data['dimensions'] = 'original';
  523. }
  524. switch ($data['dimensions']) {
  525. case 'original':
  526. // If the underlay is smaller than the target size,
  527. // then when preparing the underlay by cropping it,
  528. // the offsets may need to be negative
  529. // which will produce a 'cropped' image larger than the original.
  530. // In this case, we need to calculate the position of the bg image
  531. // in relation to the space it will occupy under the top layer
  532. //$crop_rules['xoffset'] = $underlay->info['width'] - $image->info['width'] ;
  533. $crop_rules['width'] = $image->info['width'];
  534. $crop_rules['height'] = $image->info['height'];
  535. break;
  536. case 'background':
  537. $crop_rules['width'] = $underlay->info['width'];
  538. $crop_rules['height'] = $underlay->info['height'];
  539. break;
  540. case 'minimum':
  541. $crop_rules['width'] = min($underlay->info['width'], $image->info['width']);
  542. $crop_rules['height'] = min($underlay->info['height'], $image->info['height']);
  543. break;
  544. case 'maximum':
  545. $crop_rules['width'] = max($underlay->info['width'], $image->info['width']);
  546. $crop_rules['height'] = max($underlay->info['height'], $image->info['height']);
  547. break;
  548. }
  549. // imageapi crop assumes upsize is legal.
  550. // Crop both before processing to avoid unwanted processing.
  551. image_crop_effect($underlay, $crop_rules);
  552. // @todo: BUG - this doesn't position either
  553. // Actually this fails because imagecache_crop fills it with solid color when 'cropping' to a larger size.
  554. //imagecache_crop_image($image, $crop_rules);
  555. //dpm(get_defined_vars());
  556. // This func modifies the underlay image by ref, placing the current canvas on it.
  557. if (image_overlay($image, $underlay, $data['xpos'], $data['ypos'], $data['alpha'], TRUE)) {
  558. //$image->resource = $underlay->resource;
  559. //$image = $underlay; //@todo: this is a no-op.
  560. return TRUE;
  561. }
  562. }
  563. return FALSE;
  564. }
  565. /**
  566. * Image dimensions callback for the underlay (background) effect.
  567. *
  568. * @param array $dimensions
  569. * Dimensions to be modified - an associative array containing the items
  570. * 'width' and 'height' (in pixels).
  571. * @param array $data
  572. * An associative array containing the effect data.
  573. */
  574. function canvasactions_canvas2file_dimensions(array &$dimensions, array $data) {
  575. if ($data['dimensions'] !== 'original') {
  576. $underlay = imagecache_actions_image_load($data['path']);
  577. if ($underlay) {
  578. // If the new dimensions are taken from the background, we don't need to
  579. // know the original dimensions, we can just set the new dimensions to the
  580. // dimensions of the background. Otherwise, we need to know the old
  581. // dimensions. If unknown we have to leave them unknown.
  582. switch ($data['dimensions']) {
  583. case 'background':
  584. $dimensions['width'] = $underlay->info['width'];
  585. $dimensions['height'] = $underlay->info['height'];
  586. break;
  587. case 'minimum':
  588. if ($dimensions['width'] !== NULL) {
  589. $dimensions['width'] = min($underlay->info['width'], $dimensions['width']);
  590. }
  591. if ($dimensions['height'] !== NULL) {
  592. $dimensions['height'] = min($underlay->info['height'], $dimensions['height']);
  593. }
  594. break;
  595. case 'maximum':
  596. if ($dimensions['width'] !== NULL) {
  597. $dimensions['width'] = max($underlay->info['width'], $dimensions['width']);
  598. }
  599. if ($dimensions['height'] !== NULL) {
  600. $dimensions['height'] = max($underlay->info['height'], $dimensions['height']);
  601. }
  602. break;
  603. }
  604. }
  605. }
  606. }
  607. /**
  608. * Image effect form callback for the overlay (watermark) effect.
  609. *
  610. * @param array $data
  611. * The current configuration for this image effect.
  612. *
  613. * @return array
  614. * The form definition for this effect.
  615. */
  616. function canvasactions_file2canvas_form(array $data) {
  617. $defaults = array(
  618. 'xpos' => '',
  619. 'ypos' => '',
  620. 'alpha' => '100',
  621. 'scale' => '',
  622. 'path' => '',
  623. );
  624. $data = array_merge($defaults, (array) $data);
  625. $form = array(
  626. 'help' => array(
  627. '#markup' => t('Note that using a non transparent overlay that is larger than the source image may result in unwanted results - a solid background.'),
  628. ),
  629. );
  630. $form += imagecache_actions_pos_form($data);
  631. $form['alpha'] = array(
  632. '#type' => 'textfield',
  633. '#title' => t('opacity'),
  634. '#default_value' => $data['alpha'],
  635. '#field_suffix' => t('%'),
  636. '#size' => 6,
  637. '#description' => t('Opacity: 0-100. <b>Warning:</b> Due to a limitation in the GD toolkit, using an opacity other than 100% requires the system to use an algorithm that\'s much slower than the built-in functions. If you want partial transparency, you are better to use an already-transparent png as the overlay source image.'),
  638. '#element_validate' => array('imagecache_actions_validate_number_non_negative'),
  639. );
  640. $form['scale'] = array(
  641. '#type' => 'textfield',
  642. '#title' => t('scale'),
  643. '#default_value' => $data['scale'],
  644. '#field_suffix' => t('%'),
  645. '#size' => 6,
  646. '#description' => t('Scales the overlay with respect to the source, thus not its own dimensions. Leave empty to use the original size of overlay image.'),
  647. '#element_validate' => array('imagecache_actions_validate_number_positive'),
  648. );
  649. $form['path'] = array(
  650. '#type' => 'textfield',
  651. '#title' => t('file name'),
  652. '#default_value' => $data['path'],
  653. '#description' => imagecache_actions_file_field_description(),
  654. '#element_validate' => array('imagecache_actions_validate_file'),
  655. );
  656. return $form;
  657. }
  658. /**
  659. * Implements theme_hook() for the overlay (watermark) effect summary.
  660. *
  661. * @param array $variables
  662. * An associative array containing:
  663. * - data: The current configuration for this image effect.
  664. *
  665. * @return string
  666. * The HTML for the summary of this image effect.
  667. * @ingroup themeable
  668. */
  669. function theme_canvasactions_file2canvas_summary(array $variables) {
  670. $data = $variables['data'];
  671. return '<strong>' . $data['path'] . '</strong>, x:' . $data['xpos'] . ', y:' . $data['ypos'] . ', alpha:' . (!empty($data['alpha']) ? $data['alpha'] : 100) . '%' . ', scale:' . (!empty($data['scale']) ? $data['scale'].'%' : '-');
  672. }
  673. /**
  674. * Image effect callback for the overlay (watermark) image effect.
  675. *
  676. * @param stdClass $image
  677. * @param array $data
  678. *
  679. * @return boolean
  680. * true on success, false otherwise.
  681. */
  682. function canvasactions_file2canvas_effect(stdClass $image, array $data) {
  683. $overlay = imagecache_actions_image_load($data['path']);
  684. if ($overlay) {
  685. if (!empty($data['scale']) && $data['scale'] > 0) {
  686. // Scale the overlay with respect to the dimensions of the source being
  687. // overlaid. To maintain the aspect ratio, only the width of the overlay
  688. // is scaled like that, the height of the overlay follows the aspect
  689. // ratio (that is why we use image_scale instead of image_resize).
  690. $overlay_w = $image->info['width'] * $data['scale'] / 100;
  691. image_scale($overlay, $overlay_w, NULL, TRUE);
  692. }
  693. if (!isset($data['alpha'])) {
  694. $data['alpha'] = 100;
  695. }
  696. return image_overlay($image, $overlay, $data['xpos'], $data['ypos'], $data['alpha']);
  697. }
  698. return FALSE;
  699. }
  700. /**
  701. * Image effect form callback for the overlay: source image to canvas effect.
  702. *
  703. * @param array $data
  704. * The current configuration for this image effect.
  705. *
  706. * @return array
  707. * The form definition for this effect.
  708. */
  709. function canvasactions_source2canvas_form($data) {
  710. $defaults = array(
  711. 'xpos' => '',
  712. 'ypos' => '',
  713. 'alpha' => '100',
  714. 'path' => '',
  715. );
  716. $data = array_merge($defaults, (array) $data);
  717. $form = imagecache_actions_pos_form($data);
  718. $form['alpha'] = array(
  719. '#type' => 'textfield',
  720. '#title' => t('opacity'),
  721. '#default_value' => $data['alpha'],
  722. '#size' => 6,
  723. '#description' => t('Opacity: 0-100.'),
  724. );
  725. return $form;
  726. }
  727. /**
  728. * Implements theme_hook() for the overlay: source img to canvas effect summary.
  729. *
  730. * @param array $variables
  731. * An associative array containing:
  732. * - data: The current configuration for this image effect.
  733. *
  734. * @return string
  735. * The HTML for the summary of this image effect.
  736. * @ingroup themeable
  737. */
  738. function theme_canvasactions_source2canvas_summary(array $variables) {
  739. $data = $variables['data'];
  740. return 'xpos:' . $data['xpos'] . ', ypos:' . $data['ypos'] . ' alpha:' . $data['alpha'] . '%';
  741. }
  742. /**
  743. * Image effect callback for the overlay: source image to canvas effect.
  744. *
  745. * @param stdClass $image
  746. * @param array $data
  747. *
  748. * @return boolean
  749. * true on success, false otherwise.
  750. */
  751. function canvasactions_source2canvas_effect(stdClass $image, array $data) {
  752. $overlay = image_load($image->source, $image->toolkit);
  753. return image_overlay($image, $overlay, $data['xpos'], $data['ypos'], $data['alpha']);
  754. }
  755. /**
  756. * Image effect form callback for the aspect switcher effect.
  757. *
  758. * @param array $data
  759. * The current configuration for this image effect.
  760. *
  761. * @return array
  762. * The form definition for this effect.
  763. */
  764. function canvasactions_aspect_form(array $data) {
  765. $defaults = array(
  766. 'ratio_adjustment' => 1,
  767. 'portrait' => '',
  768. 'landscape' => '',
  769. );
  770. $data = array_merge($defaults, (array) $data);
  771. $form = array(
  772. 'help' => array(
  773. '#markup' => t('You must create the two presets to use <em>before</em> enabling this process.'),
  774. )
  775. );
  776. // The PASS_THROUGH parameter is new as of D7.23, and is added here to prevent
  777. // image_style_options() from double-encoding the human-readable image style
  778. // name, since the form API will already sanitize options in a select list.
  779. $styles = image_style_options(TRUE, PASS_THROUGH);
  780. // @todo: remove the current style to prevent (immediate) recursion?
  781. $form['portrait'] = array(
  782. '#type' => 'select',
  783. '#title' => t('Style to use if the image is portrait (vertical)'),
  784. '#default_value' => $data['portrait'],
  785. '#options' => $styles,
  786. '#description' => t('If you choose none, no extra processing will be done.'),
  787. );
  788. $form['landscape'] = array(
  789. '#type' => 'select',
  790. '#title' => t('Style to use if the image is landscape (horizontal)'),
  791. '#default_value' => $data['landscape'],
  792. '#options' => $styles,
  793. '#description' => t('If you choose none, no extra processing will be done.'),
  794. );
  795. $form['ratio_adjustment'] = array(
  796. '#type' => 'textfield',
  797. '#title' => t('Ratio Adjustment (advanced)'),
  798. '#size' => 3,
  799. '#default_value' => $data['ratio_adjustment'],
  800. '#description' => t("
  801. This allows you to bend the rules for how different the proportions need to be to trigger the switch.
  802. <br/>If the (width/height)*n is greater than 1, use 'landscape', otherwise use 'portrait'.
  803. <br/>When n = 1 (the default) it will switch between portrait and landscape modes.
  804. <br/>If n > 1, images that are slightly wide will still be treated as portraits.
  805. If n < 1 then blunt portraits will be treated as landscape.
  806. "),
  807. );
  808. return $form;
  809. }
  810. /**
  811. * Implements theme_hook() for the aspect switcher effect summary.
  812. *
  813. * @param array $variables
  814. * An associative array containing:
  815. * - data: The current configuration for this image effect.
  816. *
  817. * @return string
  818. * The HTML for the summary of this image effect.
  819. * @ingroup themeable
  820. */
  821. function theme_canvasactions_aspect_summary(array $variables) {
  822. $data = $variables['data'];
  823. $label = imagecache_actions_get_style_label($data['portrait']);
  824. $output = t('Portrait size: %label', array('%label' => $label));
  825. $label = imagecache_actions_get_style_label($data['landscape']);
  826. $output .= ', ' . t('Landscape size: %label', array('%label' => $label));
  827. if ($data['ratio_adjustment'] != 1) {
  828. $output .= ', ' . t("(switch at 1:@ratio_adjustment)", array('@ratio_adjustment' => $data['ratio_adjustment']));
  829. }
  830. return trim($output);
  831. }
  832. /**
  833. * Image effect callback for the aspect switcher effect.
  834. *
  835. * @param stdClass $image
  836. * @param array $data
  837. *
  838. * @return boolean
  839. * true on success, false otherwise.
  840. */
  841. function canvasactions_aspect_effect(stdClass $image, array $data) {
  842. $ratio_adjustment = isset($data['ratio_adjustment']) ? floatval( $data['ratio_adjustment']) : 1;
  843. $aspect = $image->info['width'] / $image->info['height'];
  844. // width / height * adjustment. If > 1, it's wide.
  845. $style_name = (($aspect * $ratio_adjustment) > 1) ? $data['landscape'] : $data['portrait'];
  846. if (empty($style_name)) {
  847. // Do nothing. just return what we've got.
  848. return TRUE;
  849. }
  850. $style = image_style_load($style_name);
  851. if (empty($style)) {
  852. // Required preset has gone missing?
  853. watchdog('imagecache_actions', "When running 'aspect' action, I was unable to load sub-action %style_name. Either it's been deleted or the DB needs an update", array('%style_name' => $style_name), WATCHDOG_ERROR);
  854. return FALSE;
  855. }
  856. // Run the preset actions ourself.
  857. foreach ($style['effects'] as $sub_effect) {
  858. image_effect_apply($image, $sub_effect);
  859. }
  860. return TRUE;
  861. }
  862. /**
  863. * Image dimensions callback for the aspect switcher effect.
  864. *
  865. * @param array $dimensions
  866. * Dimensions to be modified - an associative array containing the items
  867. * 'width' and 'height' (in pixels).
  868. * @param array $data
  869. * An associative array containing the effect data.
  870. */
  871. function canvasactions_aspect_dimensions(array &$dimensions, array $data) {
  872. if (empty($dimensions['width']) || empty($dimensions['height'])) {
  873. // We cannot know which preset would be executed and thus cannot know the
  874. // resulting dimensions, unless both styles return the same dimensions:
  875. $landscape_dimensions = $portrait_dimensions = $dimensions;
  876. image_style_transform_dimensions($data['landscape'], $landscape_dimensions);
  877. image_style_transform_dimensions($data['portrait'], $portrait_dimensions);
  878. if ($landscape_dimensions == $portrait_dimensions) {
  879. $dimensions = $landscape_dimensions;
  880. }
  881. else {
  882. $dimensions['width'] = $dimensions['height'] = NULL;
  883. }
  884. }
  885. else {
  886. $ratio_adjustment = isset($data['ratio_adjustment']) ? floatval( $data['ratio_adjustment']) : 1;
  887. $aspect = $dimensions['width'] / $dimensions['height'];
  888. $style_name = (($aspect * $ratio_adjustment) > 1) ? $data['landscape'] : $data['portrait'];
  889. image_style_transform_dimensions($style_name, $dimensions);
  890. }
  891. }
  892. /**
  893. * Image effect form callback for the resize percent effect.
  894. *
  895. * @param array $data
  896. * The current configuration for this image effect.
  897. *
  898. * @return array
  899. * The form definition for this effect.
  900. */
  901. function canvasactions_resizepercent_form(array $data) {
  902. $defaults = array(
  903. 'width' => '',
  904. 'height' => '',
  905. );
  906. $data = array_merge($defaults, (array) $data);
  907. $form['#element_validate'] = array('image_effect_scale_validate');
  908. $form['width'] = array(
  909. '#type' => 'textfield',
  910. '#title' => t('Width'),
  911. '#default_value' => !empty($data['width']) ? (float) $data['width'] : '',
  912. '#field_suffix' => ' ' . t('percent'),
  913. '#required' => FALSE,
  914. '#size' => 10,
  915. '#element_validate' => array('canvasactions_resizepercent_validate'),
  916. '#allow_negative' => FALSE,
  917. );
  918. $form['height'] = array(
  919. '#type' => 'textfield',
  920. '#title' => t('Height'),
  921. '#default_value' => !empty($data['height']) ? (float) $data['height'] : '',
  922. '#field_suffix' => ' ' . t('percent'),
  923. '#required' => FALSE,
  924. '#size' => 10,
  925. '#element_validate' => array('canvasactions_resizepercent_validate'),
  926. '#allow_negative' => FALSE,
  927. );
  928. return $form;
  929. }
  930. /**
  931. * Element validate handler to ensure that a positive number is specified.
  932. *
  933. * @param array $element
  934. * The form element to validate.
  935. * @param array $form_state
  936. * The form state.
  937. */
  938. function canvasactions_resizepercent_validate($element, &$form_state) {
  939. element_validate_number($element, $form_state);
  940. if (!form_get_error($element)) {
  941. if ($element['#value'] != '' && (float) $element['#value'] <= 0) {
  942. form_error($element, t('!name must be a positive number.', array('!name' => $element['#title'])));
  943. }
  944. }
  945. }
  946. /**
  947. * Calculate percent based on input, fallback if only one value is provided.
  948. *
  949. * @param array $data
  950. *
  951. * @return float[]|FALSE
  952. */
  953. function _canvasactions_resizepercent_calculate_percent(array $data) {
  954. // Fallback if only one value is provided.
  955. if (empty($data['height'])) {
  956. if (empty($data['width'])) {
  957. return FALSE;
  958. }
  959. $data['height'] = $data['width'];
  960. }
  961. else if (empty($data['width'])) {
  962. $data['width'] = $data['height'];
  963. }
  964. // Get percentage values in decimal values.
  965. $data['width'] = (float) $data['width'] / 100;
  966. $data['height'] = (float) $data['height'] / 100;
  967. return $data;
  968. }
  969. /**
  970. * Implements theme_hook() for the resize percent effect summary.
  971. *
  972. * @param array $variables
  973. * An associative array containing:
  974. * - data: The current configuration for this image effect.
  975. *
  976. * @return string
  977. * The HTML for the summary of this image effect.
  978. * @ingroup themeable
  979. */
  980. function theme_canvasactions_resizepercent_summary(array $variables) {
  981. $data = _canvasactions_resizepercent_calculate_percent($variables['data']);
  982. if (!$data) {
  983. return t('Invalid effect data');
  984. }
  985. if ($data['width'] != $data['height']) {
  986. return t('@width%x@height%', array('@width' => 100 * $data['width'], '@height' => 100 * $data['height']));
  987. }
  988. else {
  989. return t('scale to @percent%', array('@percent' => (float) 100 * $data['height']));
  990. }
  991. }
  992. /**
  993. * Image effect callback for the resize percent effect.
  994. *
  995. * @param stdClass $image
  996. * @param array $data
  997. *
  998. * @return boolean
  999. * true on success, false otherwise.
  1000. */
  1001. function canvasactions_resizepercent_effect(stdClass $image, array $data) {
  1002. $data = _canvasactions_resizepercent_calculate_percent($data);
  1003. $data['width'] = (int) round($image->info['width'] * $data['width']);
  1004. $data['height'] = (int) round($image->info['height'] * $data['height']);
  1005. return image_resize_effect($image, $data);
  1006. }
  1007. /**
  1008. * Image dimensions callback for the resize percent effect.
  1009. *
  1010. * @param array $dimensions
  1011. * Dimensions to be modified - an associative array containing the items
  1012. * 'width' and 'height' (in pixels).
  1013. * @param array $data
  1014. * An associative array containing the effect data.
  1015. */
  1016. function canvasactions_resizepercent_dimensions(array &$dimensions, array $data) {
  1017. $data = _canvasactions_resizepercent_calculate_percent($data);
  1018. $dimensions['width'] = (int) round($dimensions['width'] * $data['width']);
  1019. $dimensions['height'] = (int) round($dimensions['height'] * $data['height']);
  1020. }
  1021. /**
  1022. * Image effect form callback for the blur effect.
  1023. *
  1024. * @param array $data
  1025. * The current configuration for this image effect.
  1026. *
  1027. * @return array
  1028. * The form definition for this effect.
  1029. */
  1030. function canvasactions_blur_form(array $data) {
  1031. $form['intensity'] = array(
  1032. '#type' => 'textfield',
  1033. '#title' => t('Blur intensity'),
  1034. '#description' => t('A higher intensity results in more blur. The larger the image, the larger value you need to get a really blurred image, think 50 to 100 for 600x400 images.'),
  1035. '#size' => 5,
  1036. '#default_value' => isset($data['intensity']) ? (int) $data['intensity'] : 2,
  1037. '#element_validate' => array('element_validate_integer_positive'),
  1038. );
  1039. return $form;
  1040. }
  1041. /**
  1042. * Implements theme_hook() for the underlay (background) effect summary.
  1043. *
  1044. * @param array $variables
  1045. * An associative array containing:
  1046. * - data: The current configuration for this image effect.
  1047. *
  1048. * @return string
  1049. * The HTML for the summary of this image effect.
  1050. * @ingroup themeable
  1051. */
  1052. function theme_canvasactions_blur_summary($variables) {
  1053. return t('Intensity: @intensity', array('@intensity' => $variables['data']['intensity']));
  1054. }
  1055. /**
  1056. * Image effect callback for the resize percent effect.
  1057. *
  1058. * @param stdClass $image
  1059. * @param array $data
  1060. *
  1061. * @return boolean
  1062. * true on success, false otherwise.
  1063. */
  1064. function canvasactions_blur_effect(stdClass $image, array $data) {
  1065. return image_toolkit_invoke('blur', $image, $data);
  1066. }
  1067. /**
  1068. * GD toolkit specific implementation of the blur effect.
  1069. *
  1070. * @param stdClass $image
  1071. * @param int $intensity
  1072. * The number of times to apply the blur filter.
  1073. *
  1074. * @return boolean
  1075. * True on success, false otherwise.
  1076. */
  1077. function image_gd_blur(stdClass $image, $intensity) {
  1078. $intensity = (int) $intensity;
  1079. $result = TRUE;
  1080. $i = 0;
  1081. while ($result && $i++ < $intensity) {
  1082. $result = imagefilter($image->resource, IMG_FILTER_GAUSSIAN_BLUR);
  1083. }
  1084. return $result;
  1085. }
  1086. /**
  1087. * Imagemagick toolkit specific implementation of the blur effect.
  1088. *
  1089. * See http://www.imagemagick.org/Usage/blur/.
  1090. *
  1091. * @param stdClass $image
  1092. * @param int $intensity
  1093. * The "intensity" of the blur effect.
  1094. *
  1095. * @return boolean
  1096. * True on success, false otherwise.
  1097. */
  1098. function image_imagemagick_blur(stdClass $image, $intensity) {
  1099. // To get similar results asd with the GD factor, we use a formula to alter
  1100. // the intensity into the sigma value that is passed to IM.
  1101. $sigma = 4.0 + 0.8 * $intensity;
  1102. $image->ops[] = '-blur ' . escapeshellarg(sprintf('0x%f', $sigma));
  1103. return TRUE;
  1104. }
  1105. /**
  1106. * Builds the interlace form.
  1107. *
  1108. * This effect has no options, only some help text, so the form is displayed
  1109. * anyway.
  1110. */
  1111. function canvasactions_interlace_form() {
  1112. $form = array();
  1113. $form['help'] = array(
  1114. '#markup' => '<p><strong>There are no user-configurable options for this process.</strong></p>
  1115. <p>This effect will save the derivative image in an interlace / progressive way
  1116. which might improve perceived performance, especially for large images.
  1117. File size and loading speed will not change, but the user will pretty quickly
  1118. see a "degraded" copy of the entire image instead of a clear copy of the upper
  1119. part of the image.</p>
  1120. <p>Wikipedia: <a href="https://en.wikipedia.org/wiki/Interlacing_(bitmaps)">Interlacing (bitmaps)</a></p>',
  1121. );
  1122. return $form;
  1123. }
  1124. /**
  1125. * Image effect callback for the Interlace / Progressive effect.
  1126. *
  1127. * @param stdClass $image
  1128. * @param array $data
  1129. *
  1130. * @return bool
  1131. * true on success, false otherwise.
  1132. */
  1133. function canvasactions_interlace_effect(stdClass $image, array $data) {
  1134. return image_toolkit_invoke('interlace', $image, array($data));
  1135. }
  1136. /**
  1137. * GD toolkit specific implementation of the image interlace effect.
  1138. *
  1139. * @param stdClass $image
  1140. * Image object containing the GD image resource to operate on.
  1141. * param array $data
  1142. * The current configuration for this image effect.
  1143. *
  1144. * @return bool
  1145. * true on success, false otherwise.
  1146. */
  1147. function image_gd_interlace($image/*, array $data*/) {
  1148. imageinterlace($image->resource, 1);
  1149. return TRUE;
  1150. }
  1151. /**
  1152. * Imagemagick toolkit specific implementation of the image interlace effect.
  1153. *
  1154. * @param stdClass $image
  1155. * Image object containing the image resource to operate on.
  1156. * param array $data
  1157. * The current configuration for this image effect.
  1158. *
  1159. * @return bool
  1160. * true on success, false otherwise.
  1161. */
  1162. function image_imagemagick_interlace($image/*, array $data*/) {
  1163. $image->ops[] = '-interlace Plane';
  1164. return TRUE;
  1165. }
  1166. /**
  1167. * Image effect form callback for the Perspective effect.
  1168. *
  1169. * @param array $data
  1170. * The current configuration for this image effect.
  1171. *
  1172. * @return array
  1173. * The form definition for this effect.
  1174. */
  1175. function canvasactions_perspective_form(array $data) {
  1176. $defaults = array(
  1177. 'vanish' => 'right',
  1178. 'symmetry' => 'symmetrical',
  1179. 'distortion' => 14,
  1180. 'opposite_distortion' => 10,
  1181. );
  1182. $data = array_merge($defaults, $data);
  1183. $form['vanish'] = array(
  1184. '#type' => 'radios',
  1185. '#title' => t('Vanishing point'),
  1186. '#options' => array(
  1187. 'top' => t('Top'),
  1188. 'left' => t('Left'),
  1189. 'right' => t('Right'),
  1190. 'bottom' => t('Bottom'),
  1191. ),
  1192. '#theme' => 'canvasactions_perspective_anchor',
  1193. '#default_value' => $data['vanish'],
  1194. '#description' => t('The position of the vanishing point relative to the source image.'),
  1195. );
  1196. $form['symmetry'] = array(
  1197. '#type' => 'radios',
  1198. '#title' => t('Symmetry of image perspective'),
  1199. '#description' => t('If symmetrical, the perspective effect will be built symmetrically. If asymmetrical, you can set different distortion values for both sides. Mathematically speaking: symmetrical distortion results in an isosceles trapezoid, whereas asymmetrical distortion results in just an acute trapezoid.'),
  1200. '#default_value' => $data['symmetry'],
  1201. '#options' => array(
  1202. 'symmetrical' => t('Symmetrical perspective'),
  1203. 'asymmetrical' => t('Asymmetrical perspective'),
  1204. ),
  1205. );
  1206. $form['distortion'] = array(
  1207. '#type' => 'textfield',
  1208. '#title' => t('Distortion'),
  1209. '#field_suffix' => '%',
  1210. '#size' => 5,
  1211. '#default_value' => $data['distortion'],
  1212. '#element_validate' => array('canvasactions_perspective_distortion_validate'),
  1213. '#description' => t('How much the corner(s) (on the vanishing point side of the image) should move to the horizon (i.e. the line containing the vanishing point). With 0% you will have no perspective effect at all and the vanishing point will be infinitely far away. With a sum of 100%, the 2 corner(s) and the vanishing point will be the same, resulting in a triangle instead of a trapezoid. For a pleasing effect, you should choose (a) number(s) between 0 and 35%, especially with ImageMagick as that toolkit also adds some stretching within the image.'),
  1214. );
  1215. $form['opposite_distortion'] = array(
  1216. '#type' => 'textfield',
  1217. '#title' => t('Distortion for opposite side'),
  1218. '#states' => array(
  1219. 'visible' => array(
  1220. ':input[name="data[symmetry]"]' => array('value' => 'asymmetrical'),
  1221. ),
  1222. ),
  1223. '#field_suffix' => '%',
  1224. '#size' => 5,
  1225. '#default_value' => $data['opposite_distortion'],
  1226. '#element_validate' => array('canvasactions_perspective_distortion_validate'),
  1227. '#description' => t('How much the 2nd corner on the vanishing point side of the image should move to the horizon line containing the vanishing point.'),
  1228. );
  1229. $form['additional_help'] = array(
  1230. '#markup' => '<p>Some notes:</p>
  1231. <ul><li>This effect adds a perspective effect to an image.
  1232. Normally, to get a realistic effect, the side that gets the perspective effect should be reduced in size.
  1233. However, this effect does not do so, as it is easy to add a (percentage) resize effect to the image style yourself.
  1234. A resize to 85% of the original size is a good start when experimenting.</li>
  1235. <li>CSS3 also defines <a href="https://www.w3.org/TR/2009/WD-css3-3d-transforms-20090320/#perspective-property">3D perspective transformations</a>.
  1236. So you might get some of the results of ths effect with pure CSS as well.</li></ul>',
  1237. );
  1238. return $form;
  1239. }
  1240. /**
  1241. * Form element validation handler for distortion.
  1242. *
  1243. * @param array $element
  1244. * The form element to validate.
  1245. * @param array $form_state
  1246. * The form state.
  1247. */
  1248. function canvasactions_perspective_distortion_validate($element, &$form_state) {
  1249. $symmetrical = $form_state['values']['data']['symmetry'] === 'symmetrical';
  1250. $element_name = $element['#name'];
  1251. // Do not check opposite distortion if it is hidden in the UI.
  1252. if (!$symmetrical || $element_name === 'data[distortion]') {
  1253. $value = $element['#value'];
  1254. // Check value itself: a number between 0 and 100 (50 if symmetrical):
  1255. $max_value = $symmetrical ? 50 : 100;
  1256. if (!is_numeric($value) || $value < 0 || $value >= $max_value) {
  1257. if ($symmetrical) {
  1258. form_error($element, t('!name must be a value between 0 and 50.', array('!name' => $element['#title'])));
  1259. }
  1260. else {
  1261. form_error($element, t('!name must be a value between 0 and 100.',array('!name' => $element['#title'])));
  1262. }
  1263. }
  1264. // Sum of both distortion values should also be smaller then 100.
  1265. if (!$symmetrical) {
  1266. $other_value_name = $element_name === 'data[distortion]' ? 'opposite_distortion' : 'distortion';
  1267. $other_value = $form_state['values']['data'][$other_value_name];
  1268. if (is_numeric($other_value) && $value + $other_value >= 100) {
  1269. form_error($element, t('The sum of %name and %name2 must be lower then 100.',
  1270. array(
  1271. '%name' => $element['#title'],
  1272. '%name2' => $other_value_name === 'distortion' ? t('Distortion') : t('Distortion for opposite side'),
  1273. ))
  1274. );
  1275. }
  1276. }
  1277. }
  1278. }
  1279. /**
  1280. * Implements theme_hook() for the define perspective effect summary.
  1281. *
  1282. * @param array $variables
  1283. * An associative array containing:
  1284. * - data: The current configuration for this image effect.
  1285. *
  1286. * @return string
  1287. * The HTML for the summary of this image effect.
  1288. * @ingroup themeable
  1289. */
  1290. function theme_canvasactions_perspective_summary(array $variables) {
  1291. $data = $variables['data'];
  1292. $output = array();
  1293. $output[] = t('%symmetry. Vanishing point: %vanish.',
  1294. array(
  1295. '%symmetry' => $data['symmetry'],
  1296. '%vanish' => $data['vanish'],
  1297. ));
  1298. if ($data['symmetry'] == 'asymmetrical') {
  1299. switch ($data['vanish']) {
  1300. case 'top':
  1301. case 'bottom':
  1302. $output[] = t('Left distortion: %distortion, right distortion: %opposite_distortion.',
  1303. array(
  1304. '%distortion' => $data['distortion'] . '%',
  1305. '%opposite_distortion' => $data['opposite_distortion'] . '%',
  1306. ));
  1307. break;
  1308. case 'right':
  1309. case 'left':
  1310. $output[] = t('Top distortion: %distortion, bottom distortion: %opposite_distortion.',
  1311. array(
  1312. '%distortion' => $data['distortion'] . '%',
  1313. '%opposite_distortion' => $data['opposite_distortion'] . '%',
  1314. ));
  1315. break;
  1316. }
  1317. }
  1318. else {
  1319. $output[] = t('Distortion: %distortion.', array('%distortion' => $data['distortion'] . '%'));
  1320. }
  1321. return implode(' ', $output);
  1322. }
  1323. /**
  1324. * Image effect callback for the Perspective effect.
  1325. *
  1326. * @param stdClass $image
  1327. * Image object containing the image to operate on.
  1328. * @param array $data
  1329. * The current configuration for this image effect, contains the keys:
  1330. * distortion, vanish, symmetry and opposite_distortion options.
  1331. *
  1332. * @return bool
  1333. * True on success, false otherwise.
  1334. */
  1335. function canvasactions_perspective_effect(stdClass $image, array $data) {
  1336. if (!image_toolkit_invoke('perspective', $image, array($data))) {
  1337. watchdog('imagecache_canvasactions', 'Image perspective transform failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array(
  1338. '%toolkit' => $image->toolkit,
  1339. '%path' => $image->source,
  1340. '%mimetype' => $image->info['mime_type'],
  1341. '%dimensions' => $image->info['height'] . 'x' . $image->info['height'],
  1342. ), WATCHDOG_ERROR);
  1343. return FALSE;
  1344. }
  1345. return TRUE;
  1346. }
  1347. /**
  1348. * GD toolkit specific implementation of the image Perspective effect.
  1349. *
  1350. * @param stdClass $image
  1351. * Image object containing the image resource to operate on.
  1352. * @param array $data
  1353. * The current configuration for this image effect, contains the keys:
  1354. * distortion, vanish, symmetry and opposite_distortion options.
  1355. *
  1356. * @return bool
  1357. * True on success, false otherwise.
  1358. */
  1359. function image_gd_perspective(stdClass $image, $data) {
  1360. $width = $image->info['width'];
  1361. $height = $image->info['height'];
  1362. $distortion = $data['distortion'];
  1363. $opposite_distortion = $data['symmetry'] === 'symmetrical' ? $distortion : $data['opposite_distortion'];
  1364. // To reduce distortion, we work with a temporary hires version of the image.
  1365. // @todo: during processing we have 2 resources this size: this might crash on memory limits: warn and/or prevent.
  1366. $multiplier = 3;
  1367. $hires_width = $width * $multiplier;
  1368. $hires_height = $height * $multiplier;
  1369. $hires_source_image = imagecreatetruecolor($hires_width, $hires_height);
  1370. $transparent_white = imagecolorallocatealpha($hires_source_image, 255, 255, 255, 127);
  1371. imagealphablending($hires_source_image, FALSE);
  1372. imagefilledrectangle($hires_source_image, 0, 0, $hires_width, $hires_height, $transparent_white);
  1373. imagesavealpha($hires_source_image, TRUE);
  1374. imagecopyresized($hires_source_image, $image->resource, 0, 0, 0, 0, $hires_width, $hires_height, $width, $height);
  1375. imagedestroy($image->resource);
  1376. // Creating a hires target canvas to apply the perspective effect on.
  1377. $hires_target_image = imagecreatetruecolor($hires_width, $hires_height);
  1378. $transparent_white = imagecolorallocatealpha($hires_target_image, 255, 255, 255, 127);
  1379. // We don't want to blend: the transparent background we set is only for the
  1380. // parts that do not get covered.
  1381. imagealphablending($hires_target_image, FALSE);
  1382. imagefilledrectangle($hires_target_image, 0, 0, $hires_width, $hires_height, $transparent_white);
  1383. // Building perspective effect with help four point distortion methods.
  1384. // On each step found new distortion point by right triangle formula.
  1385. switch ($data['vanish']) {
  1386. case 'top':
  1387. $left = round($hires_width * $distortion / 100);
  1388. $right = round($hires_width - ($hires_width * (100 - $opposite_distortion) / 100));
  1389. $tg_beta_left = $left / $hires_height;
  1390. $tg_beta_right = $right / $hires_height;
  1391. for ($y = 0; $y < $hires_height; $y++) {
  1392. $new_left = ($hires_height - $y) * $tg_beta_left;
  1393. $new_right = ($hires_height - $y) * $tg_beta_right;
  1394. $new_width = $hires_width - $new_left - $new_right;
  1395. imagecopyresampled($hires_target_image, $hires_source_image,
  1396. $new_left, $y,
  1397. 0, $y,
  1398. $new_width, 1,
  1399. $hires_width, 1);
  1400. }
  1401. break;
  1402. case 'bottom':
  1403. $left = round($hires_width * $distortion / 100);
  1404. $right = round($hires_width - ($hires_width * (100 - $opposite_distortion) / 100));
  1405. $tg_beta_left = $left / $hires_height;
  1406. $tg_beta_right = $right / $hires_height;
  1407. for ($y = $hires_height; $y > 0; $y--) {
  1408. $new_left = $y * $tg_beta_left;
  1409. $new_right = $y * $tg_beta_right;
  1410. $new_width = $hires_width - $new_left - $new_right;
  1411. imagecopyresampled($hires_target_image, $hires_source_image,
  1412. $new_left, $y,
  1413. 0, $y,
  1414. $new_width, 1,
  1415. $hires_width, 1);
  1416. }
  1417. break;
  1418. case 'right':
  1419. $top = round($hires_height * $distortion / 100);
  1420. $bottom = round($hires_height - ($hires_height * (100 - $opposite_distortion) / 100));
  1421. $tg_beta_top = $top / $hires_width;
  1422. $tg_beta_bottom = $bottom / $hires_width;
  1423. for ($x = $hires_width; $x > 0; $x--) {
  1424. $new_top = $x * $tg_beta_top;
  1425. $new_bottom = $x * $tg_beta_bottom;
  1426. $new_height = $hires_height - $new_top - $new_bottom;
  1427. imagecopyresampled($hires_target_image, $hires_source_image,
  1428. $x, $new_top,
  1429. $x, 0,
  1430. 1, $new_height,
  1431. 1, $hires_height);
  1432. }
  1433. break;
  1434. case 'left':
  1435. $top = round($hires_height * $distortion / 100);
  1436. $bottom = round($hires_height - ($hires_height * (100 - $opposite_distortion) / 100));
  1437. $tg_beta_top = $top / $hires_width;
  1438. $tg_beta_bottom = $bottom / $hires_width;
  1439. for ($x = 0; $x < $hires_width; $x++) {
  1440. $new_top = ($hires_width - $x) * $tg_beta_top;
  1441. $new_bottom = ($hires_width - $x) * $tg_beta_bottom;
  1442. $new_height = $hires_height - $new_top - $new_bottom;
  1443. imagecopyresampled($hires_target_image, $hires_source_image,
  1444. $x, $new_top,
  1445. $x, 0,
  1446. 1, $new_height,
  1447. 1, $hires_height);
  1448. }
  1449. break;
  1450. }
  1451. imagedestroy($hires_source_image);
  1452. imagealphablending($hires_target_image, FALSE);
  1453. imagesavealpha($hires_target_image, TRUE);
  1454. // Return image with perspective effect to original size.
  1455. $target_image = imagecreatetruecolor($width, $height);
  1456. imagealphablending($target_image, FALSE);
  1457. imagecopyresampled($target_image, $hires_target_image, 0, 0, 0, 0, $width, $height, $hires_width, $hires_height);
  1458. imagedestroy($hires_target_image);
  1459. imagesavealpha($target_image, TRUE);
  1460. $image->resource = $target_image;
  1461. return TRUE;
  1462. }
  1463. /**
  1464. * Imagemagick toolkit specific implementation of the image Perspective effect.
  1465. *
  1466. * @param stdClass $image
  1467. * Image object containing the image to operate on.
  1468. * @param array $data
  1469. * The current configuration for this image effect, contains the keys
  1470. * distortion, vanish, symmetry and opposite_distortion options.
  1471. *
  1472. * @return bool
  1473. * True on success, false otherwise.
  1474. */
  1475. function image_imagemagick_perspective(stdClass $image, $data) {
  1476. $width = $image->info['width'];
  1477. $height = $image->info['height'];
  1478. $distortion = $data['distortion'];
  1479. $opposite_distortion = $data['symmetry'] === 'symmetrical' ? $distortion : $data['opposite_distortion'];
  1480. switch ($data['vanish']) {
  1481. case 'top':
  1482. $left = round($width * $distortion / 100);
  1483. $right = round($width * (100 - $opposite_distortion) / 100);
  1484. $perspective_arg = "0,0,$left,0 0,$height,0,$height $width,0,$right,0 $width,$height,$width,$height";
  1485. break;
  1486. case 'right':
  1487. default: // Prevents warning about possibly undefined variable.
  1488. $top = round($height * $distortion / 100);
  1489. $bottom = round($height * (100 - $opposite_distortion) / 100);
  1490. $perspective_arg = "0,0,0,0 0,$height,0,$height $width,0,$width,$top $width,$height,$width,$bottom";
  1491. break;
  1492. case 'bottom':
  1493. $left = round($width * $distortion / 100);
  1494. $right = round($width * (100 - $opposite_distortion) / 100);
  1495. $perspective_arg = "0,0,0,0 0,$height,$left,$height $width,0,$width,0 $width,$height,$right,$height";
  1496. break;
  1497. case 'left':
  1498. $top = round($height * $distortion / 100);
  1499. $bottom = round($height * (100 - $opposite_distortion) / 100);
  1500. $perspective_arg = "0,0,0,$top 0,$height,0,$bottom $width,0,$width,0 $width,$height,$width,$height";
  1501. break;
  1502. }
  1503. $transparent_white = escapeshellarg('#ffffffff');
  1504. $perspective = escapeshellarg($perspective_arg);
  1505. $image->ops[] = "-background $transparent_white -virtual-pixel background -distort Perspective $perspective";
  1506. return TRUE;
  1507. }
  1508. /** @noinspection PhpDocMissingThrowsInspection */
  1509. /**
  1510. * Implements theme_hook().
  1511. *
  1512. * @param array $variables
  1513. * An associative array containing:
  1514. * - element: A render element containing radio buttons.
  1515. *
  1516. * @return string
  1517. * The HTML for a 3x3 grid of checkboxes for image anchors.
  1518. * @ingroup themeable
  1519. */
  1520. function theme_canvasactions_perspective_anchor($variables) {
  1521. $element = $variables['element'];
  1522. $rows = $row = $option = array();
  1523. $blank = array('#markup' => '');
  1524. $blank = drupal_render($blank);
  1525. /** @noinspection PhpUnhandledExceptionInspection */
  1526. $image = array(
  1527. '#markup' => theme('image', array(
  1528. 'path' => drupal_get_path('module', 'image') . '/sample.png',
  1529. 'attributes' => array('width' => "40", 'height' => "40"),
  1530. )),
  1531. );
  1532. $image = drupal_render($image);
  1533. foreach (element_children($element) as $key) {
  1534. $element[$key]['#attributes']['title'] = $element[$key]['#title'];
  1535. unset($element[$key]['#title']);
  1536. $option[] = drupal_render($element[$key]);
  1537. }
  1538. $row[] = $blank;
  1539. $row[] = $option[0];
  1540. $row[] = $blank;
  1541. $rows[] = $row;
  1542. $row = array();
  1543. $row[] = $option[1];
  1544. $row[] = $image;
  1545. $row[] = $option[2];
  1546. $rows[] = $row;
  1547. $row = array();
  1548. $row[] = $blank;
  1549. $row[] = $option[3];
  1550. $row[] = $blank;
  1551. $rows[] = $row;
  1552. /** @noinspection PhpUnhandledExceptionInspection */
  1553. return theme('table', array('header' => array(), 'rows' => $rows, 'attributes' => array('class' => array('image-anchor'))));
  1554. }