diff --git a/config/schema/imagemagick.schema.yml b/config/schema/imagemagick.schema.yml index a9b0b4b..3bbf0db 100644 --- a/config/schema/imagemagick.schema.yml +++ b/config/schema/imagemagick.schema.yml @@ -1,8 +1,12 @@ # Imagemagick module schema -# The auto orientation effect has no settings. image.effect.imagemagick_auto_orient: - type: sequence + type: mapping + label: 'Automatically correct orientation' + mapping: + scan_exif: + type: boolean + label: 'Scan EXIF data when calculating styled image dimensions' # The strip metadata effect has no settings. image.effect.imagemagick_strip_metadata: diff --git a/src/Plugin/ImageEffect/AutoOrientImageEffect.php b/src/Plugin/ImageEffect/AutoOrientImageEffect.php index 76b4855..8b922f5 100644 --- a/src/Plugin/ImageEffect/AutoOrientImageEffect.php +++ b/src/Plugin/ImageEffect/AutoOrientImageEffect.php @@ -8,7 +8,13 @@ namespace Drupal\imagemagick\Plugin\ImageEffect; use Drupal\Core\Image\ImageInterface; -use Drupal\image\ImageEffectBase; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\image\ConfigurableImageEffectBase; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; /** * Automatically adjusts the orientation of an image resource. @@ -19,7 +25,87 @@ use Drupal\image\ImageEffectBase; * description = @Translation("Automatically rotates images according to orientation flag set by many phones and digital cameras.") * ) */ -class AutoOrientImageEffect extends ImageEffectBase { +class AutoOrientImageEffect extends ConfigurableImageEffectBase implements ContainerFactoryPluginInterface { + + /** + * The MIME type guessing service. + * + * @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface + */ + protected $mimeTypeGuesser; + + /** + * The file system service. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + + /** + * Constructs an AutoOrientImageEffect object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Psr\Log\LoggerInterface $logger + * A logger instance. + * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mime_type_guesser + * The MIME type guessing service. + * @param \Drupal\Core\File\FileSystemInterface $file_system + * The file system service. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, MimeTypeGuesserInterface $mime_type_guesser, FileSystemInterface $file_system) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $logger); + $this->mimeTypeGuesser = $mime_type_guesser; + $this->fileSystem = $file_system; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('logger.factory')->get('image'), + $container->get('file.mime_type.guesser'), + $container->get('file_system') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'scan_exif' => TRUE, + ]; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form['scan_exif'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Scan image file'), + '#description' => $this->t('When selected, original image files supporting EXIF data (like JPEG of TIFF) will be scanned to determine styled image orientation and dimensions. This slightly impacts performance, but allows to render more accurate HTML img tags.'), + '#default_value' => $this->configuration['scan_exif'], + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + $this->configuration['scan_exif'] = $form_state->getValue('scan_exif'); + } /** * {@inheritdoc} @@ -31,8 +117,30 @@ class AutoOrientImageEffect extends ImageEffectBase { /** * {@inheritdoc} */ - public function transformDimensions(array &$dimensions) { - $dimensions['width'] = $dimensions['height'] = NULL; + public function transformDimensions(array &$dimensions, $uri) { + if ($dimensions['width'] && $dimensions['height'] && $this->configuration['scan_exif']) { + // Both dimensions in input, and effect is configured to check the + // the input file. Check if the file MIME type is one that can have + // EXIF data, read EXIF data, and determine image orientation. + $mime_type = $this->mimeTypeGuesser->guess($uri); + if (in_array($mime_type, ['image/jpeg', 'image/tiff'])) { + if ($file_path = $this->fileSystem->realpath($uri)) { + if ($exif_data = @exif_read_data($file_path)) { + $orientation = isset($exif_data['Orientation']) ? $exif_data['Orientation'] : NULL; + if (in_array($orientation, [5, 6, 7, 8])) { + $tmp = $dimensions['width']; + $dimensions['width'] = $dimensions['height']; + $dimensions['height'] = $tmp; + } + } + } + } + } + else { + // Either no full dimensions in input, or effect is configured to + // skip checking the input file. Set both dimensions to NULL. + $dimensions['width'] = $dimensions['height'] = NULL; + } } } diff --git a/src/Plugin/ImageToolkit/ImagemagickToolkit.php b/src/Plugin/ImageToolkit/ImagemagickToolkit.php index 178172f..258cffd 100644 --- a/src/Plugin/ImageToolkit/ImagemagickToolkit.php +++ b/src/Plugin/ImageToolkit/ImagemagickToolkit.php @@ -7,13 +7,14 @@ namespace Drupal\imagemagick\Plugin\ImageToolkit; +use Drupal\Component\Utility\Image as ImageUtility; +use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\ImageToolkit\ImageToolkitBase; use Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface; -use Drupal\Component\Utility\Image as ImageUtility; -use Drupal\Component\Utility\Unicode; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; @@ -586,8 +587,6 @@ class ImagemagickToolkit extends ImageToolkitBase { * An escaped string for use in the ::imagemagickExec method. */ public function escapeShellArg($arg) { - // return '\'' . str_replace('\'', '\'\\\'\'', $arg) . '\''; - // Solution proposed in #1502924-8. $old_locale = setlocale(LC_CTYPE, 0); setlocale(LC_CTYPE, 'en_US.UTF-8'); @@ -894,7 +893,7 @@ class ImagemagickToolkit extends ImageToolkitBase { $output = preg_replace('/\n/', '
', $status['output']); $requirements['imagemagick_version'] = array( 'title' => t('ImageMagick'), - 'description' => $output, + 'description' => SafeMarkup::format($output), 'severity' => REQUIREMENT_INFO, ); }