utility.inc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. /**
  3. * @file utility.inc: uitility form, conversion and rendering functions for
  4. * image processing.
  5. */
  6. /*
  7. * File field handling.
  8. */
  9. /**
  10. * @return string
  11. */
  12. function imagecache_actions_file_field_description() {
  13. return t('File is either a file with one of the valid schemes, an absolute path, or a relative path (relative to the current directory, probably the Drupal site root). Valid schemes are a.o. private://, public://, and if the !module module has been installed, also module:// and theme://.',
  14. array('!module' => l('System Stream Wrapper', 'http://drupal.org/project/system_stream_wrapper', array('external' => TRUE))));
  15. }
  16. /**
  17. * Validates that the file as specified in the element exists and is readable.
  18. *
  19. * This is a Form API #element_validate callback.
  20. *
  21. * @param array $element
  22. * @param array $form_status
  23. */
  24. function imagecache_actions_validate_file(&$element, &$form_status) {
  25. if (!imagecache_actions_find_file($element['#value'])) {
  26. form_error($element, t("Unable to find the file '%file'. Please check the path.", array('%file' => $element['#value'])) );
  27. }
  28. }
  29. /**
  30. * Looks up and returns the full path of the given file.
  31. *
  32. * We accept files with the following schemes:
  33. * - private://
  34. * - public://
  35. * - temporary://
  36. * - module://{module}/{component}
  37. *
  38. * Files without a scheme are looked up as are:
  39. * - relative: relative to the current directory (probably $drupal_root).
  40. * - absolute: as is.
  41. *
  42. * @param string $file
  43. * A file name, with a scheme, relative, or absolute.
  44. *
  45. * @return string|false
  46. * The full file path of the file, so the image toolkit knows exactly where it
  47. * is.
  48. */
  49. function imagecache_actions_find_file($file) {
  50. $result = FALSE;
  51. if (is_readable($file)) {
  52. $result = drupal_realpath($file);
  53. }
  54. return $result;
  55. }
  56. /**
  57. * Loads the given file as an image object.
  58. *
  59. * @param string $file
  60. * A file name, with a scheme, relative, or absolute.
  61. * @param bool $toolkit
  62. *
  63. * @return object|FALSE
  64. * The image object.
  65. *
  66. * @see imagecache_actions_find_file()
  67. * @see image_load()
  68. */
  69. function imagecache_actions_image_load($file, $toolkit = FALSE) {
  70. $full_path = imagecache_actions_find_file($file);
  71. if (!$full_path) {
  72. trigger_error("Failed to find file $file.", E_USER_ERROR);
  73. return FALSE;
  74. }
  75. $image = image_load($full_path, $toolkit);
  76. if (!$image) {
  77. trigger_error("Failed to open file '$file' as an image resource.", E_USER_ERROR);
  78. }
  79. return $image;
  80. }
  81. /**
  82. * Return an array with context information about the image.
  83. *
  84. * This information can e.g. be used by effects that allow custom PHP like
  85. * - Custom action.
  86. * - Text from PHP code.
  87. * - Text from alt or title.
  88. *
  89. * @param object $image
  90. * The image object.
  91. * @param array $data
  92. * An associative array with the effect options.
  93. *
  94. * @return array
  95. * An associative array with context information about the image.
  96. */
  97. function imagecache_actions_get_image_context($image, $data) {
  98. // Store context about the image.
  99. $image_context = array(
  100. 'effect_data' => $data,
  101. 'managed_file' => NULL,
  102. 'referring_entities' => array(),
  103. 'entity' => NULL,
  104. 'image_field' => NULL,
  105. );
  106. // Find the managed file object (at most 1 object as 'uri' is a unique index).
  107. $managed_file = reset(file_load_multiple(array(), array('uri' => $image->source)));
  108. if ($managed_file !== FALSE) {
  109. $image_context['managed_file'] = $managed_file;
  110. // And find the entities referring to this managed file.
  111. $references = file_get_file_references($managed_file, NULL, FIELD_LOAD_CURRENT, 'image');
  112. if ($references) {
  113. // Load referring entities.
  114. foreach ($references as $field_name => $field_references) {
  115. foreach ($field_references as $entity_type => $entity_stubs) {
  116. $image_context['referring_entities'][$field_name][$entity_type] = entity_load($entity_type, array_keys($entity_stubs));
  117. }
  118. }
  119. // Make it easy to access the '1st' entity and its referring image field.
  120. reset($image_context['referring_entities']);
  121. list($field_name, $field_references) = each($image_context['referring_entities']);
  122. reset($field_references);
  123. list($entity_type, $entities) = each($field_references);
  124. reset($entities);
  125. list(, $image_context['entity']) = each($entities);
  126. $image_field = field_get_items($entity_type, $image_context['entity'], $field_name);
  127. if ($image_field !== FALSE) {
  128. // Get referring item
  129. foreach ($image_field as $image_field_value) {
  130. if ($image_field_value['fid'] === $managed_file->fid) {
  131. $image_context['image_field'] = $image_field_value;
  132. }
  133. }
  134. }
  135. }
  136. }
  137. // @todo: support for media module, or is that based upon managed files?
  138. return $image_context;
  139. }
  140. /**
  141. * Given two imageapi objects with dimensions, and some positioning values,
  142. * calculate a new x,y for the layer to be placed at.
  143. *
  144. * This is a different approach to imagecache_actions_keyword_filter() - more
  145. * like css.
  146. *
  147. * The $style is an array, and expected to have 'top, bottom, left, right'
  148. * attributes set. These values may be positive, negative, or in %.
  149. *
  150. * % is calculated relative to the base image dimensions.
  151. * Using % requires that the layer is positioned CENTERED on that location, so
  152. * some offsets are added to it. 'right-25%' is not lined up with a margin 25%
  153. * in, it's centered at a point 25% in - which is therefore identical with
  154. * left+75%
  155. *
  156. * @param $base
  157. * @param $layer
  158. * @param $style
  159. *
  160. * @return array
  161. * A keyed array of absolute x,y co-ordinates to place the layer at.
  162. */
  163. function imagecache_actions_calculate_relative_position($base, $layer, $style) {
  164. // both images should now have size info available.
  165. if (isset($style['bottom'])) {
  166. $ypos = imagecache_actions_calculate_offset('bottom', $style['bottom'], $base->info['height'], $layer->info['height']);
  167. }
  168. if (isset($style['top'])) {
  169. $ypos = imagecache_actions_calculate_offset('top', $style['top'], $base->info['height'], $layer->info['height']);
  170. }
  171. if (isset($style['right'])) {
  172. $xpos = imagecache_actions_calculate_offset('right', $style['right'], $base->info['width'], $layer->info['width']);
  173. }
  174. if (isset($style['left'])) {
  175. $xpos = imagecache_actions_calculate_offset('left', $style['left'], $base->info['width'], $layer->info['width']);
  176. }
  177. if (! isset($ypos)) {
  178. // assume center
  179. $ypos = ($base->info['height'] / 2) - ($layer->info['height'] / 2);
  180. }
  181. if (! isset($xpos)) {
  182. // assume center
  183. $xpos = ($base->info['width'] / 2) - ($layer->info['width'] / 2);
  184. }
  185. #dpm(__FUNCTION__ . " Calculated offsets");
  186. #dpm(get_defined_vars());
  187. return array('x' => $xpos, 'y' => $ypos);
  188. }
  189. /**
  190. * Positive numbers are IN from the edge, negative offsets are OUT.
  191. *
  192. * $keyword, $value, $base_size, $layer_size
  193. * eg
  194. * left,20 200, 100 = 20
  195. * right,20 200, 100 = 80 (object 100 wide placed 20 px from the right = x=80)
  196. *
  197. * top,50%, 200, 100 = 50 (Object is centered when using %)
  198. * top,20%, 200, 100 = -10
  199. * bottom,-20, 200, 100 = 220
  200. * right, -25%, 200, 100 = 200 (this ends up just off screen)
  201. *
  202. * Also, the value can be a string, eg "bottom-100", or "center+25%"
  203. */
  204. function imagecache_actions_calculate_offset($keyword, $value, $base_size, $layer_size) {
  205. $offset = 0; // used to account for dimensions of the placed object
  206. $direction = 1;
  207. $base = 0;
  208. if ($keyword == 'right' || $keyword == 'bottom') {
  209. $direction = -1;
  210. $offset = -1 * $layer_size;
  211. $base = $base_size;
  212. }
  213. if ($keyword == 'middle' || $keyword == 'center') {
  214. $base = $base_size / 2;
  215. $offset = -1 * ($layer_size / 2);
  216. }
  217. // Keywords may be used to stand in for numeric values
  218. switch ($value) {
  219. case 'left':
  220. case 'top':
  221. $value = 0;
  222. break;
  223. case 'middle':
  224. case 'center':
  225. $value = $base_size / 2;
  226. break;
  227. case 'bottom':
  228. case 'right':
  229. $value = $base_size;
  230. }
  231. // Handle keyword-number cases like top+50% or bottom-100px,
  232. // @see imagecache_actions_keyword_filter().
  233. if (preg_match('/^(.+)([\+\-])(\d+)([^\d]*)$/', $value, $results)) {
  234. list(, $value_key, $value_mod, $mod_value, $mod_unit) = $results;
  235. if ($mod_unit == '%') {
  236. $mod_value = $mod_value / 100 * $base_size;
  237. }
  238. $mod_direction = ($value_mod == '-') ? -1 : + 1;
  239. switch ($value_key) {
  240. case 'left':
  241. case 'top':
  242. default:
  243. $mod_base = 0;
  244. break;
  245. case 'middle':
  246. case 'center':
  247. $mod_base = $base_size / 2;
  248. break;
  249. case 'bottom':
  250. case 'right':
  251. $mod_base = $base_size;
  252. break;
  253. }
  254. $modified_value = $mod_base + ($mod_direction * $mod_value);
  255. return $modified_value;
  256. }
  257. // handle % values
  258. if (substr($value, strlen($value) -1, 1) == '%') {
  259. $value = intval($value / 100 * $base_size);
  260. $offset = -1 * ($layer_size / 2);
  261. }
  262. $value = $base + ($direction * $value);
  263. // Add any extra offset to position the item
  264. return $value + $offset;
  265. }
  266. /**
  267. * Convert a hex string to its RGBA (Red, Green, Blue, Alpha) integer
  268. * components.
  269. *
  270. * Stolen from imageapi D6 2011-01
  271. *
  272. * @param string $hex
  273. * A string specifing an RGB color in the formats:
  274. * '#ABC','ABC','#ABCD','ABCD','#AABBCC','AABBCC','#AABBCCDD','AABBCCDD'
  275. *
  276. * @return array
  277. * An array with four elements for red, green, blue, and alpha.
  278. */
  279. function imagecache_actions_hex2rgba($hex) {
  280. $hex = ltrim($hex, '#');
  281. if (preg_match('/^[0-9a-f]{3}$/i', $hex)) {
  282. // 'FA3' is the same as 'FFAA33' so r=FF, g=AA, b=33
  283. $r = str_repeat($hex{0}, 2);
  284. $g = str_repeat($hex{1}, 2);
  285. $b = str_repeat($hex{2}, 2);
  286. $a = '0';
  287. }
  288. elseif (preg_match('/^[0-9a-f]{6}$/i', $hex)) {
  289. // #FFAA33 or r=FF, g=AA, b=33
  290. list($r, $g, $b) = str_split($hex, 2);
  291. $a = '0';
  292. }
  293. elseif (preg_match('/^[0-9a-f]{8}$/i', $hex)) {
  294. // #FFAA33 or r=FF, g=AA, b=33
  295. list($r, $g, $b, $a) = str_split($hex, 2);
  296. }
  297. elseif (preg_match('/^[0-9a-f]{4}$/i', $hex)) {
  298. // 'FA37' is the same as 'FFAA3377' so r=FF, g=AA, b=33, a=77
  299. $r = str_repeat($hex{0}, 2);
  300. $g = str_repeat($hex{1}, 2);
  301. $b = str_repeat($hex{2}, 2);
  302. $a = str_repeat($hex{3}, 2);
  303. }
  304. else {
  305. // error: invalid hex string, @todo: set form error.
  306. return FALSE;
  307. }
  308. $r = hexdec($r);
  309. $g = hexdec($g);
  310. $b = hexdec($b);
  311. $a = hexdec($a);
  312. // alpha over 127 is illegal. assume they meant half that.
  313. if ($a > 127) {
  314. $a = (int) $a/2;
  315. }
  316. return array('red' => $r, 'green' => $g, 'blue' => $b, 'alpha' => $a);
  317. }
  318. /**
  319. * Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
  320. * Called on either the x or y values.
  321. *
  322. * May be something like "20", "center", "left+20", "bottom+10". + values are
  323. * in from the sides, so bottom+10 is 10 UP from the bottom.
  324. *
  325. * "center+50" is also OK.
  326. *
  327. * "30%" will place the CENTER of the object at 30% across. to get a 30% margin,
  328. * use "left+30%"
  329. *
  330. * @param string|int $value
  331. * string or int value.
  332. * @param int $base_size
  333. * Size in pixels of the range this item is to be placed in.
  334. * @param int $layer_size
  335. * Size in pixels of the object to be placed.
  336. *
  337. * @return int
  338. */
  339. function imagecache_actions_keyword_filter($value, $base_size, $layer_size) {
  340. // See above for the patterns this matches
  341. if (! preg_match('/([a-z]*)([\+\-]?)(\d*)([^\d]*)/', $value, $results) ) {
  342. trigger_error("imagecache_actions had difficulty parsing the string '$value' when calculating position. Please check the syntax.", E_USER_WARNING);
  343. }
  344. list(, $keyword, $plusminus, $value, $unit) = $results;
  345. return imagecache_actions_calculate_offset($keyword, $plusminus . $value . $unit, $base_size, $layer_size);
  346. }
  347. /**
  348. * @deprecated
  349. *
  350. * Given a file path relative the default file scheme, tries to locate it and,
  351. * is successful, finds all fields that use it.
  352. *
  353. * @param string $filepath
  354. * Actually a Drupal file URI, probably.
  355. * @param bool $load_entity
  356. * Whether to return whole entities or just the identifying fields of them.
  357. *
  358. * @return array
  359. * A nested array of basic entity data, grouped by entity type and field name.
  360. * Empty array if no file fields are referring the given path.
  361. */
  362. function imagecache_actions_fields_from_filepath($filepath, $load_entity = TRUE) {
  363. if (!module_exists('file')) {
  364. return array();
  365. }
  366. // Unsure if we are given a real filepath (the normal assumption) or a
  367. // Drupal scheme URI. Resolve either.
  368. if (!file_valid_uri($filepath)) {
  369. $filepath = file_build_uri($filepath);
  370. }
  371. $files = file_load_multiple(array(), array('uri' => $filepath));
  372. if ($files) {
  373. $fields = array();
  374. foreach ($files as $fid => $file) {
  375. $references = file_get_file_references($file, NULL, FIELD_LOAD_CURRENT, 'image');
  376. $fields[$fid] = $references;
  377. }
  378. if ($load_entity) {
  379. // These references are pretty low on information. Load the actual nodes/entities too.
  380. imagecache_actions_entities_from_references($fields);
  381. }
  382. return $fields;
  383. }
  384. return array();
  385. }
  386. /**
  387. * @deprecated
  388. *
  389. * Load additional data - Fully load the entities that actually use the given file, with all their data.
  390. *
  391. * Replaces the full entity object in place of the placeholder object that file_get_file_references() gives us.
  392. */
  393. function imagecache_actions_entities_from_references(&$fields) {
  394. foreach ($fields as $fid => $field_ids) {
  395. foreach ($field_ids as $field_id => $entity_types) {
  396. foreach ($entity_types as $entity_type => $entity_instances) {
  397. #$entities = entity_load($entity_type, array_keys($entity_instances));
  398. foreach ($entity_instances as $entity_id => $entity) {
  399. $entities = entity_load($entity_type, array($entity_id));
  400. $entity = reset($entities);
  401. // Add this extra data to the return info, replacing the lightweight version
  402. $fields[$fid][$field_id][$entity_type][$entity_id] = $entity;
  403. } // All actual entities
  404. } // All types of entity that ref the file
  405. } // All references to this file
  406. } // All entries for this file found in the db (expected to be only 1)
  407. }
  408. /**
  409. * @param string $value
  410. * @param int|null $current_pixels
  411. *
  412. * @return int|null
  413. */
  414. function imagecache_actions_percent_filter($value, $current_pixels) {
  415. if (strpos($value, '%') !== FALSE) {
  416. $value = $current_pixels !== NULL ? str_replace('%', '', $value) * 0.01 * $current_pixels : NULL;
  417. }
  418. return $value;
  419. }