imagecache_autorotate.module 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <?php
  2. /**
  3. * @file Autorotate image based on EXIF Orientation tag.
  4. *
  5. * EXIF: https://en.wikipedia.org/wiki/Exchangeable_image_file_format
  6. * EXIF orientation tag: http://sylvana.net/jpegcrop/exif_orientation.html
  7. *
  8. * Originally contributed by jonathan_hunt https://drupal.org/user/28976,
  9. * September 1, 2009
  10. */
  11. /**
  12. * Implements hook_image_effect_info().
  13. *
  14. * Defines information about the supported effects.
  15. */
  16. function imagecache_autorotate_image_effect_info() {
  17. $effects = array();
  18. $effects['imagecache_autorotate'] = array(
  19. 'label' => t('Autorotate'),
  20. 'help' => t('Autorotate image based on EXIF orientation and reset that tag.'),
  21. 'effect callback' => 'imagecache_autorotate_effect',
  22. 'dimensions callback' => 'imagecache_autorotate_dimensions',
  23. 'form callback' => 'imagecache_autorotate_form',
  24. 'summary theme' => 'imagecache_autorotate_summary',
  25. );
  26. return $effects;
  27. }
  28. /**
  29. * Implements hook_theme().
  30. *
  31. * Registers theme functions for the effect summaries.
  32. */
  33. function imagecache_autorotate_theme() {
  34. return array(
  35. 'imagecache_autorotate_summary' => array(
  36. 'variables' => array('data' => NULL),
  37. ),
  38. );
  39. }
  40. /**
  41. * Builds the auto-rotate form.
  42. *
  43. * This effect has no options, only some help text, so the form is displayed
  44. * anyway.
  45. */
  46. function imagecache_autorotate_form() {
  47. $form = array();
  48. $form['help'] = array(
  49. '#markup' => "<p><strong>There are no user-configurable options for this process.</strong></p>
  50. <p>Certain cameras can embed <em>orientation</em> information into image
  51. files when they save them. This information is embedded in an EXIF tag
  52. and can be used to rotate images to their correct position for display.
  53. <em>Not all cameras or images contain this information.</em>
  54. This process is only useful for images that contain this information,
  55. whereas for other images it is harmless.
  56. </p>
  57. <p>Although most modern browsers do support the orientation tag, the
  58. information may get lost or become incorrect by other operations.
  59. So, to support all browsers and prevent rotation errors, it is better to
  60. start each image style with this effect.
  61. </p>
  62. <p>The expected/supported values are:<br/>
  63. <strong>Tag</strong>: <code>0x0112 Orientation</code>
  64. </p>
  65. <ul>
  66. <li>1 = Horizontal (normal)</li>
  67. <li>3 = Rotate 180</li>
  68. <li>6 = Rotate 90 CW</li>
  69. <li>8 = Rotate 270 CW</li>
  70. </ul>
  71. <p>Wikipedia: <a href='https://en.wikipedia.org/wiki/Exchangeable_image_file_format'>Exchangeable image file format</a></p>
  72. ",
  73. );
  74. return $form;
  75. }
  76. /**
  77. * Implements theme_hook() for the autorotate effect summary.
  78. *
  79. * param array $variables
  80. * An associative array containing:
  81. * - data: The current configuration for this image effect.
  82. *
  83. * @return string
  84. * The HTML for the summary of this image effect.
  85. * @ingroup themeable
  86. */
  87. function theme_imagecache_autorotate_summary(/*array $variables*/) {
  88. return 'image based on its EXIF data.';
  89. }
  90. /**
  91. * Autorotate image based on EXIF Orientation tag.
  92. */
  93. function imagecache_autorotate_effect(stdClass $image /*, $data*/) {
  94. // Test to see if EXIF is supported by the current image type.
  95. if (in_array($image->info['mime_type'], array('image/jpeg', 'image/tiff'))) {
  96. // Hand over to toolkit.
  97. return image_toolkit_invoke('imagecache_autorotate', $image);
  98. }
  99. else if ($image->source === 'modules/image/sample.png' && user_access('administer image styles')) {
  100. if (!extension_loaded('exif')) {
  101. // Issue a warning if we are in the admin screen and the exif extension is
  102. // not enabled.
  103. drupal_set_message(t('The autorotate image effect requires the exif extension to be enabled.'), 'warning');
  104. if ($image->toolkit === 'imagemagick') {
  105. drupal_set_message(t('Though imagemagick will work without the exif extension, subsequent effects may fail as the image dimensions cannot be updated.'), 'warning');
  106. }
  107. }
  108. }
  109. return TRUE;
  110. }
  111. /**
  112. * GD toolkit specific implementation of this image effect.
  113. *
  114. * @param stdClass $image
  115. *
  116. * @return bool
  117. * true on success, false otherwise.
  118. */
  119. function image_gd_imagecache_autorotate(stdClass $image) {
  120. if (!function_exists('exif_read_data')) {
  121. watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function is not available in this PHP installation. You probably have to enable the exif extension.')));
  122. return FALSE;
  123. }
  124. // Read and check result.
  125. $exif = @exif_read_data(drupal_realpath($image->source));
  126. if ($exif === FALSE && $image->extension === 'jpg') {
  127. watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function returned FALSE.')));
  128. return FALSE;
  129. }
  130. if (isset($exif['Orientation'])) {
  131. // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html:
  132. // 1 = Horizontal (normal)
  133. // 2 = Mirror horizontal
  134. // 3 = Rotate 180
  135. // 4 = Mirror vertical
  136. // 5 = Mirror horizontal and rotate 270 CW
  137. // 6 = Rotate 90 CW
  138. // 7 = Mirror horizontal and rotate 90 CW
  139. // 8 = Rotate 270 CW
  140. // @todo: Add horizontal and vertical flips etc.
  141. // imagecopy seems to be able to mirror, see conmments on
  142. // http://php.net/manual/en/function.imagecopy.php
  143. // @todo: Create sample set for tests.
  144. switch ($exif['Orientation']) {
  145. case 3:
  146. $degrees = 180;
  147. break;
  148. case 6:
  149. $degrees = 90;
  150. break;
  151. case 8:
  152. $degrees = 270;
  153. break;
  154. default:
  155. $degrees = 0;
  156. }
  157. if ($degrees != 0) {
  158. return image_rotate($image, $degrees);
  159. }
  160. }
  161. return TRUE;
  162. }
  163. /**
  164. * Imagemagick toolkit specific implementation of this image effect.
  165. *
  166. * @param stdClass $image
  167. * An image object.
  168. *
  169. * @return bool
  170. * true on success, false otherwise.
  171. *
  172. * @see http://www.imagemagick.org/script/command-line-options.php#auto-orient
  173. */
  174. function image_imagemagick_imagecache_autorotate(stdClass $image) {
  175. // Use the exif extension, if enabled, to figure out the new dimensions.
  176. // Moreover (see [#2366163]): to prevent a bug in IM to incorrectly rotate the
  177. // image when it should not, we only pass the auto-orient argument when the
  178. // exif extension could detect the 'Orientation' tag.
  179. if (function_exists('exif_read_data')) {
  180. $exif = @exif_read_data(drupal_realpath($image->source));
  181. if (isset($exif['Orientation'])) {
  182. switch ($exif['Orientation']) {
  183. case 1:
  184. // Normal orientation: no need to rotate or to change the dimensions.
  185. break;
  186. case 5:
  187. case 6:
  188. case 7:
  189. case 8:
  190. // 90 or 270 degrees rotation (+ optional mirror): swap dimensions.
  191. $image->ops[] = '-auto-orient';
  192. $tmp = $image->info['width'];
  193. $image->info['width'] = $image->info['height'];
  194. $image->info['height'] = $tmp;
  195. break;
  196. default:
  197. // All other orientations: pass the arguments, but the dimensions
  198. // remain the same.
  199. $image->ops[] = '-auto-orient';
  200. break;
  201. }
  202. }
  203. elseif ($exif === FALSE && $image->extension === 'jpg') {
  204. watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function returned FALSE.')));
  205. }
  206. }
  207. else {
  208. // We do add the auto-orient argument to IM. IM will determine itself
  209. // whether to rotate or not.
  210. $image->ops[] = '-auto-orient';
  211. // However we cannot keep track of the dimensions anymore.
  212. if ($image->info['width'] !== $image->info['height']) {
  213. $image->info['width'] = $image->info['height'] = NULL;;
  214. }
  215. }
  216. return TRUE;
  217. }
  218. /**
  219. * Image dimensions callback for this image effect.
  220. *
  221. * @param array $dimensions
  222. * An array with the dimensions (in pixels) to be modified.
  223. * param array $data
  224. * An associative array containing the effect data.
  225. */
  226. function imagecache_autorotate_dimensions(array &$dimensions/*, array $data*/) {
  227. // We can only know the resulting dimensions if both dimensions are equal.
  228. // Otherwise we need to inspect the image itself, which is not passed in here.
  229. // (this callback was introduced to enhance performance by NOT accessing the
  230. // image file when rendering the width and height attributes of the html img
  231. // tag).
  232. if ($dimensions['width'] !== $dimensions['height']) {
  233. $dimensions['width'] = $dimensions['height'] = NULL;
  234. }
  235. }