imagecache_coloractions.module 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  1. <?php
  2. /**
  3. * @file
  4. * Additional actions for imagecache processing.
  5. *
  6. * Exposes some of the simpler PHP 'imagefilter' actions (colorshift,
  7. * brightness, negative)
  8. * - A transparency masker for merging with backgrounds.
  9. * - A pseudo - file conversion feature.
  10. *
  11. * Compatible with the 2008 revision (imagecache 2)
  12. *
  13. * @author dan http://coders.co.nz
  14. * @author sydneyshan http://enigmadigital.net.au
  15. */
  16. if (! function_exists('imagecache_actions_calculate_relative_position') ) {
  17. module_load_include('inc', 'imagecache_actions', 'utility');
  18. }
  19. module_load_include('inc', 'imagecache_actions', 'utility-color');
  20. // There is no way to specify a file in hook_image_effect_info,
  21. // so placing this here for the time being.
  22. include_once dirname(__FILE__) . '/transparency.inc';
  23. /**
  24. * Implements hook_image_effect_info().
  25. *
  26. * Defines information about the supported effects.
  27. */
  28. function imagecache_coloractions_image_effect_info() {
  29. // @todo: standardize naming. requires a hook_update_n().
  30. $effects = array();
  31. $effects['coloractions_colorshift'] = array(
  32. 'label' => t('Color Shift'),
  33. 'help' => t('Adjust image colors.'),
  34. 'effect callback' => 'coloractions_colorshift_effect',
  35. 'dimensions passthrough' => TRUE,
  36. 'form callback' => 'coloractions_colorshift_form',
  37. 'summary theme' => 'coloractions_colorshift_summary',
  38. );
  39. $effects['imagecache_coloroverlay'] = array(
  40. 'label' => t('Color Overlay'),
  41. 'help' => t('Apply a color tint to an image (retaining blacks and whites).'),
  42. 'effect callback' => 'coloractions_coloroverlay_effect',
  43. 'dimensions passthrough' => TRUE,
  44. 'form callback' => 'coloractions_coloroverlay_form',
  45. 'summary theme' => 'coloractions_coloroverlay_summary',
  46. );
  47. $effects['coloractions_brightness'] = array(
  48. 'label' => t('Brightness'),
  49. 'help' => t('Adjust image brightness.'),
  50. 'effect callback' => 'coloractions_brightness_effect',
  51. 'dimensions passthrough' => TRUE,
  52. 'form callback' => 'coloractions_brightness_form',
  53. 'summary theme' => 'coloractions_brightness_summary',
  54. );
  55. // @todo: changing inverse to invert at this place requires a hook_update_n().
  56. $effects['coloractions_inverse'] = array(
  57. 'label' => t('Negative Image'),
  58. 'help' => t('Invert colors and brightness.'),
  59. 'effect callback' => 'coloractions_invert_effect',
  60. 'dimensions passthrough' => TRUE,
  61. 'form callback' => 'coloractions_invert_form',
  62. 'summary theme' => 'coloractions_invert_summary',
  63. );
  64. // @todo Convert may need a little more work.
  65. $effects['coloractions_convert'] = array(
  66. 'label' => t('Change file format'),
  67. 'help' => t('Choose to save the image as a different filetype.'),
  68. 'effect callback' => 'coloractions_convert_effect',
  69. 'dimensions passthrough' => TRUE,
  70. 'form callback' => 'coloractions_convert_form',
  71. 'summary theme' => 'coloractions_convert_summary',
  72. );
  73. $effects['coloractions_posterize'] = array(
  74. 'label' => t('Posterize'),
  75. 'help' => t('Reduce the image to a limited number of color levels per channel.'),
  76. 'effect callback' => 'coloractions_posterize_effect',
  77. 'dimensions passthrough' => TRUE,
  78. 'form callback' => 'coloractions_posterize_form',
  79. 'summary theme' => 'coloractions_posterize_summary',
  80. );
  81. $effects['imagecache_alpha'] = array(
  82. 'label' => t('Alpha Transparency'),
  83. 'help' => t('Adjust transparency.'),
  84. 'effect callback' => 'coloractions_alpha_effect',
  85. 'dimensions passthrough' => TRUE,
  86. 'form callback' => 'coloractions_alpha_form',
  87. 'summary theme' => 'coloractions_alpha_summary',
  88. );
  89. $effects['imagecache_adjustlevels'] = array(
  90. 'label' => t('Adjust Levels'),
  91. 'help' => t('Adjust the color levels of the image.'),
  92. 'effect callback' => 'coloractions_adjustlevels_effect',
  93. 'dimensions passthrough' => TRUE,
  94. 'form callback' => 'coloractions_adjustlevels_form',
  95. 'summary theme' => 'coloractions_adjustlevels_summary',
  96. );
  97. $effects['imagecache_desaturatealpha'] = array(
  98. 'label' => t('Desaturate Alpha'),
  99. 'help' => t('Desaturate the image while retaining transparency.'),
  100. 'effect callback' => 'coloractions_desaturatealpha_effect',
  101. 'dimensions passthrough' => TRUE,
  102. 'summary theme' => 'coloractions_desaturatealpha_summary',
  103. );
  104. return $effects;
  105. }
  106. /**
  107. * Implements hook_theme().
  108. *
  109. * Registers theme functions for the effect summaries.
  110. */
  111. function imagecache_coloractions_theme() {
  112. return array(
  113. 'coloractions_colorshift_summary' => array(
  114. 'variables' => array('data' => NULL),
  115. ),
  116. 'coloractions_coloroverlay_summary' => array(
  117. 'variables' => array('data' => NULL),
  118. ),
  119. 'coloractions_brightness_summary' => array(
  120. 'variables' => array('data' => NULL),
  121. ),
  122. 'coloractions_convert_summary' => array(
  123. 'variables' => array('data' => NULL),
  124. ),
  125. 'coloractions_posterize_summary' => array(
  126. 'variables' => array('data' => NULL),
  127. ),
  128. 'coloractions_alpha_summary' => array(
  129. 'variables' => array('data' => NULL),
  130. 'file' => 'transparency.inc',
  131. ),
  132. 'coloractions_adjustlevels_summary' => array(
  133. 'variables' => array('data' => NULL),
  134. ),
  135. 'coloractions_desaturatealpha_summary' => array(
  136. 'variables' => array('data' => NULL),
  137. ),
  138. );
  139. }
  140. /**
  141. * Implements hook_image_style_flush().
  142. *
  143. * This hook checks if the style contains a change format image effect and, if
  144. * so, creates an .htaccess file in the root of the derivative folder that
  145. * forces the correct Content-Type header on images served from that folder.
  146. *
  147. * @param array $style
  148. */
  149. function imagecache_coloractions_image_style_flush($style) {
  150. if (!is_array($style)) {
  151. // See [#2190759].
  152. return;
  153. }
  154. // Error in core: the old style + set of effects is passed in. This means
  155. // that when a convert effect is added or deleted we won't notice. So we
  156. // "change" the order of execution by duplicating these lines from
  157. // image_style_flush():
  158. // Clear image style and effect caches.
  159. cache_clear_all('image_styles', 'cache');
  160. cache_clear_all('image_effects:', 'cache', TRUE);
  161. drupal_static_reset('image_styles');
  162. drupal_static_reset('image_effects');
  163. // Now load the current state of our style.
  164. $new_style = image_style_load(isset($style['name']) ? $style['name'] : NULL, isset($style['isid']) ? $style['isid'] : NULL);
  165. // If the style is flushed because it is being deleted it might be gone.
  166. if (is_array($new_style)) {
  167. // Now back to our actual work: determine if we have to crate an .htaccess
  168. // file.
  169. include_once dirname(__FILE__) . '/imagecache_coloractions.htaccess_creator.inc';
  170. imagecache_coloractions_create_htaccess_for_style($new_style);
  171. }
  172. }
  173. /**
  174. * Image effect form callback for the color shift effect.
  175. *
  176. * @param array $data
  177. * The current configuration for this image effect.
  178. *
  179. * @return array
  180. * The form definition for this effect.
  181. */
  182. function coloractions_colorshift_form(array $data) {
  183. $defaults = array(
  184. 'RGB' => array(
  185. 'HEX' => '#FF0000',
  186. ),
  187. );
  188. $data = array_merge($defaults, (array) $data);
  189. $form = array('#theme' => 'imagecache_rgb_form');
  190. $form['RGB'] = imagecache_rgb_form($data['RGB']);
  191. $form['note'] = array('#value' => t("<p>
  192. Note that colorshift is a mathematical filter that doesn't always
  193. have the expected result.
  194. To shift an image precisely TO a target color,
  195. desaturate (greyscale) it before colorizing.
  196. The hue (color wheel) is the <em>direction</em> the
  197. existing colors are shifted. The tone (inner box) is the amount.
  198. Keep the tone half-way up the left site of the color box
  199. for best results.
  200. </p>"));
  201. return $form;
  202. }
  203. /**
  204. * Implements theme_hook() for the color shift effect summary.
  205. *
  206. * @param array $variables
  207. * An associative array containing:
  208. * - data: The current configuration for this image effect.
  209. *
  210. * @return string
  211. * The HTML for the summary of this image effect.
  212. * @ingroup themeable
  213. */
  214. function theme_coloractions_colorshift_summary(array $variables) {
  215. return theme_imagecacheactions_rgb($variables['data']);
  216. }
  217. /**
  218. * Image effect callback for the color shift effect.
  219. *
  220. * @param stdClass $image
  221. * @param array $data
  222. *
  223. * @return bool
  224. * true on success, false otherwise.
  225. */
  226. function coloractions_colorshift_effect(stdClass $image, array $data) {
  227. // convert color from hex (as it is stored in the UI)
  228. if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) {
  229. $data['RGB'] = array_merge($data['RGB'], $deduced);
  230. }
  231. return image_toolkit_invoke('colorshift', $image, array($data));
  232. }
  233. /**
  234. * GD toolkit specific implementation of the color shift effect.
  235. *
  236. * @param stdClass $image
  237. * @param array $data
  238. * The parameters for this effect.
  239. *
  240. * @return bool
  241. * true on success, false otherwise.
  242. */
  243. function image_gd_colorshift(stdClass $image, array $data) {
  244. $RGB = $data['RGB'];
  245. if (!function_exists('imagefilter')) {
  246. module_load_include('inc', 'imagecache_actions', 'imagefilter');
  247. }
  248. return imagefilter($image->resource, 4, $RGB['red'], $RGB['green'], $RGB['blue']);
  249. }
  250. /**
  251. * Imagemagick toolkit specific implementation of the color shift effect.
  252. *
  253. * @param stdClass $image
  254. * @param array $data
  255. * The parameters for this effect.
  256. *
  257. * @return bool
  258. * true on success, false otherwise.
  259. */
  260. function image_imagemagick_colorshift(stdClass $image, array $data) {
  261. $RGB = $data['RGB'];
  262. $image->ops[] = "-fill rgb" . escapeshellcmd('(') . "{$RGB['red']},{$RGB['green']},{$RGB['blue']}" . escapeshellcmd(')') . " -colorize 50" . escapeshellcmd('%');
  263. return TRUE;
  264. }
  265. /**
  266. * Image effect form callback for the color overlay effect.
  267. *
  268. * @param array $data
  269. * The current configuration for this image effect.
  270. *
  271. * @return array
  272. * The form definition for this effect.
  273. */
  274. function coloractions_coloroverlay_form(array $data) {
  275. $defaults = array(
  276. 'RGB' => array(
  277. 'HEX' => '#E2DB6A',
  278. ),
  279. );
  280. $data = array_merge($defaults, (array) $data);
  281. $form = array('#theme' => 'imagecache_rgb_form');
  282. $form['RGB'] = imagecache_rgb_form($data['RGB']);
  283. $form['note'] = array('#value' => t("<p>
  284. Note that color overlay is a mathematical filter that doesn't always
  285. have the expected result.
  286. To shift an image precisely TO a target color,
  287. desaturate (greyscale) it before colorizing.
  288. The hue (color wheel) is the <em>direction</em> the
  289. existing colors are shifted. The tone (inner box) is the amount.
  290. Keep the tone half-way up the left site of the color box
  291. for best results.
  292. </p>"));
  293. return $form;
  294. }
  295. /**
  296. * Implements theme_hook() for the color overlay effect summary.
  297. *
  298. * @param array $variables
  299. * An associative array containing:
  300. * - data: The current configuration for this image effect.
  301. *
  302. * @return string
  303. * The HTML for the summary of this image effect.
  304. * @ingroup themeable
  305. */
  306. function theme_coloractions_coloroverlay_summary(array $variables) {
  307. return theme_imagecacheactions_rgb($variables['data']);
  308. }
  309. /**
  310. * Image effect callback for the color overlay effect.
  311. *
  312. * @param stdClass $image
  313. * @param array $data
  314. *
  315. * @return bool
  316. * true on success, false otherwise.
  317. */
  318. function coloractions_coloroverlay_effect(stdClass $image, array $data) {
  319. // convert color from hex (as it is stored in the UI)
  320. if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) {
  321. $data['RGB'] = array_merge($data['RGB'], $deduced);
  322. }
  323. return image_toolkit_invoke('coloroverlay', $image, array($data));
  324. }
  325. /**
  326. * GD toolkit specific implementation of the color overlay effect.
  327. *
  328. * @param stdClass $image
  329. * @param array $data
  330. * The parameters for this effect.
  331. *
  332. * @return bool
  333. * true on success, false otherwise.
  334. */
  335. function image_gd_coloroverlay(stdClass $image, array $data) {
  336. $RGB = $data['RGB'];
  337. $w = $image->info['width'];
  338. $h = $image->info['height'];
  339. for($y=0;$y<$h;$y++) {
  340. for($x=0;$x<$w;$x++) {
  341. $rgb = imagecolorat($image->resource, $x, $y);
  342. $source = imagecolorsforindex($image->resource, $rgb);
  343. if($source['red'] <= 128){
  344. $final_r = (2 * $source['red'] * $RGB['red'])/256;
  345. }else{
  346. $final_r = 255 - (((255 - (2 * ($source['red'] - 128))) * (255 - $RGB['red']))/256);
  347. }
  348. if($source['green'] <= 128){
  349. $final_g = (2 * $source['green'] * $RGB['green'])/256;
  350. }else{
  351. $final_g = 255 - (((255 - (2 * ($source['green'] - 128))) * (255 - $RGB['green']))/256);
  352. }
  353. if($source['blue'] <= 128){
  354. $final_b = (2 * $source['blue'] * $RGB['blue'])/256;
  355. }else{
  356. $final_b = 255 - (((255 - (2 * ($source['blue'] - 128))) * (255 - $RGB['blue']))/256);
  357. }
  358. $final_colour = imagecolorallocatealpha($image->resource, $final_r, $final_g, $final_b, $source['alpha']);
  359. imagesetpixel($image->resource, $x, $y, $final_colour);
  360. }
  361. }
  362. return TRUE;
  363. }
  364. /**
  365. * Imagemagick toolkit specific implementation of the color overlay effect.
  366. *
  367. * @param stdClass $image
  368. * @param array $data
  369. * The parameters for this effect.
  370. *
  371. * @return bool
  372. * true on success, false otherwise.
  373. */
  374. function image_imagemagick_coloroverlay(stdClass $image, array $data) {
  375. $RGB = $data['RGB'];
  376. $image->ops[] = escapeshellcmd('(') . " +clone +matte -fill rgb" . escapeshellcmd('(') . "{$RGB['red']},{$RGB['green']},{$RGB['blue']}" . escapeshellcmd(')') . " -colorize 100" . escapeshellcmd('%') . " +clone +swap -compose overlay -composite " . escapeshellcmd(')') . " -compose SrcIn -composite";
  377. return TRUE;
  378. }
  379. /**
  380. * Image effect form callback for the brightness effect.
  381. *
  382. * @param array $data
  383. * The current configuration for this image effect.
  384. *
  385. * @return array
  386. * The form definition for this effect.
  387. */
  388. function coloractions_brightness_form(array $data) {
  389. $default = array('filter_arg1' => '100');
  390. $data = array_merge($default, (array) $data);
  391. $form = array();
  392. $form['help'] = array('#value' => t("The brightness effect seldom looks good on its own, but can be useful to wash out an image before making it transparent - eg for a watermark."));
  393. $form['filter_arg1'] = array(
  394. '#type' => 'textfield',
  395. '#title' => t('Brightness'),
  396. '#description' => t('-255 - +255'),
  397. '#default_value' => $data['filter_arg1'],
  398. '#size' => 3,
  399. );
  400. return $form;
  401. }
  402. /**
  403. * Implements theme_hook() for the brightness effect summary.
  404. *
  405. * @param array $variables
  406. * An associative array containing:
  407. * - data: The current configuration for this image effect.
  408. *
  409. * @return string
  410. * The HTML for the summary of this image effect.
  411. * @ingroup themeable
  412. */
  413. function theme_coloractions_brightness_summary(array $variables) {
  414. return t("Adjust") . " : " . $variables['data']['filter_arg1'];
  415. }
  416. /**
  417. * Image effect callback for the brightness effect.
  418. *
  419. * @param stdClass $image
  420. * @param array $data
  421. *
  422. * @return bool
  423. * true on success, false otherwise.
  424. */
  425. function coloractions_brightness_effect(stdClass $image, array $data) {
  426. return image_toolkit_invoke('brightness', $image, array($data));
  427. }
  428. /**
  429. * GD toolkit specific implementation of the brightness effect.
  430. *
  431. * @param stdClass $image
  432. * @param array $data
  433. * The parameters for this effect.
  434. *
  435. * @return bool
  436. * true on success, false otherwise.
  437. */
  438. function image_gd_brightness(stdClass $image, array $data) {
  439. if (!function_exists('imagefilter')) {
  440. module_load_include('inc', 'imagecache_actions', 'imagefilter'); }
  441. return imagefilter($image->resource, 2, $data['filter_arg1']);
  442. }
  443. /**
  444. * Imagemagick toolkit specific implementation of the brightness effect.
  445. *
  446. * @param stdClass $image
  447. * @param array $data
  448. * The parameters for this effect.
  449. *
  450. * @return bool
  451. * true on success, false otherwise.
  452. */
  453. function image_imagemagick_brightness(stdClass $image, array $data) {
  454. $image->ops[] = "-modulate " . (int)(100 + ( $data['filter_arg1'] / 128 * 100 ));
  455. return TRUE;
  456. }
  457. /**
  458. * Image effect form callback for the image invert effect.
  459. *
  460. * This effect has no parameters.
  461. *
  462. * param array $data
  463. * The current configuration for this image effect.
  464. *
  465. * @return array
  466. * The form definition for this effect.
  467. */
  468. function coloractions_invert_form(/*array $data*/) {
  469. $form = array();
  470. return $form;
  471. }
  472. /**
  473. * Image effect callback for the image invert effect.
  474. *
  475. * @param stdClass $image
  476. * @param array $data
  477. *
  478. * @return bool
  479. * true on success, false otherwise.
  480. */
  481. function coloractions_invert_effect(stdClass $image, array $data) {
  482. return image_toolkit_invoke('invert', $image, array($data));
  483. }
  484. /**
  485. * GD toolkit specific implementation of the image invert effect.
  486. *
  487. * @param stdClass $image
  488. * param array $data
  489. * The parameters for this effect.
  490. *
  491. * @return bool
  492. * true on success, false otherwise.
  493. */
  494. function image_gd_invert(stdClass $image/*, array $data*/) {
  495. if (!function_exists('imagefilter')) {
  496. module_load_include('inc', 'imagecache_actions', 'imagefilter');
  497. }
  498. return imagefilter($image->resource, 0);
  499. }
  500. /**
  501. * Imagemagick toolkit specific implementation of the image invert effect.
  502. *
  503. * param stdClass $image
  504. * param array $data
  505. * The parameters for this effect.
  506. *
  507. * @return bool
  508. * true on success, false otherwise.
  509. */
  510. function image_imagemagick_invert(/*stdClass $image, array $data*/) {
  511. // @todo
  512. return FALSE;
  513. }
  514. /**
  515. * Image effect form callback for the convert image format effect.
  516. *
  517. * @param array $data
  518. * The current configuration for this image effect.
  519. *
  520. * @return array
  521. * The form definition for this effect.
  522. */
  523. function coloractions_convert_form(array $data) {
  524. $defaults = array(
  525. 'format' => 'image/png',
  526. 'quality' => '75',
  527. );
  528. $data = array_merge($defaults, $data);
  529. $form = array(
  530. 'help' => array(
  531. '#markup' => t("If you've been using transparencies in the process, the result may get saved as a PNG (as the image was treated as a one in in-between processes). If this is not desired (file sizes may get too big) you should use this process to force a flatten action before saving. "),
  532. ),
  533. 'help2' => array(
  534. '#markup' => t("For technical reasons, changing the file format within imagecache does <em>not</em> change the filename suffix. A png may be saved as a *.jpg or vice versa. This may confuse some browsers and image software, but most of them have no trouble. "),
  535. ),
  536. 'format' => array(
  537. '#title' => t("File format"),
  538. '#type' => 'select',
  539. '#default_value' => isset($data['format']) ? $data['format'] : 'image/png',
  540. '#options' => coloractions_file_formats(),
  541. ),
  542. 'quality' => array(
  543. '#type' => 'textfield',
  544. '#title' => t('Quality'),
  545. '#description' => t('Override the default image quality. Works for Imagemagick only. Ranges from 0 to 100. For jpg, higher values mean better image quality but bigger files. For png it is a combination of compression and filter'),
  546. '#size' => 10,
  547. '#maxlength' => 3,
  548. '#default_value' => $data['quality'],
  549. '#field_suffix' => '%',
  550. ),
  551. );
  552. return $form;
  553. }
  554. /**
  555. * Implements theme_hook() for the convert image format effect summary.
  556. *
  557. * @param array $variables
  558. * An associative array containing:
  559. * - data: The current configuration for this image effect.
  560. *
  561. * @return string
  562. * The HTML for the summary of this image effect.
  563. * @ingroup themeable
  564. */
  565. function theme_coloractions_convert_summary($variables) {
  566. $data = $variables['data'];
  567. $formats = coloractions_file_formats();
  568. if ($formats[$data['format']] == 'jpg') {
  569. return t('Convert to: @format, quality: @quality%', array(
  570. '@format' => $formats[$data['format']],
  571. '@quality' => $data['quality']
  572. ));
  573. }
  574. else {
  575. return t("Convert to") .": ". $formats[$data['format']];
  576. }
  577. }
  578. /**
  579. * Image effect callback for the convert image format effect.
  580. *
  581. * @param stdClass $image
  582. * @param array $data
  583. *
  584. * @return bool
  585. * true on success, false otherwise.
  586. */
  587. function coloractions_convert_effect(stdClass $image, array $data) {
  588. $formats = coloractions_file_formats();
  589. $image->info['mime_type'] = $data['format'];
  590. $image->info['extension'] = $formats[$data['format']];
  591. image_toolkit_invoke('convert', $image, array($data));
  592. return TRUE;
  593. }
  594. /**
  595. * GD toolkit specific implementation of the convert image format effect.
  596. *
  597. * param stdClass $image
  598. * param array $data
  599. * The parameters for this effect.
  600. *
  601. * @return bool
  602. * true on success, false otherwise.
  603. */
  604. function image_gd_convert(/*stdClass $image, array $data*/) {
  605. return TRUE;
  606. }
  607. /**
  608. * Imagemagick toolkit specific implementation of the color shift effect.
  609. *
  610. * Converting the image format with imagemagick is done by prepending the output
  611. * format to the target file separated by a colon (:). This is done with
  612. * hook_imagemagick_arguments_alter(), see below.
  613. *
  614. * @param stdClass $image
  615. * @param array $data
  616. * The parameters for this effect.
  617. *
  618. * @return bool
  619. * true on success, false otherwise.
  620. */
  621. function image_imagemagick_convert(stdClass $image, array $data) {
  622. $image->ops['output_format'] = $image->info['extension'];
  623. $image->ops['custom_quality_value'] = (int) $data['quality'];
  624. return TRUE;
  625. }
  626. /**
  627. * Implements hook_imagemagick_arguments_alter().
  628. *
  629. * This hook moves a change in output format from the args (action list) to the
  630. * destination format setting within the context.
  631. */
  632. function imagecache_coloractions_imagemagick_arguments_alter(&$args, &$context) {
  633. if (isset($args['output_format'])) {
  634. $context['destination_format'] = $args['output_format'];
  635. unset($args['output_format']);
  636. }
  637. if (isset($args['custom_quality_value'])) {
  638. $args['quality'] = sprintf('-quality %d', $args['custom_quality_value']);
  639. unset($args['custom_quality_value']);
  640. }
  641. }
  642. /**
  643. * Mini mime-type list
  644. *
  645. * image_type_to_extension and image_type_to_mime_type?
  646. */
  647. function coloractions_file_formats() {
  648. return array('image/jpeg' => 'jpg', 'image/gif' => 'gif', 'image/png' => 'png');
  649. }
  650. /**
  651. * Image effect form callback for the posterize effect.
  652. *
  653. * @param array $data
  654. * The current configuration for this image effect.
  655. *
  656. * @return array
  657. * The form definition for this effect.
  658. */
  659. function coloractions_posterize_form(array $data) {
  660. $form = array();
  661. $form['colors'] = array(
  662. '#type' => 'textfield',
  663. '#title' => t('Color levels per channel'),
  664. '#default_value' => isset($data['colors']) ? $data['colors'] : '',
  665. '#required' => TRUE,
  666. '#size' => 10,
  667. '#element_validate' => array('image_effect_integer_validate'),
  668. '#allow_negative' => FALSE,
  669. '#description' => t('Number of unique values per color channel to reduce this image to. The transparency channel is left unchanged. This effect can be used to reduce file size on png images.'),
  670. );
  671. return $form;
  672. }
  673. /**
  674. * Implements theme_hook() for the posterize effect summary.
  675. *
  676. * @param array $variables
  677. * An associative array containing:
  678. * - data: The current configuration for this image effect.
  679. *
  680. * @return string
  681. * The HTML for the summary of this image effect.
  682. * @ingroup themeable
  683. */
  684. function theme_coloractions_posterize_summary(array $variables) {
  685. return t(': Reduce to @colors color levels per channel', array('@colors' => $variables['data']['colors']));
  686. }
  687. /**
  688. * Image effect callback for the posterize effect.
  689. *
  690. * @param stdClass $image
  691. * @param array $data
  692. *
  693. * @return bool
  694. * true on success, false otherwise.
  695. */
  696. function coloractions_posterize_effect(stdClass $image, array $data) {
  697. if (!image_toolkit_invoke('posterize', $image, array($data['colors']))) {
  698. watchdog('imagecache_actions', 'Image posterize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array(
  699. '%toolkit' => $image->toolkit,
  700. '%path' => $image->source,
  701. '%mimetype' => $image->info['mime_type'],
  702. '%dimensions' => $image->info['height'] . 'x' . $image->info['height'],
  703. ), WATCHDOG_ERROR);
  704. return FALSE;
  705. }
  706. return TRUE;
  707. }
  708. /**
  709. * GD toolkit specific implementation of the posterize effect.
  710. *
  711. * Based on:
  712. * http://www.qtcentre.org/threads/36385-Posterizes-an-image-with-results-identical-to-Gimp-s-Posterize-command?p=167712#post167712
  713. *
  714. * @param stdClass $image
  715. * @param int $colors
  716. * The parameter for this effect.
  717. *
  718. * @return bool
  719. * true on success, false otherwise.
  720. */
  721. function image_gd_posterize(stdClass $image, $colors) {
  722. // Value step for colors per channel.
  723. $round_to = 255 / ($colors - 1);
  724. $alpha_bit_mask = 255 << 24;
  725. for ($x = imagesx($image->resource); $x--; ) {
  726. for ($y = imagesy($image->resource); $y--; ) {
  727. $rgb = imagecolorat($image->resource, $x, $y);
  728. // Use bitmasks to extract numbers we want, faster equivalent to imagecolorsforindex().
  729. $a = $rgb & $alpha_bit_mask; // Alpha
  730. $r = $rgb >> 16 & 255; // Red
  731. $g = $rgb >> 8 & 255; // Green
  732. $b = $rgb & 255; // Blue
  733. // (int) (value + 0.5) faster equivalent to round() and already an int.
  734. $new_r = (int) (((int) ($r / $round_to + 0.5)) * $round_to + 0.5);
  735. $new_g = (int) (((int) ($g / $round_to + 0.5)) * $round_to + 0.5);
  736. $new_b = (int) (((int) ($b / $round_to + 0.5)) * $round_to + 0.5);
  737. // Faster equivalent to imagecolorallocatealpha().
  738. $color_combined = $a | ($new_r << 16) | ($new_g << 8) | $new_b;
  739. imagesetpixel($image->resource, $x, $y, $color_combined);
  740. }
  741. }
  742. return TRUE;
  743. }
  744. /**
  745. * Imagemagick toolkit specific implementation of the color shift effect.
  746. *
  747. * @param stdClass $image
  748. * @param int $colors
  749. * The parameter for this effect.
  750. *
  751. * @return bool
  752. * true on success, false otherwise.
  753. */
  754. function image_imagemagick_posterize(stdClass $image, $colors) {
  755. // In newer versions of ImageMagick dithering has no effect on posterize.
  756. // Turn dithering off on older versions of ImageMagick for consistency.
  757. $image->ops[] = ' +dither -posterize ' . (int) $colors;
  758. return TRUE;
  759. }
  760. /**
  761. * Image effect form callback for the brightness effect.
  762. *
  763. * Settings for color level adjustment actions.
  764. *
  765. * @param array $data
  766. * The current configuration for this image effect.
  767. *
  768. * @return array
  769. * The form definition for this effect.
  770. */
  771. function coloractions_adjustlevels_form(array $data) {
  772. $defaults = array(
  773. 'independent_colors' => FALSE,
  774. 'all_colors' => array(
  775. 'low' => 0,
  776. 'high' => 1,
  777. ),
  778. 'per_color' => array(
  779. 'low_red' => 0,
  780. 'high_red' => 1,
  781. 'low_green' => 0,
  782. 'high_green' => 1,
  783. 'low_blue' => 0,
  784. 'high_blue' => 1,
  785. ),
  786. );
  787. $data = array_merge($defaults, $data);
  788. $form = array(
  789. '#type' => 'container',
  790. 'help' => array(
  791. '#type' => 'markup',
  792. '#markup' => t("<p>Adjusting color levels scales the given channels to a range specified by the 'low' and 'high' values.
  793. These 'low' and 'high' values can be any value between 0 and 1.
  794. E.g. assume that 'low' is 0.2 and 'high' is 0.9.
  795. Pixels that had a value of 0, will get a value of 0.2 * 255 = 51 and pixels that had a value of 255, will get a value of 0.9 * 255 = 229.</p>
  796. <p>Note that color level adjustment is a mathematical filter and a such doesn't do automatic balancing.</p>"),
  797. ),
  798. '#element_validate' => array('coloractions_validate_form'),
  799. ) ;
  800. $form['independent_colors'] = array(
  801. '#type' => 'checkbox',
  802. '#title' => t('Set each color independently'),
  803. '#default_value' => $data['independent_colors'],
  804. );
  805. $form['all_colors'] = array(
  806. '#type' => 'container',
  807. '#tree' => TRUE,
  808. '#title' => t('All colors range'),
  809. '#required' => !$data['independent_colors'],
  810. '#states' => array(
  811. 'visible' => array(':input[name="data[independent_colors]"]' => array('checked' => FALSE)),
  812. 'required' => array(':input[name="data[independent_colors]"]' => array('checked' => FALSE)),
  813. ),
  814. );
  815. $form['all_colors'] += coloractions_adjustlevels_form_helper(array(
  816. 'low' => array('title' => t('Low'), 'default' => $data['all_colors']['low']),
  817. 'high' => array('title' => t('High'), 'default' => $data['all_colors']['high']),
  818. ));
  819. $form['per_color'] = array(
  820. '#type' => 'container',
  821. '#tree' => TRUE,
  822. '#title' => t('Individual Color Ranges'),
  823. '#required' => $data['independent_colors'],
  824. '#states' => array(
  825. 'visible' => array(':input[name="data[independent_colors]"]' => array('checked' => TRUE)),
  826. 'required' => array(':input[name="data[independent_colors]"]' => array('checked' => TRUE)),
  827. ),
  828. );
  829. $form['per_color'] += coloractions_adjustlevels_form_helper(array(
  830. 'low_red' => array('title' => t('Red Low'), 'default' => $data['per_color']['low_red']),
  831. 'high_red' => array('title' => t('Red High'), 'default' => $data['per_color']['high_red']),
  832. 'low_green' => array('title' => t('Green Low'), 'default' => $data['per_color']['low_green']),
  833. 'high_green' => array('title' => t('Green High'), 'default' => $data['per_color']['high_green']),
  834. 'low_blue' => array('title' => t('Blue Low'), 'default' => $data['per_color']['low_blue']),
  835. 'high_blue' => array('title' => t('Blue High'), 'default' => $data['per_color']['high_blue']),
  836. ));
  837. return $form;
  838. }
  839. /**
  840. * Helper function to create the form for the color level adjustment effect.
  841. *
  842. * @param array $data
  843. * Array containing the form elements
  844. * names as keys for array elements containing title and default value.
  845. *
  846. * @return array
  847. */
  848. function coloractions_adjustlevels_form_helper(array $data) {
  849. $form = array();
  850. foreach ($data as $name => $value) {
  851. $form[$name] = array(
  852. '#type' => 'textfield',
  853. '#title' => $value['title'],
  854. '#default_value' => $value['default'],
  855. '#size' => 5,
  856. '#element_validate' => array('coloractions_validate_scale_0_1'),
  857. );
  858. }
  859. return $form;
  860. }
  861. /**
  862. * Form element validation handler for elements that should contain a number
  863. * between 0 and 1.
  864. */
  865. function coloractions_validate_scale_0_1($element/*, &$form_state*/) {
  866. $value = $element['#value'];
  867. if ($value != '' && (!is_numeric($value) || (float) $value > 1.0 || (float) $value < 0.0)) {
  868. form_error($element, t('%name must be a value between 0 and 1.', array('%name' => $element['#title'])));
  869. }
  870. }
  871. /**
  872. * Form element validation handler that compares low and high values.
  873. */
  874. function coloractions_validate_form($element/*, &$form_state*/) {
  875. $independent_colors = !empty($element['independent_colors']['#value']);
  876. if (!$independent_colors) {
  877. // Compare low and high.
  878. coloractions_validate_low_and_high($element, 'all_colors', '');
  879. }
  880. else {
  881. // Compare low and high per color
  882. coloractions_validate_low_and_high($element, 'per_color', '_red');
  883. coloractions_validate_low_and_high($element, 'per_color', '_green');
  884. coloractions_validate_low_and_high($element, 'per_color', '_blue');
  885. }
  886. }
  887. function coloractions_validate_low_and_high($element, $fieldset, $suffix) {
  888. if ((float) $element[$fieldset]["low$suffix"]['#value'] > (float) $element[$fieldset]["high$suffix"]['#value']) {
  889. form_error($element[$fieldset]["high$suffix"], t('%name-high must be higher then %name-low.',
  890. array('%name-high' => $element[$fieldset]["high$suffix"]['#title'], '%name-low' => $element[$fieldset]["low$suffix"]['#title'])));
  891. }
  892. }
  893. /**
  894. * Implements theme_hook() for the adjust color levels effect summary.
  895. *
  896. * @param array $variables
  897. * An associative array containing:
  898. * - data: The current configuration for this image effect.
  899. *
  900. * @return string
  901. * The HTML for the summary of this image effect.
  902. * @ingroup themeable
  903. */
  904. function theme_coloractions_adjustlevels_summary(array $variables) {
  905. $data = $variables['data'];
  906. if (empty($data['independent_colors'])) {
  907. return t('@range',
  908. array('@range' => "[{$data['all_colors']['low']} - {$data['all_colors']['high']}]"));
  909. }
  910. else {
  911. return t('red: @red-range, green: @green-range, blue: @blue-range',
  912. array('@red-range' => "[{$data['per_color']['low_red']} - {$data['per_color']['high_red']}]",
  913. '@green-range' => "[{$data['per_color']['low_green']} - {$data['per_color']['high_green']}]",
  914. '@blue-range' => "[{$data['per_color']['low_blue']} - {$data['per_color']['high_blue']}]"));
  915. }
  916. }
  917. /**
  918. * Image effect callback for the adjust levels effect.
  919. *
  920. * @param stdClass $image
  921. * @param array $data
  922. *
  923. * @return bool
  924. * true on success, false otherwise.
  925. */
  926. function coloractions_adjustlevels_effect(stdClass $image, array $data) {
  927. return image_toolkit_invoke('adjustlevels', $image, array($data));
  928. }
  929. /**
  930. * GD toolkit specific implementation of the adjust levels effect.
  931. *
  932. * @param stdClass $image
  933. * @param array $data
  934. * The parameters for this effect.
  935. *
  936. * @return bool
  937. * true on success, false otherwise.
  938. */
  939. function image_gd_adjustlevels(stdClass $image, array $data) {
  940. $width = $image->info['width'];
  941. $height = $image->info['height'];
  942. if ($data['independent_colors']) {
  943. $lower_r = $data['per_color']['low_red'] * 255;
  944. $factor_r = ($data['per_color']['high_red'] * 255 - $lower_r) / 255;
  945. $lower_g = $data['per_color']['low_green'] * 255;
  946. $factor_g = ($data['per_color']['high_green'] * 255 - $lower_g) / 255;
  947. $lower_b = $data['per_color']['low_blue'] * 255;
  948. $factor_b = ($data['per_color']['high_blue'] * 255 - $lower_b) / 255;
  949. }
  950. else {
  951. $lower_r = $lower_g = $lower_b = $data['all_colors']['low'] * 255;
  952. $factor_r = $factor_g = $factor_b = ($data['all_colors']['high'] * 255 - $lower_r) / 255;
  953. }
  954. for ($y = 0; $y < $height; $y++) {
  955. for ($x = 0; $x < $width; $x++) {
  956. $rgb = imagecolorat($image->resource, $x, $y);
  957. $source = imagecolorsforindex($image->resource, $rgb);
  958. $final_r = $lower_r + $factor_r * $source['red'];
  959. $final_g = $lower_g + $factor_g * $source['green'];
  960. $final_b = $lower_b + $factor_b * $source['blue'];
  961. $final_colour = imagecolorallocatealpha($image->resource, $final_r, $final_g, $final_b, $source['alpha']);
  962. imagesetpixel($image->resource, $x, $y, $final_colour);
  963. }
  964. }
  965. return TRUE;
  966. }
  967. /**
  968. * Implements theme_hook() for the desaturate alpha effect summary.
  969. *
  970. * param array $variables
  971. * An associative array containing:
  972. * - data: The current configuration for this image effect.
  973. *
  974. * @return string
  975. * The HTML for the summary of this image effect.
  976. * @ingroup themeable
  977. */
  978. function theme_coloractions_desaturatealpha_summary(/*array $variables*/) {
  979. return t(': Desaturates the image while retaining transparency.');
  980. }
  981. /**
  982. * Image effect callback for the desaturate alpha effect.
  983. *
  984. * @param stdClass $image
  985. * @param array $data
  986. *
  987. * @return bool
  988. * true on success, false otherwise.
  989. */
  990. function coloractions_desaturatealpha_effect(stdClass $image, array $data) {
  991. return image_toolkit_invoke('desaturatealpha', $image, array($data));
  992. }
  993. /**
  994. * GD toolkit specific implementation of the adjust levels effect.
  995. *
  996. * @param stdClass $image
  997. * param array $data
  998. * The parameters for this effect.
  999. *
  1000. * @return bool
  1001. * true on success, false otherwise.
  1002. */
  1003. function image_gd_desaturatealpha(stdClass $image/*, array $data*/) {
  1004. imagealphablending($image->resource, FALSE);
  1005. $result = imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
  1006. imagealphablending($image->resource, TRUE);
  1007. return $result;
  1008. }