diff --git a/core/includes/image.inc b/core/includes/image.inc index 9e2d06f..447500e 100644 --- a/core/includes/image.inc +++ b/core/includes/image.inc @@ -5,8 +5,7 @@ * API for manipulating images. */ -use Drupal\system\Plugin\ImageToolkitInterface; -use Drupal\Component\Image\Image; +use Drupal\Core\Image\ImageFile; /** * @defgroup image Image toolkits @@ -44,8 +43,6 @@ * * @param string $filepath * String specifying the path of the image file. - * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit - * (optional) An image toolkit object to override the default. * * @return array * FALSE, if the file could not be found or is not an image. Otherwise, a @@ -55,276 +52,13 @@ * - "extension": Commonly used file extension for the image. * - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png'). * - "file_size": File size in bytes. - */ -function image_get_info($filepath, ImageToolkitInterface $toolkit = NULL) { - $details = FALSE; - if (!is_file($filepath) && !is_uploaded_file($filepath)) { - return $details; - } - - if ($toolkit === NULL) { - $toolkit = Drupal::service('image.toolkit'); - } - if ($toolkit) { - $image = new stdClass(); - $image->source = $filepath; - $image->toolkit = $toolkit; - $details = $toolkit->getInfo($image); - if (isset($details) && is_array($details)) { - $details['file_size'] = filesize($filepath); - } - } - - return $details; -} - -/** - * Scales an image to the exact width and height given. - * - * This function achieves the target aspect ratio by cropping the original image - * equally on both sides, or equally on the top and bottom. This function is - * useful to create uniform sized avatars from larger images. - * - * The resulting image always has the exact target dimensions. - * - * @param object $image - * An image object returned by image_load(). - * @param int $width - * The target width, in pixels. - * @param int $height - * The target height, in pixels. - * - * @return bool - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_resize() - * @see image_crop() - */ -function image_scale_and_crop($image, $width, $height) { - $scale = max($width / $image->info['width'], $height / $image->info['height']); - $x = ($image->info['width'] * $scale - $width) / 2; - $y = ($image->info['height'] * $scale - $height) / 2; - - if (image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) { - return image_crop($image, $x, $y, $width, $height); - } - return FALSE; -} - -/** - * Scales image dimensions while maintaining aspect ratio. * * @deprecated as of Drupal 8.0. Use - * \Drupal\Component\Image\Image::scaleDimensions() directly instead. - * - * @see image_scale() + * \Drupal\Core\Image\ImageFile::getFileInfo(). */ -function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) { - return Image::scaleDimensions($dimensions, $width, $height, $upscale); -} - -/** - * Scales an image while maintaining aspect ratio. - * - * The resulting image can be smaller for one or both target dimensions. - * - * @param object $image - * An image object returned by image_load(). - * @param int $width - * (optional) The target width, in pixels. This value is omitted then the - * scaling will based only on the height value. - * @param int $height - * (optional) The target height, in pixels. This value is omitted then the - * scaling will based only on the width value. - * @param bool $upscale - * (optional) Boolean indicating that files smaller than the dimensions will - * be scaled up. This generally results in a low quality image. - * - * @return bool - * TRUE on success, FALSE on failure. - * - * @see image_dimensions_scale() - * @see image_load() - * @see image_scale_and_crop() - */ -function image_scale($image, $width = NULL, $height = NULL, $upscale = FALSE) { - $dimensions = $image->info; - - // Scale the dimensions - if they don't change then just return success. - if (!image_dimensions_scale($dimensions, $width, $height, $upscale)) { - return TRUE; - } - - return image_resize($image, $dimensions['width'], $dimensions['height']); -} - -/** - * Resizes an image to the given dimensions (ignoring aspect ratio). - * - * @param object $image - * An image object returned by image_load(). - * @param int $width - * The target width, in pixels. - * @param int $height - * The target height, in pixels. - * - * @return bool - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see \Drupal\system\Plugin\ImageToolkitInterface::resize() - */ -function image_resize($image, $width, $height) { - $width = (int) round($width); - $height = (int) round($height); - - return $image->toolkit->resize($image, $width, $height); -} - -/** - * Rotates an image by the given number of degrees. - * - * @param $image - * An image object returned by image_load(). - * @param int $degrees - * The number of (clockwise) degrees to rotate the image. - * @param string $background - * (optional) An hexadecimal integer specifying the background color to use - * for the uncovered area of the image after the rotation. E.g. 0x000000 for - * black, 0xff00ff for magenta, and 0xffffff for white. For images that - * support transparency, this will default to transparent. Otherwise it will - * be white. - * - * @return bool - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see \Drupal\system\Plugin\ImageToolkitInterface::rotate() - */ -function image_rotate($image, $degrees, $background = NULL) { - return $image->toolkit->rotate($image, $degrees, $background); -} - -/** - * Crops an image to a rectangle specified by the given dimensions. - * - * @param $image - * An image object returned by image_load(). - * @param int $x - * The top left coordinate, in pixels, of the crop area (x axis value). - * @param int $y - * The top left coordinate, in pixels, of the crop area (y axis value). - * @param int $width - * The target width, in pixels. - * @param int $height - * The target height, in pixels. - * - * @return bool - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_scale_and_crop() - * @see \Drupal\system\Plugin\ImageToolkitInterface::crop() - */ -function image_crop($image, $x, $y, $width, $height) { - $aspect = $image->info['height'] / $image->info['width']; - if (empty($height)) $height = $width / $aspect; - if (empty($width)) $width = $height * $aspect; - - $width = (int) round($width); - $height = (int) round($height); - - return $image->toolkit->crop($image, $x, $y, $width, $height); -} - -/** - * Converts an image to grayscale. - * - * @param $image - * An image object returned by image_load(). - * - * @return bool - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see \Drupal\system\Plugin\ImageToolkitInterface::desaturate() - */ -function image_desaturate($image) { - return $image->toolkit->desaturate($image); -} - -/** - * Loads an image file and returns an image object. - * - * Any changes to the file are not saved until image_save() is called. - * - * @param string $file - * Path to an image file. - * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit - * (optional) Image toolkit object to override the default. - * - * @return object - * An image object or FALSE if there was a problem loading the file. The - * image object has the following properties: - * - 'source' - The original file path. - * - 'info' - The array of information returned by image_get_info() - * - 'toolkit' - The name of the image toolkit requested when the image was - * loaded. - * Image toolkits may add additional properties. The caller is advised not to - * monkey about with them. - * - * @see image_save() - * @see image_get_info() - */ -function image_load($file, ImageToolkitInterface $toolkit = NULL) { - if ($toolkit === NULL) { - $toolkit = Drupal::service('image.toolkit'); - } - if ($toolkit) { - $image = new stdClass(); - $image->source = $file; - $image->info = image_get_info($file, $toolkit); - if (isset($image->info) && is_array($image->info)) { - $image->toolkit = $toolkit; - if ($toolkit->load($image)) { - return $image; - } - } - } - return FALSE; -} - -/** - * Closes the image and saves the changes to a file. - * - * @param object $image - * An image object returned by image_load(). The object's 'info' property - * will be updated if the file is saved successfully. - * @param $destination - * (optional) Destination path where the image should be saved. If it is empty - * the original image file will be overwritten. - * - * @return bool - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see \Drupal\system\Plugin\ImageToolkitInterface::save() - */ -function image_save($image, $destination = NULL) { - if (empty($destination)) { - $destination = $image->source; - } - if ($return = $image->toolkit->save($image, $destination)) { - // Clear the cached file size and refresh the image information. - clearstatcache(TRUE, $destination); - $image->info = image_get_info($destination, $image->toolkit); - - if (drupal_chmod($destination)) { - return $return; - } - } - return FALSE; +function image_get_info($filepath) { + $image = new ImageFile($filepath); + return $image->getFileInfo(); } /** diff --git a/core/lib/Drupal/Core/Image/ImageFile.php b/core/lib/Drupal/Core/Image/ImageFile.php new file mode 100644 index 0000000..3e914f5 --- /dev/null +++ b/core/lib/Drupal/Core/Image/ImageFile.php @@ -0,0 +1,439 @@ +source = $source; + } + + /** + * Sets a value for a given key. + * + * @param string $key + * The key to set the value for. + * @param mixed $value + * The value to set for this key. + * + * @return self + * Returns this image file. + */ + public function set($key, $value) { + $this->info[$key] = $value; + return $this; + } + + /** + * Replaces the file info. + * + * @param array $info + * Information about this file. + * + * @return self + * Returns this image file. + */ + public function setInfo(array $info) { + $this->info = $info; + return $this; + } + + /** + * Retrieves specific information about this image file. + * + * @param string $key + * The key of the info to retrieve, e.g., 'height', 'width', 'extension', + * 'mime_type', 'file_size'. + * + * @return mixed|null + * The value requested, or NULL. + */ + public function get($key) { + $info = $this->getInfo(); + if (isset($info[$key])) { + return $info[$key]; + } + return NULL; + } + + /** + * Retrives all information about this image file. + * + * @return array + * An associative array of image file information. + */ + public function getInfo() { + if (!$this->info) { + $this->processInfo(); + } + return $this->info; + } + + /** + * Sets the image file resource. + * + * @param resource $resource + * The image file handle. + * + * @return self + * Returns this image file. + */ + public function setResource($resource) { + $this->resource = $resource; + return $this; + } + + /** + * Determines if this image file has a resource set. + * + * @return bool + * TRUE if this image file has a resource set, FALSE otherwise. + */ + public function hasResource() { + return (bool) $this->resource; + } + + /** + * Retrieves the image file resource. + * + * @return resource + * The image file handle. + */ + public function getResource() { + if (!$this->hasResource()) { + $this->processInfo(); + $this->getToolkit()->load($this); + } + return $this->resource; + } + + /** + * Sets the source path of the image file. + * + * @param string $source + * A string specifying the path of the image file. + * + * @return self + * Returns this image file. + */ + public function setSource($source) { + $this->source = $source; + return $this; + } + + /** + * Retrieves the source path of the image file. + * + * @return string + * The source path of the image file. + */ + public function getSource() { + return $this->source; + } + + /** + * Sets a custom image toolkit. + * + * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit + * The image toolkit to use for this image file. + * + * @return self + * Returns this image file. + */ + public function setToolkit(ImageToolkitInterface $toolkit) { + $this->toolkit = $toolkit; + return $this; + } + + /** + * Returns the image toolkit being used for this image file. + * + * If a custom toolkit was not specified, this will fallback to the default. + * + * @return \Drupal\system\Plugin\ImageToolkitInterface + * The image toolkit used for this image file. + */ + protected function getToolkit() { + if (!$this->toolkit) { + $this->toolkit = \Drupal::service('image.toolkit'); + } + return $this->toolkit; + } + + /** + * Returns the ID of the image toolkit used for this image file. + * + * @return string + * The ID of the image toolkit. + */ + public function getToolkitId() { + return $this->getToolkit()->getPluginId(); + } + + /** + * Gets details about an image. + * + * Drupal supports GIF, JPG and PNG file formats when used with the GD + * toolkit, and may support others, depending on which toolkits are + * installed. + * + * @return array|bool + * FALSE, if the file could not be found or is not an image. Otherwise, a + * keyed array containing information about the image: + * - "width": Width, in pixels. + * - "height": Height, in pixels. + * - "extension": Commonly used file extension for the image. + * - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png'). + * - "file_size": File size in bytes. + */ + public function getFileInfo() { + if ($this->processInfo()) { + return $this->getInfo(); + } + return FALSE; + } + + /** + * Closes the image and saves the changes to a file. + * + * @param string|null $destination + * (optional) Destination path where the image should be saved. If it is empty + * the original image file will be overwritten. + * + * @return bool + * TRUE on success, FALSE on failure. + * + * @see \Drupal\system\Plugin\ImageToolkitInterface::save() + */ + public function save($destination = NULL) { + if (empty($destination)) { + $destination = $this->getSource(); + } + if ($return = $this->getToolkit()->save($this, $destination)) { + // Clear the cached file size and refresh the image information. + clearstatcache(TRUE, $destination); + $this->setSource($destination); + $this->processInfo(); + + if (drupal_chmod($destination)) { + return $return; + } + } + return FALSE; + } + + /** + * Prepares the image information. + * + * @return bool + * FALSE, if the file could not be found or is not an image. Otherwise, the + * image information is populated. + */ + protected function processInfo() { + $destination = $this->getSource(); + if (!is_file($destination) && !is_uploaded_file($destination)) { + return FALSE; + } + + if ($details = $this->getToolkit()->getInfo($this)) { + $details['file_size'] = filesize($destination); + $this->setInfo($details); + } + return TRUE; + } + + /** + * Scales an image while maintaining aspect ratio. + * + * The resulting image can be smaller for one or both target dimensions. + * + * @param int $width + * (optional) The target width, in pixels. This value is omitted then the + * scaling will based only on the height value. + * @param int $height + * (optional) The target height, in pixels. This value is omitted then the + * scaling will based only on the width value. + * @param bool $upscale + * (optional) Boolean indicating that files smaller than the dimensions will + * be scaled up. This generally results in a low quality image. + * + * @return bool + * TRUE on success, FALSE on failure. + */ + public function scale($width = NULL, $height = NULL, $upscale = FALSE) { + $dimensions = $this->getInfo(); + + // Scale the dimensions - if they don't change then just return success. + if (!Image::scaleDimensions($dimensions, $width, $height, $upscale)) { + return TRUE; + } + + return $this->resize($dimensions['width'], $dimensions['height']); + + } + + /** + * Scales an image to the exact width and height given. + * + * This function achieves the target aspect ratio by cropping the original image + * equally on both sides, or equally on the top and bottom. This function is + * useful to create uniform sized avatars from larger images. + * + * The resulting image always has the exact target dimensions. + * + * @param int $width + * The target width, in pixels. + * @param int $height + * The target height, in pixels. + * + * @return bool + * TRUE on success, FALSE on failure. + */ + public function scaleAndCrop($width, $height) { + $scale = max($width / $this->get('width'), $height / $this->get('height')); + $x = ($this->get('width') * $scale - $width) / 2; + $y = ($this->get('height') * $scale - $height) / 2; + + if ($this->resize($this->get('width') * $scale, $this->get('height') * $scale)) { + return $this->crop($x, $y, $width, $height); + } + return FALSE; + } + + /** + * Crops an image to a rectangle specified by the given dimensions. + * + * @param int $x + * The top left coordinate, in pixels, of the crop area (x axis value). + * @param int $y + * The top left coordinate, in pixels, of the crop area (y axis value). + * @param int $width + * The target width, in pixels. + * @param int $height + * The target height, in pixels. + * + * @return bool + * TRUE on success, FALSE on failure. + * + * @see \Drupal\system\Plugin\ImageToolkitInterface::crop() + */ + function crop($x, $y, $width, $height) { + $aspect = $this->get('height') / $this->get('width'); + if (empty($height)) $height = $width / $aspect; + if (empty($width)) $width = $height * $aspect; + + $width = (int) round($width); + $height = (int) round($height); + + return $this->getToolkit()->crop($this, $x, $y, $width, $height); + } + + /** + * Resizes an image to the given dimensions (ignoring aspect ratio). + * + * @param int $width + * The target width, in pixels. + * @param int $height + * The target height, in pixels. + * + * @return bool + * TRUE on success, FALSE on failure. + * + * @see \Drupal\system\Plugin\ImageToolkitInterface::resize() + */ + public function resize($width, $height) { + $width = (int) round($width); + $height = (int) round($height); + + return $this->getToolkit()->resize($this, $width, $height); + } + + /** + * Converts an image to grayscale. + * + * @return bool + * TRUE on success, FALSE on failure. + * + * @see \Drupal\system\Plugin\ImageToolkitInterface::desaturate() + */ + public function desaturate() { + return $this->getToolkit()->desaturate($this); + } + + /** + * Rotates an image by the given number of degrees. + * + * @param int $degrees + * The number of (clockwise) degrees to rotate the image. + * @param string $background + * (optional) An hexadecimal integer specifying the background color to use + * for the uncovered area of the image after the rotation. E.g. 0x000000 for + * black, 0xff00ff for magenta, and 0xffffff for white. For images that + * support transparency, this will default to transparent. Otherwise it will + * be white. + * + * @return bool + * TRUE on success, FALSE on failure. + * + * @see \Drupal\system\Plugin\ImageToolkitInterface::rotate() + */ + public function rotate($degrees, $background = NULL) { + return $this->getToolkit()->rotate($this, $degrees, $background); + } + +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php index d3bceb8..f29320f 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php @@ -104,10 +104,10 @@ function testSchemaMapping() { $expected['mapping']['label']['type'] = 'label'; $expected['mapping']['effects']['type'] = 'sequence'; $expected['mapping']['effects']['sequence'][0]['type'] = 'mapping'; - $expected['mapping']['effects']['sequence'][0]['mapping']['name']['type'] = 'string'; - $expected['mapping']['effects']['sequence'][0]['mapping']['data']['type'] = 'image.effect.[%parent.name]'; + $expected['mapping']['effects']['sequence'][0]['mapping']['id']['type'] = 'string'; + $expected['mapping']['effects']['sequence'][0]['mapping']['data']['type'] = 'image.effect.[%parent.id]'; $expected['mapping']['effects']['sequence'][0]['mapping']['weight']['type'] = 'integer'; - $expected['mapping']['effects']['sequence'][0]['mapping']['ieid']['type'] = 'string'; + $expected['mapping']['effects']['sequence'][0]['mapping']['uuid']['type'] = 'string'; $expected['mapping']['langcode']['label'] = 'Default language'; $expected['mapping']['langcode']['type'] = 'string'; @@ -189,9 +189,9 @@ function testSchemaData() { // The function is_array() doesn't work with ArrayAccess, so we use count(). $this->assertTrue(count($effects) == 1, 'Got an array with effects for image.style.large data'); - $ieid = key($effects->getValue()); - $effect = $effects[$ieid]; - $this->assertTrue(count($effect['data']) && $effect['name']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.'); + $uuid = key($effects->getValue()); + $effect = $effects[$uuid]; + $this->assertTrue(count($effect['data']) && $effect['id']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.'); $this->assertEqual($effect['data']['width']->getType(), 'integer', 'Got the right type for the scale effect width.'); $this->assertEqual($effect['data']['width']->getValue(), 480, 'Got the right value for the scale effect width.' ); diff --git a/core/modules/file/file.module b/core/modules/file/file.module index bcdb460..50fea80 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -5,6 +5,7 @@ * Defines a "managed_file" Form API field and a "file" field for Field module. */ +use Drupal\Core\Image\ImageFile; use Drupal\file\Plugin\Core\Entity\File; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Template\Attribute; @@ -462,10 +463,11 @@ function file_validate_image_resolution(File $file, $maximum_dimensions = 0, $mi list($width, $height) = explode('x', $maximum_dimensions); if ($info['width'] > $width || $info['height'] > $height) { // Try to resize the image to fit the dimensions. - if ($image = image_load($file->getFileUri())) { - image_scale($image, $width, $height); - image_save($image); - $file->filesize = $image->info['file_size']; + $image = new ImageFile($file->getFileUri()); + if ($image->getResource()) { + $image->scale($width, $height); + $image->save(); + $file->filesize = $image->get('file_size'); drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions))); } else { diff --git a/core/modules/image/config/image.style.large.yml b/core/modules/image/config/image.style.large.yml index 30bb1af..cbd1c0c 100644 --- a/core/modules/image/config/image.style.large.yml +++ b/core/modules/image/config/image.style.large.yml @@ -2,11 +2,11 @@ name: large label: 'Large (480x480)' effects: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d: - name: image_scale + id: image_scale data: width: '480' height: '480' upscale: '1' weight: '0' - ieid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d + uuid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d langcode: en diff --git a/core/modules/image/config/image.style.medium.yml b/core/modules/image/config/image.style.medium.yml index 1047f86..ba38b73 100644 --- a/core/modules/image/config/image.style.medium.yml +++ b/core/modules/image/config/image.style.medium.yml @@ -2,11 +2,11 @@ name: medium label: 'Medium (220x220)' effects: bddf0d06-42f9-4c75-a700-a33cafa25ea0: - name: image_scale + id: image_scale data: width: '220' height: '220' upscale: '1' weight: '0' - ieid: bddf0d06-42f9-4c75-a700-a33cafa25ea0 + uuid: bddf0d06-42f9-4c75-a700-a33cafa25ea0 langcode: en diff --git a/core/modules/image/config/image.style.thumbnail.yml b/core/modules/image/config/image.style.thumbnail.yml index 5834812..0368dd3 100644 --- a/core/modules/image/config/image.style.thumbnail.yml +++ b/core/modules/image/config/image.style.thumbnail.yml @@ -2,11 +2,11 @@ name: thumbnail label: 'Thumbnail (100x100)' effects: 1cfec298-8620-4749-b100-ccb6c4500779: - name: image_scale + id: image_scale data: width: '100' height: '100' upscale: '1' weight: '0' - ieid: 1cfec298-8620-4749-b100-ccb6c4500779 + uuid: 1cfec298-8620-4749-b100-ccb6c4500779 langcode: en diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml index 6916f19..871c0cc 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -26,13 +26,13 @@ image.style.*: sequence: - type: mapping mapping: - name: + id: type: string data: - type: image.effect.[%parent.name] + type: image.effect.[%parent.id] weight: type: integer - ieid: + uuid: type: string langcode: type: string diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc index 02af74e..2b384bb 100644 --- a/core/modules/image/image.admin.inc +++ b/core/modules/image/image.admin.inc @@ -5,6 +5,7 @@ * Administration pages for image settings. */ +use Drupal\image\ImageStyleInterface; use Symfony\Component\HttpFoundation\RedirectResponse; /** @@ -35,7 +36,7 @@ function image_style_list() { * @ingroup forms * @see image_style_form_submit() */ -function image_style_form($form, &$form_state, $style) { +function image_style_form($form, &$form_state, ImageStyleInterface $style) { $title = t('Edit style %name', array('%name' => $style->label())); drupal_set_title($title, PASS_THROUGH); @@ -69,54 +70,55 @@ function image_style_form($form, &$form_state, $style) { $form['effects'] = array( '#theme' => 'image_style_effects', ); - if (!empty($style->effects)) { - foreach ($style->effects as $key => $effect) { - $form['effects'][$key]['#weight'] = isset($form_state['input']['effects']) ? $form_state['input']['effects'][$key]['weight'] : NULL; - $form['effects'][$key]['label'] = array( - '#markup' => check_plain($effect['label']), - ); - $form['effects'][$key]['summary'] = array( - '#markup' => isset($effect['summary theme']) ? theme($effect['summary theme'], array('data' => $effect['data'])) : '', - ); - $form['effects'][$key]['weight'] = array( - '#type' => 'weight', - '#title' => t('Weight for @title', array('@title' => $effect['label'])), - '#title_display' => 'invisible', - '#default_value' => $effect['weight'], - ); + foreach ($style->getEffects()->sort() as $effect) { + $key = $effect->getUuid(); + $form['effects'][$key]['#weight'] = isset($form_state['input']['effects']) ? $form_state['input']['effects'][$key]['weight'] : NULL; + $form['effects'][$key]['label'] = array( + '#markup' => check_plain($effect->label()), + ); + $form['effects'][$key]['summary'] = $effect->getSummary(); + $form['effects'][$key]['weight'] = array( + '#type' => 'weight', + '#title' => t('Weight for @title', array('@title' => $effect->label())), + '#title_display' => 'invisible', + '#default_value' => $effect->getWeight(), + ); - $links = array(); - if (isset($effect['form callback'])) { - $links['edit'] = array( - 'title' => t('edit'), - 'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key, - ); - } - $links['delete'] = array( - 'title' => t('delete'), - 'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete', - ); - $form['effects'][$key]['operations'] = array( - '#type' => 'operations', - '#links' => $links, - ); - $form['effects'][$key]['configure'] = array( - '#type' => 'link', - '#title' => t('edit'), - '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key, - '#access' => isset($effect['form callback']), - ); - $form['effects'][$key]['remove'] = array( - '#type' => 'link', - '#title' => t('delete'), - '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete', + $links = array(); + if ($effect->hasForm()) { + $links['edit'] = array( + 'title' => t('edit'), + 'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key, ); } + $links['delete'] = array( + 'title' => t('delete'), + 'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete', + ); + $form['effects'][$key]['operations'] = array( + '#type' => 'operations', + '#links' => $links, + ); + $form['effects'][$key]['configure'] = array( + '#type' => 'link', + '#title' => t('edit'), + '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key, + '#access' => $effect->hasForm(), + ); + $form['effects'][$key]['remove'] = array( + '#type' => 'link', + '#title' => t('delete'), + '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete', + ); } // Build the new image effect addition form and add it to the effect list. $new_effect_options = array(); - foreach (image_effect_definitions() as $effect => $definition) { + $effects = Drupal::service('plugin.manager.image.effect')->getDefinitions(); + uasort($effects, function ($a, $b) { + return strcasecmp($a['id'], $b['id']); + }); + foreach ($effects as $effect => $definition) { $new_effect_options[$effect] = $definition['label']; } $form['effects']['new'] = array( @@ -168,21 +170,21 @@ function image_style_form_add_validate($form, &$form_state) { function image_style_form_add_submit($form, &$form_state) { $style = $form_state['image_style']; // Check if this field has any configuration options. - $effect = image_effect_definition_load($form_state['values']['new']); + $effect = Drupal::service('plugin.manager.image.effect')->getDefinition($form_state['values']['new']); // Load the configuration form for this option. - if (isset($effect['form callback'])) { + if (!$effect['no_form']) { $path = 'admin/config/media/image-styles/manage/' . $style->id() . '/add/' . $form_state['values']['new']; $form_state['redirect'] = array($path, array('query' => array('weight' => $form_state['values']['weight']))); } // If there's no form, immediately add the image effect. else { $effect = array( - 'name' => $effect['name'], + 'id' => $effect['id'], 'data' => array(), 'weight' => $form_state['values']['weight'], ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); drupal_set_message(t('The image effect was successfully applied.')); } } @@ -195,15 +197,9 @@ function image_style_form_submit($form, &$form_state) { // Update image effect weights. if (!empty($form_state['values']['effects'])) { - foreach ($form_state['values']['effects'] as $ieid => $effect_data) { - if (isset($style->effects[$ieid])) { - $effect = array( - 'name' => $style->effects[$ieid]['name'], - 'data' => $style->effects[$ieid]['data'], - 'weight' => $effect_data['weight'], - 'ieid' => $ieid, - ); - $style->effects[$ieid] = $effect; + foreach ($form_state['values']['effects'] as $uuid => $effect_data) { + if ($style->getEffects()->has($uuid)) { + $style->getEffect($uuid)->setWeight($effect_data['weight']); } } } @@ -262,246 +258,6 @@ function image_style_add_form_submit($form, &$form_state) { } /** - * Form builder; Form for adding and editing image effects. - * - * This form is used universally for editing all image effects. Each effect adds - * its own custom section to the form by calling the 'form callback' specified - * in hook_image_effect_info(). - * - * @param $form_state - * An associative array containing the current state of the form. - * @param $style - * An image style array. - * @param $effect - * An image effect array. - * - * @ingroup forms - * @see image_resize_form() - * @see image_scale_form() - * @see image_rotate_form() - * @see image_crop_form() - * @see image_effect_form_submit() - */ -function image_effect_form($form, &$form_state, $style, $effect) { - // If there's no configuration for this image effect, return to - // the image style page. - if (!isset($effect['form callback'])) { - return new RedirectResponse(url('admin/config/media/image-styles/manage/' . $style->id(), array('absolute' => TRUE))); - } - $form_state['image_style'] = $style; - $form_state['image_effect'] = $effect; - - if (!empty($effect['ieid'])) { - $title = t('Edit %label effect', array('%label' => $effect['label'])); - } - else{ - $title = t('Add %label effect', array('%label' => $effect['label'])); - } - drupal_set_title($title, PASS_THROUGH); - - $form['#attached']['css'][drupal_get_path('module', 'image') . '/css/image.admin.css'] = array(); - - $form['ieid'] = array( - '#type' => 'value', - '#value' => !empty($effect['ieid']) ? $effect['ieid'] : NULL, - ); - $form['name'] = array( - '#type' => 'value', - '#value' => $effect['name'], - ); - - $form['data'] = call_user_func($effect['form callback'], $effect['data']); - $form['data']['#tree'] = TRUE; - - // Check the URL for a weight, then the image effect, otherwise use default. - $weight = Drupal::request()->query->get('weight'); - $form['weight'] = array( - '#type' => 'hidden', - '#value' => isset($weight) ? intval($weight) : (isset($effect['weight']) ? $effect['weight'] : count($style->effects)), - ); - - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => !empty($effect['ieid']) ? t('Update effect') : t('Add effect'), - ); - $form['actions']['cancel'] = array( - '#type' => 'link', - '#title' => t('Cancel'), - '#href' => 'admin/config/media/image-styles/manage/' . $style->id(), - ); - - return $form; -} - -/** - * Submit handler for updating an image effect. - */ -function image_effect_form_submit($form, &$form_state) { - form_state_values_clean($form_state); - - $effect = $form_state['values']; - $style = $form_state['image_style']; - image_effect_save($style, $effect); - - drupal_set_message(t('The image effect was successfully applied.')); - $form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $style->id(); -} - -/** - * Element validate handler to ensure a hexadecimal color value. - */ -function image_effect_color_validate($element, &$form_state) { - if ($element['#value'] != '') { - $hex_value = preg_replace('/^#/', '', $element['#value']); - if (!preg_match('/^#[0-9A-F]{3}([0-9A-F]{3})?$/', $element['#value'])) { - form_error($element, t('!name must be a hexadecimal color value.', array('!name' => $element['#title']))); - } - } -} - -/** - * Element validate handler to ensure that either a height or a width is - * specified. - */ -function image_effect_scale_validate($element, &$form_state) { - if (empty($element['width']['#value']) && empty($element['height']['#value'])) { - form_error($element, t('Width and height can not both be blank.')); - } -} - -/** - * Form structure for the image resize form. - * - * Note that this is not a complete form, it only contains the portion of the - * form for configuring the resize options. Therefore it does not not need to - * include metadata about the effect, nor a submit button. - * - * @param $data - * The current configuration for this resize effect. - */ -function image_resize_form($data) { - $form['width'] = array( - '#type' => 'number', - '#title' => t('Width'), - '#default_value' => isset($data['width']) ? $data['width'] : '', - '#field_suffix' => ' ' . t('pixels'), - '#required' => TRUE, - '#min' => 1, - ); - $form['height'] = array( - '#type' => 'number', - '#title' => t('Height'), - '#default_value' => isset($data['height']) ? $data['height'] : '', - '#field_suffix' => ' ' . t('pixels'), - '#required' => TRUE, - '#min' => 1, - ); - return $form; -} - -/** - * Form structure for the image scale form. - * - * Note that this is not a complete form, it only contains the portion of the - * form for configuring the scale options. Therefore it does not not need to - * include metadata about the effect, nor a submit button. - * - * @param $data - * The current configuration for this scale effect. - */ -function image_scale_form($data) { - $form = image_resize_form($data); - $form['#element_validate'] = array('image_effect_scale_validate'); - $form['width']['#required'] = FALSE; - $form['height']['#required'] = FALSE; - $form['upscale'] = array( - '#type' => 'checkbox', - '#default_value' => (isset($data['upscale'])) ? $data['upscale'] : 0, - '#title' => t('Allow Upscaling'), - '#description' => t('Let scale make images larger than their original size'), - ); - return $form; -} - -/** - * Form structure for the image crop form. - * - * Note that this is not a complete form, it only contains the portion of the - * form for configuring the crop options. Therefore it does not not need to - * include metadata about the effect, nor a submit button. - * - * @param $data - * The current configuration for this crop effect. - */ -function image_crop_form($data) { - $data += array( - 'width' => '', - 'height' => '', - 'anchor' => 'center-center', - ); - - $form = image_resize_form($data); - $form['anchor'] = array( - '#type' => 'radios', - '#title' => t('Anchor'), - '#options' => array( - 'left-top' => t('Top') . ' ' . t('Left'), - 'center-top' => t('Top') . ' ' . t('Center'), - 'right-top' => t('Top') . ' ' . t('Right'), - 'left-center' => t('Center') . ' ' . t('Left'), - 'center-center' => t('Center'), - 'right-center' => t('Center') . ' ' . t('Right'), - 'left-bottom' => t('Bottom') . ' ' . t('Left'), - 'center-bottom' => t('Bottom') . ' ' . t('Center'), - 'right-bottom' => t('Bottom') . ' ' . t('Right'), - ), - '#theme' => 'image_anchor', - '#default_value' => $data['anchor'], - '#description' => t('The part of the image that will be retained during the crop.'), - ); - - return $form; -} - -/** - * Form structure for the image rotate form. - * - * Note that this is not a complete form, it only contains the portion of the - * form for configuring the rotate options. Therefore it does not not need to - * include metadata about the effect, nor a submit button. - * - * @param $data - * The current configuration for this rotate effect. - */ -function image_rotate_form($data) { - $form['degrees'] = array( - '#type' => 'number', - '#default_value' => (isset($data['degrees'])) ? $data['degrees'] : 0, - '#title' => t('Rotation angle'), - '#description' => t('The number of degrees the image should be rotated. Positive numbers are clockwise, negative are counter-clockwise.'), - '#field_suffix' => '°', - '#required' => TRUE, - ); - $form['bgcolor'] = array( - '#type' => 'textfield', - '#default_value' => (isset($data['bgcolor'])) ? $data['bgcolor'] : '#FFFFFF', - '#title' => t('Background color'), - '#description' => t('The background color to use for exposed areas of the image. Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave blank for transparency on image types that support it.'), - '#size' => 7, - '#maxlength' => 7, - '#element_validate' => array('image_effect_color_validate'), - ); - $form['random'] = array( - '#type' => 'checkbox', - '#default_value' => (isset($data['random'])) ? $data['random'] : 0, - '#title' => t('Randomize'), - '#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'), - ); - return $form; -} - -/** * Returns HTML for the page containing the list of image styles. * * @param $variables diff --git a/core/modules/image/image.api.php b/core/modules/image/image.api.php index d2990f5..471f9fa 100644 --- a/core/modules/image/image.api.php +++ b/core/modules/image/image.api.php @@ -11,58 +11,16 @@ */ /** - * Define information about image effects provided by a module. - * - * This hook enables modules to define image manipulation effects for use with - * an image style. - * - * @return - * An array of image effects. This array is keyed on the machine-readable - * effect name. Each effect is defined as an associative array containing the - * following items: - * - "label": The human-readable name of the effect. - * - "effect callback": The function to call to perform this image effect. - * - "dimensions passthrough": (optional) Set this item if the effect doesn't - * change the dimensions of the image. - * - "dimensions callback": (optional) The function to call to transform - * dimensions for this effect. - * - "help": (optional) A brief description of the effect that will be shown - * when adding or configuring this image effect. - * - "form callback": (optional) The name of a function that will return a - * $form array providing a configuration form for this image effect. - * - "summary theme": (optional) The name of a theme function that will output - * a summary of this image effect's configuration. - * - * @see hook_image_effect_info_alter() - */ -function hook_image_effect_info() { - $effects = array(); - - $effects['mymodule_resize'] = array( - 'label' => t('Resize'), - 'help' => t('Resize an image to an exact set of dimensions, ignoring aspect ratio.'), - 'effect callback' => 'mymodule_resize_effect', - 'dimensions callback' => 'mymodule_resize_dimensions', - 'form callback' => 'mymodule_resize_form', - 'summary theme' => 'mymodule_resize_summary', - ); - - return $effects; -} - -/** - * Alter the information provided in hook_image_effect_info(). + * Alter the information provided in \Drupal\image\Annotation\ImageEffect. * * @param $effects * The array of image effects, keyed on the machine-readable effect name. - * - * @see hook_image_effect_info() */ function hook_image_effect_info_alter(&$effects) { // Override the Image module's crop effect with more options. - $effects['image_crop']['effect callback'] = 'mymodule_crop_effect'; - $effects['image_crop']['dimensions callback'] = 'mymodule_crop_dimensions'; - $effects['image_crop']['form callback'] = 'mymodule_crop_form'; + $effects['image_crop']['effect_callback'] = 'mymodule_crop_effect'; + $effects['image_crop']['dimensions_callback'] = 'mymodule_crop_dimensions'; + $effects['image_crop']['form_callback'] = 'mymodule_crop_form'; } /** diff --git a/core/modules/image/image.effects.inc b/core/modules/image/image.effects.inc deleted file mode 100644 index 619b09b..0000000 --- a/core/modules/image/image.effects.inc +++ /dev/null @@ -1,322 +0,0 @@ - array( - 'label' => t('Resize'), - 'help' => t('Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.'), - 'effect callback' => 'image_resize_effect', - 'dimensions callback' => 'image_resize_dimensions', - 'form callback' => 'image_resize_form', - 'summary theme' => 'image_resize_summary', - ), - 'image_scale' => array( - 'label' => t('Scale'), - 'help' => t('Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.'), - 'effect callback' => 'image_scale_effect', - 'dimensions callback' => 'image_scale_dimensions', - 'form callback' => 'image_scale_form', - 'summary theme' => 'image_scale_summary', - ), - 'image_scale_and_crop' => array( - 'label' => t('Scale and crop'), - 'help' => t('Scale and crop will maintain the aspect-ratio of the original image, then crop the larger dimension. This is most useful for creating perfectly square thumbnails without stretching the image.'), - 'effect callback' => 'image_scale_and_crop_effect', - 'dimensions callback' => 'image_resize_dimensions', - 'form callback' => 'image_resize_form', - 'summary theme' => 'image_resize_summary', - ), - 'image_crop' => array( - 'label' => t('Crop'), - 'help' => t('Cropping will remove portions of an image to make it the specified dimensions.'), - 'effect callback' => 'image_crop_effect', - 'dimensions callback' => 'image_resize_dimensions', - 'form callback' => 'image_crop_form', - 'summary theme' => 'image_crop_summary', - ), - 'image_desaturate' => array( - 'label' => t('Desaturate'), - 'help' => t('Desaturate converts an image to grayscale.'), - 'effect callback' => 'image_desaturate_effect', - 'dimensions passthrough' => TRUE, - ), - 'image_rotate' => array( - 'label' => t('Rotate'), - 'help' => t('Rotating an image may cause the dimensions of an image to increase to fit the diagonal.'), - 'effect callback' => 'image_rotate_effect', - 'dimensions callback' => 'image_rotate_dimensions', - 'form callback' => 'image_rotate_form', - 'summary theme' => 'image_rotate_summary', - ), - ); - - return $effects; -} - -/** - * Image effect callback; Resize an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the resize effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - * @return bool - * TRUE on success. FALSE on failure to resize image. - * - * @see image_resize() - */ -function image_resize_effect($image, array $data) { - if (!image_resize($image, $data['width'], $data['height'])) { - watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image dimensions callback; Resize. - * - * @param array $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param array $data - * An array of attributes to use when performing the resize effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - */ -function image_resize_dimensions(array &$dimensions, array $data) { - // The new image will have the exact dimensions defined for the effect. - $dimensions['width'] = $data['width']; - $dimensions['height'] = $data['height']; -} - -/** - * Image effect callback; Scale an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the scale effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - "upscale": A boolean indicating that the image should be upscaled if the - * dimensions are larger than the original image. - * - * @return bool - * TRUE on success. FALSE on failure to scale image. - * - * @see image_scale() - */ -function image_scale_effect($image, array $data) { - // Set sane default values. - $data += array( - 'width' => NULL, - 'height' => NULL, - 'upscale' => FALSE, - ); - - if (!image_scale($image, $data['width'], $data['height'], $data['upscale'])) { - watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image dimensions callback; Scale. - * - * @param array $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param array $data - * An array of attributes to use when performing the scale effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - "upscale": A boolean indicating that the image should be upscaled if the - * dimensions are larger than the original image. - */ -function image_scale_dimensions(array &$dimensions, array $data) { - if ($dimensions['width'] && $dimensions['height']) { - image_dimensions_scale($dimensions, $data['width'], $data['height'], $data['upscale']); - } -} - -/** - * Image effect callback; Crop an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the crop effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - "anchor": A string describing where the crop should originate in the form - * of "XOFFSET-YOFFSET". XOFFSET is either a number of pixels or - * "left", "center", "right" and YOFFSET is either a number of pixels or - * "top", "center", "bottom". - * - * @return bool - * TRUE on success. FALSE on failure to crop image. - * - * @see image_crop() - */ -function image_crop_effect($image, array $data) { - // Set sane default values. - $data += array( - 'anchor' => 'center-center', - ); - - list($x, $y) = explode('-', $data['anchor']); - $x = image_filter_keyword($x, $image->info['width'], $data['width']); - $y = image_filter_keyword($y, $image->info['height'], $data['height']); - if (!image_crop($image, $x, $y, $data['width'], $data['height'])) { - watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image effect callback; Scale and crop an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the scale and crop effect - * with the following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - * @return bool - * TRUE on success. FALSE on failure to scale and crop image. - * - * @see image_scale_and_crop() - */ -function image_scale_and_crop_effect($image, array $data) { - if (!image_scale_and_crop($image, $data['width'], $data['height'])) { - watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image effect callback; Desaturate (grayscale) an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the desaturate effect. - * - * @return bool - * TRUE on success. FALSE on failure to desaturate image. - * - * @see image_desaturate() - */ -function image_desaturate_effect($image, $data) { - if (!image_desaturate($image)) { - watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image effect callback; Rotate an image resource. - * - * @param object $image - * An image object returned by image_load(). - * @param array $data - * An array of attributes to use when performing the rotate effect containing - * the following items: - * - "degrees": The number of (clockwise) degrees to rotate the image. - * - "random": A boolean indicating that a random rotation angle should be - * used for this image. The angle specified in "degrees" is used as a - * positive and negative maximum. - * - "bgcolor": The background color to use for exposed areas of the image. - * Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave - * blank for transparency on image types that support it. - * - * @return bool - * TRUE on success. FALSE on failure to rotate image. - * - * @see image_rotate(). - */ -function image_rotate_effect($image, $data) { - // Set sane default values. - $data += array( - 'degrees' => 0, - 'bgcolor' => NULL, - 'random' => FALSE, - ); - - // Convert short #FFF syntax to full #FFFFFF syntax. - if (strlen($data['bgcolor']) == 4) { - $c = $data['bgcolor']; - $data['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3]; - } - - // Convert #FFFFFF syntax to hexadecimal colors. - if ($data['bgcolor'] != '') { - $data['bgcolor'] = hexdec(str_replace('#', '0x', $data['bgcolor'])); - } - else { - $data['bgcolor'] = NULL; - } - - if (!empty($data['random'])) { - $degrees = abs((float) $data['degrees']); - $data['degrees'] = rand(-1 * $degrees, $degrees); - } - - if (!image_rotate($image, $data['degrees'], $data['bgcolor'])) { - watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR); - return FALSE; - } - return TRUE; -} - -/** - * Image dimensions callback; Rotate. - * - * @param array $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param array $data - * An array of attributes to use when performing the rotate effect containing - * the following items: - * - "degrees": The number of (clockwise) degrees to rotate the image. - * - "random": A boolean indicating that a random rotation angle should be - * used for this image. The angle specified in "degrees" is used as a - * positive and negative maximum. - */ -function image_rotate_dimensions(array &$dimensions, array $data) { - // If the rotate is not random and the angle is a multiple of 90 degrees, - // then the new dimensions can be determined. - if (!$data['random'] && ((int) ($data['degrees']) == $data['degrees']) && ($data['degrees'] % 90 == 0)) { - if ($data['degrees'] % 180 != 0) { - $temp = $dimensions['width']; - $dimensions['width'] = $dimensions['height']; - $dimensions['height'] = $temp; - } - } - else { - $dimensions['width'] = $dimensions['height'] = NULL; - } -} diff --git a/core/modules/image/image.install b/core/modules/image/image.install index fd440da..1617e8c 100644 --- a/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -126,14 +126,19 @@ function _image_update_get_style_with_effects(array $style) { ->condition('isid', $style['isid']) ->execute(); foreach ($result as $effect) { - unset($effect['isid']); $effect['data'] = unserialize($effect['data']); // Generate a unique image effect ID for the effect. $uuid = new Uuid(); - $effect['ieid'] = $uuid->generate(); + $effect['uuid'] = $uuid->generate(); - $effects[$effect['ieid']] = $effect; + // Use 'id' instead of 'name'. + $effect['id'] = $effect['name']; + + // Clear out legacy keys. + unset($effect['isid'], $effect['ieid'], $effect['name']); + + $effects[$effect['uuid']] = $effect; } return $effects; } diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 9a3dd64..32b606c 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -6,9 +6,12 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Image\ImageFile; use Drupal\Core\Language\Language; use Drupal\field\Plugin\Core\Entity\Field; use Drupal\field\Plugin\Core\Entity\FieldInstance; +use Drupal\image\ImageEffectInterface; +use Drupal\image\ImageStyleInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -76,10 +79,10 @@ function image_help($path, $arg) { case 'admin/config/media/image-styles': return '

' . t('Image styles commonly provide thumbnail sizes by scaling and cropping images, but can also add various effects before an image is displayed. When an image is displayed with a style, a new file is created and the original image is left unchanged.') . '

'; case 'admin/config/media/image-styles/manage/%/add/%': - $effect = image_effect_definition_load($arg[7]); + $effect = Drupal::service('plugin.manager.image.effect')->getDefinition($arg[7]); return isset($effect['help']) ? ('

' . $effect['help'] . '

') : NULL; case 'admin/config/media/image-styles/manage/%/effects/%': - $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]); + $effect = entity_load('image_style', $arg[5])->getEffect($arg[7])->getPluginDefinition(); return isset($effect['help']) ? ('

' . $effect['help'] . '

') : NULL; } } @@ -168,28 +171,20 @@ function image_menu() { 'weight' => 10, 'route_name' => 'image_style_delete', ); - $items['admin/config/media/image-styles/manage/%image_style/effects/%image_effect'] = array( + $items['admin/config/media/image-styles/manage/%/effects/%'] = array( 'title' => 'Edit image effect', 'description' => 'Edit an existing effect within a style.', - 'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE), - 'page callback' => 'drupal_get_form', - 'page arguments' => array('image_effect_form', 5, 7), - 'access arguments' => array('administer image styles'), - 'file' => 'image.admin.inc', + 'route_name' => 'image_effect_edit_form', ); - $items['admin/config/media/image-styles/manage/%image_style/effects/%image_effect/delete'] = array( + $items['admin/config/media/image-styles/manage/%image_style/effects/%/delete'] = array( 'title' => 'Delete image effect', 'description' => 'Delete an existing effect from a style.', 'route_name' => 'image_effect_delete', ); - $items['admin/config/media/image-styles/manage/%image_style/add/%image_effect_definition'] = array( + $items['admin/config/media/image-styles/manage/%/add/%'] = array( 'title' => 'Add image effect', 'description' => 'Add a new effect to a style.', - 'load arguments' => array(5), - 'page callback' => 'drupal_get_form', - 'page arguments' => array('image_effect_form', 5, 7), - 'access arguments' => array('administer image styles'), - 'file' => 'image.admin.inc', + 'route_name' => 'image_effect_add_form', ); return $items; @@ -216,35 +211,45 @@ function image_theme() { // Theme functions in image.admin.inc. 'image_style_list' => array( 'variables' => array('styles' => NULL), + 'file' => 'image.admin.inc', ), 'image_style_effects' => array( 'render element' => 'form', + 'file' => 'image.admin.inc', ), 'image_style_preview' => array( 'variables' => array('style' => NULL), + 'file' => 'image.admin.inc', ), 'image_anchor' => array( 'render element' => 'element', + 'file' => 'image.admin.inc', ), 'image_resize_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), 'image_scale_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), 'image_crop_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), 'image_rotate_summary' => array( 'variables' => array('data' => NULL), + 'file' => 'image.admin.inc', ), // Theme functions in image.field.inc. 'image_widget' => array( 'render element' => 'element', + 'file' => 'image.field.inc', ), 'image_formatter' => array( 'variables' => array('item' => NULL, 'path' => NULL, 'image_style' => NULL), + 'file' => 'image.field.inc', ), ); } @@ -476,11 +481,11 @@ function image_style_deliver($style, $scheme) { } if ($success) { - $image = image_load($derivative_uri); - $uri = $image->source; + $image = new ImageFile($derivative_uri); + $uri = $image->getSource(); $headers = array( - 'Content-Type' => $image->info['mime_type'], - 'Content-Length' => $image->info['file_size'], + 'Content-Type' => $image->get('mime_type'), + 'Content-Length' => $image->get('file_size'), ); return new BinaryFileResponse($uri, 200, $headers); } @@ -494,8 +499,8 @@ function image_style_deliver($style, $scheme) { * Creates a new image derivative based on an image style. * * Generates an image derivative by creating the destination folder (if it does - * not already exist), applying all image effects defined in $style->effects, - * and saving a cached version of the resulting image. + * not already exist), applying all image effects defined in + * $style->getEffects(), and saving a cached version of the resulting image. * * @param $style * An image style array. @@ -520,17 +525,16 @@ function image_style_create_derivative($style, $source, $destination) { return FALSE; } - if (!$image = image_load($source)) { + $image = new ImageFile($source); + if (!$image->getResource()) { return FALSE; } - if (!empty($style->effects)) { - foreach ($style->effects as $effect) { - image_effect_apply($image, $effect); - } + foreach ($style->getEffects() as $effect) { + $effect->processEffect($image); } - if (!image_save($image, $destination)) { + if (!$image->save($destination)) { if (file_exists($destination)) { watchdog('image', 'Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $destination), WATCHDOG_ERROR); } @@ -552,53 +556,14 @@ function image_style_create_derivative($style, $source, $destination) { * pixels. */ function image_style_transform_dimensions($style_name, array &$dimensions) { - module_load_include('inc', 'image', 'image.effects'); $style = entity_load('image_style', $style_name); - if (!empty($style->effects)) { - foreach ($style->effects as $effect) { - if (isset($effect['dimensions passthrough'])) { - continue; - } - - if (isset($effect['dimensions callback'])) { - $effect['dimensions callback']($dimensions, $effect['data']); - } - else { - $dimensions['width'] = $dimensions['height'] = NULL; - } - } + foreach ($style->getEffects() as $effect) { + $effect->processDimensions($dimensions); } } /** - * Flushes cached media for a style. - * - * @param $style - * An image style array. - */ -function image_style_flush($style) { - // Delete the style directory in each registered wrapper. - $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE); - foreach ($wrappers as $wrapper => $wrapper_data) { - file_unmanaged_delete_recursive($wrapper . '://styles/' . $style->id()); - } - - // Let other modules update as necessary on flush. - module_invoke_all('image_style_flush', $style); - - // Clear field caches so that formatters may be added for this style. - field_info_cache_clear(); - drupal_theme_rebuild(); - - // Clear page caches when flushing. - if (module_exists('block')) { - cache('block')->deleteAll(); - } - cache('page')->deleteAll(); -} - -/** * Returns the URL for an image derivative given a style and image path. * * @param $style_name @@ -704,174 +669,6 @@ function image_style_path($style_name, $uri) { } /** - * Returns a set of image effects. - * - * These image effects are exposed by modules implementing - * hook_image_effect_info(). - * - * @return - * An array of image effects to be used when transforming images. - * @see hook_image_effect_info() - * @see image_effect_definition_load() - */ -function image_effect_definitions() { - $language_interface = language(Language::TYPE_INTERFACE); - - // hook_image_effect_info() includes translated strings, so each language is - // cached separately. - $langcode = $language_interface->id; - - $effects = &drupal_static(__FUNCTION__); - - if (!isset($effects)) { - if ($cache = cache()->get("image_effects:$langcode")) { - $effects = $cache->data; - } - else { - $effects = array(); - include_once __DIR__ . '/image.effects.inc'; - foreach (module_implements('image_effect_info') as $module) { - foreach (module_invoke($module, 'image_effect_info') as $name => $effect) { - // Ensure the current toolkit supports the effect. - $effect['module'] = $module; - $effect['name'] = $name; - $effect['data'] = isset($effect['data']) ? $effect['data'] : array(); - $effects[$name] = $effect; - } - } - uasort($effects, '_image_effect_definitions_sort'); - drupal_alter('image_effect_info', $effects); - cache()->set("image_effects:$langcode", $effects); - } - } - - return $effects; -} - -/** - * Loads the definition for an image effect. - * - * The effect definition is a set of core properties for an image effect, not - * containing any user-settings. The definition defines various functions to - * call when configuring or executing an image effect. This loader is mostly for - * internal use within image.module. Use image_effect_load() or - * entity_load() to get image effects that contain configuration. - * - * @param $effect - * The name of the effect definition to load. - * @return - * An array containing the image effect definition with the following keys: - * - "effect": The unique name for the effect being performed. Usually prefixed - * with the name of the module providing the effect. - * - "module": The module providing the effect. - * - "help": A description of the effect. - * - "function": The name of the function that will execute the effect. - * - "form": (optional) The name of a function to configure the effect. - * - "summary": (optional) The name of a theme function that will display a - * one-line summary of the effect. Does not include the "theme_" prefix. - */ -function image_effect_definition_load($effect) { - $definitions = image_effect_definitions(); - return isset($definitions[$effect]) ? $definitions[$effect] : NULL; -} - -/** - * Loads a single image effect. - * - * @param $ieid - * The image effect ID. - * @param $style_name - * The image style name. - * - * @return - * An image effect array, consisting of the following keys: - * - "ieid": The unique image effect ID. - * - "weight": The weight of this image effect within the image style. - * - "name": The name of the effect definition that powers this image effect. - * - "data": An array of configuration options for this image effect. - * Besides these keys, the entirety of the image definition is merged into - * the image effect array. Returns NULL if the specified effect cannot be - * found. - * @see image_effect_definition_load() - */ -function image_effect_load($ieid, $style_name) { - if (($style = entity_load('image_style', $style_name)) && isset($style->effects[$ieid])) { - $effect = $style->effects[$ieid]; - $definition = image_effect_definition_load($effect['name']); - $effect = array_merge($definition, $effect); - // @todo The effect's key name within the style is unknown. It *should* be - // identical to the ieid, but that is in no way guaranteed. And of course, - // the ieid key *within* the effect is senseless duplication in the first - // place. This problem can be eliminated in many places, but especially - // for loaded menu arguments like %image_effect, the actual router - // callbacks don't have access to 'ieid' anymore (unless resorting to - // dirty %index and %map tricks). - $effect['ieid'] = $ieid; - return $effect; - } - return NULL; -} - -/** - * Saves an image effect. - * - * @param ImageStyle $style - * The image style this effect belongs to. - * @param array $effect - * An image effect array. Passed by reference. - * - * @return array - * The saved image effect array. The 'ieid' key will be set for the effect. - */ -function image_effect_save($style, &$effect) { - // Remove all values that are not properties of an image effect. - // @todo Convert image effects into plugins. - $effect = array_intersect_key($effect, array_flip(array('ieid', 'module', 'name', 'data', 'weight'))); - - // Generate a unique image effect ID for a new effect. - if (empty($effect['ieid'])) { - $uuid = new Uuid(); - $effect['ieid'] = $uuid->generate(); - } - $style->effects[$effect['ieid']] = $effect; - $style->save(); - - // Flush all derivatives that exist for this style, so they are regenerated - // with the new or updated effect. - image_style_flush($style); -} - -/** - * Deletes an image effect. - * - * @param ImageStyle $style - * The image style this effect belongs to. - * @param $effect - * An image effect array. - */ -function image_effect_delete($style, $effect) { - unset($style->effects[$effect['ieid']]); - $style->save(); - image_style_flush($style); -} - -/** - * Applies an image effect to the image object. - * - * @param $image - * An image object returned by image_load(). - * @param $effect - * An image effect array. - * @return - * TRUE on success. FALSE if unable to perform the image effect on the image. - */ -function image_effect_apply($image, $effect) { - module_load_include('inc', 'image', 'image.effects'); - $function = $effect['effect callback']; - return $function($image, $effect['data']); -} - -/** * Returns HTML for an image using a specific image style. * * @param $variables @@ -945,15 +742,6 @@ function image_filter_keyword($value, $current_pixels, $new_pixels) { } /** - * Internal function for sorting image effect definitions through uasort(). - * - * @see image_effect_definitions() - */ -function _image_effect_definitions_sort($a, $b) { - return strcasecmp($a['name'], $b['name']); -} - -/** * Implements hook_entity_presave(). * * Transforms default image of image field from array into single value at save. diff --git a/core/modules/image/image.routing.yml b/core/modules/image/image.routing.yml index b178d33..01a7d53 100644 --- a/core/modules/image/image.routing.yml +++ b/core/modules/image/image.routing.yml @@ -11,3 +11,17 @@ image_effect_delete: _form: '\Drupal\image\Form\ImageEffectDeleteForm' requirements: _permission: 'administer image styles' + +image_effect_add_form: + pattern: '/admin/config/media/image-styles/manage/{image_style}/add/{image_effect}' + defaults: + _form: '\Drupal\image\Form\ImageEffectAddForm' + requirements: + _permission: 'administer image styles' + +image_effect_edit_form: + pattern: '/admin/config/media/image-styles/manage/{image_style}/effects/{image_effect}' + defaults: + _form: '\Drupal\image\Form\ImageEffectEditForm' + requirements: + _permission: 'administer image styles' diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml new file mode 100644 index 0000000..0658bf7 --- /dev/null +++ b/core/modules/image/image.services.yml @@ -0,0 +1,4 @@ +services: + plugin.manager.image.effect: + class: Drupal\image\ImageEffectManager + arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler'] diff --git a/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php new file mode 100644 index 0000000..efe1358 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php @@ -0,0 +1,53 @@ +effectManager = $effect_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.image.effect') + ); + } + + /** + * {@inheritdoc} + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * @param \Drupal\image\ImageStyleInterface $image_style + * The image style. + * @param string $image_effect + * The image effect ID. + * + * @return array|\Symfony\Component\HttpFoundation\RedirectResponse + * The form structure. + */ + public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) { + $form = parent::buildForm($form, $form_state, $request, $image_style, $image_effect); + + drupal_set_title(t('Add %label effect', array('%label' => $this->imageEffect->label())), PASS_THROUGH); + $form['actions']['submit']['#value'] = t('Add effect'); + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function prepareImageEffect($image_effect) { + return $this->effectManager->createInstance($image_effect); + } + +} diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php index 50bf2bb..330d626 100644 --- a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\ConfirmFormBase; use Drupal\image\Plugin\Core\Entity\ImageStyle; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; /** @@ -26,7 +27,7 @@ class ImageEffectDeleteForm extends ConfirmFormBase { /** * The image effect to be deleted. * - * @var array; + * @var \Drupal\image\ImageEffectInterface */ protected $imageEffect; @@ -34,7 +35,7 @@ class ImageEffectDeleteForm extends ConfirmFormBase { * {@inheritdoc} */ public function getQuestion() { - return t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $this->imageStyle->label(), '@effect' => $this->imageEffect['label'])); + return t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $this->imageStyle->label(), '@effect' => $this->imageEffect->label())); } /** @@ -63,7 +64,7 @@ public function getFormID() { */ public function buildForm(array $form, array &$form_state, $image_style = NULL, $image_effect = NULL, Request $request = NULL) { $this->imageStyle = $image_style; - $this->imageEffect = image_effect_load($image_effect, $this->imageStyle->id()); + $this->imageEffect = $this->imageStyle->getEffect($image_effect); return parent::buildForm($form, $form_state, $request); } @@ -72,8 +73,8 @@ public function buildForm(array $form, array &$form_state, $image_style = NULL, * {@inheritdoc} */ public function submitForm(array &$form, array &$form_state) { - image_effect_delete($this->imageStyle, $this->imageEffect); - drupal_set_message(t('The image effect %name has been deleted.', array('%name' => $this->imageEffect['label']))); + $this->imageStyle->deleteImageEffect($this->imageEffect); + drupal_set_message(t('The image effect %name has been deleted.', array('%name' => $this->imageEffect->label()))); $form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $this->imageStyle->id(); } diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php new file mode 100644 index 0000000..dd810c0 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php @@ -0,0 +1,52 @@ + $this->imageEffect->label())), PASS_THROUGH); + $form['actions']['submit']['#value'] = t('Update effect'); + + // If the weight is not specified in the URL, use the default. + if ($this->imageEffect->getWeight() !== '' && !$request->query->has('weight')) { + $form['weight']['#value'] = $this->imageEffect->getWeight(); + } + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function prepareImageEffect($image_effect) { + return $this->imageStyle->getEffect($image_effect); + } + +} diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php new file mode 100644 index 0000000..bfe7f5b --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php @@ -0,0 +1,123 @@ +imageStyle = $image_style; + $this->imageEffect = $this->prepareImageEffect($image_effect); + + if (!$this->imageEffect->hasForm()) { + return new RedirectResponse(url('admin/config/media/image-styles/manage/' . $image_style->id(), array('absolute' => TRUE))); + } + + $form['#attached']['css'][drupal_get_path('module', 'image') . '/css/image.admin.css'] = array(); + $form['uuid'] = array( + '#type' => 'value', + '#value' => $this->imageEffect->getUuid(), + ); + $form['id'] = array( + '#type' => 'value', + '#value' => $this->imageEffect->getPluginId(), + ); + + $form['data'] = $this->imageEffect->getForm(); + $form['data']['#tree'] = TRUE; + + // Check the URL for a weight, then the image effect, otherwise use default. + $form['weight'] = array( + '#type' => 'hidden', + '#value' => $request->query->has('weight') ? intval($request->query->get('weight')) : count($this->imageStyle->getEffects()), + ); + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#button_type' => 'primary', + ); + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => 'admin/config/media/image-styles/manage/' . $this->imageStyle->id(), + ); + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + form_state_values_clean($form_state); + $this->imageStyle->saveImageEffect($form_state['values']); + + drupal_set_message(t('The image effect was successfully applied.')); + $form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $this->imageStyle->id(); + } + + /** + * Converts an image effect ID into an object. + * + * @param string $image_effect + * The image effect ID. + * + * @return \Drupal\image\ImageEffectInterface + * The image effect object. + */ + abstract protected function prepareImageEffect($image_effect); + +} diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBag.php b/core/modules/image/lib/Drupal/image/ImageEffectBag.php new file mode 100644 index 0000000..0bdee3b --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectBag.php @@ -0,0 +1,124 @@ +manager = $manager; + $this->configurations = $configurations; + + if (!empty($configurations)) { + $this->instanceIDs = MapArray::copyValuesToKeys(array_keys($configurations)); + } + } + + /** + * {@inheritdoc} + */ + protected function initializePlugin($instance_id) { + if (!isset($this->pluginInstances[$instance_id])) { + $configuration = $this->configurations[$instance_id] + array('data' => array()); + $this->pluginInstances[$instance_id] = $this->manager->createInstance($configuration['id'], $configuration); + } + } + + /** + * Returns the current configuration of all image effects in this bag. + * + * @return array + * An associative array keyed by image effect UUID, whose values are image + * effect configurations. + */ + public function export() { + $instances = array(); + $this->rewind(); + foreach ($this as $instance_id => $instance) { + $instances[$instance_id] = $instance->export(); + } + return $instances; + } + + /** + * Removes an instance ID. + * + * @param string $instance_id + * An image effect instance IDs. + */ + public function removeInstanceID($instance_id) { + unset($this->instanceIDs[$instance_id], $this->configurations[$instance_id]); + $this->remove($instance_id); + } + + /** + * Updates the configuration for an image effect instance. + * + * If there is no plugin instance yet, a new will be instantiated. Otherwise, + * the existing instance is updated with the new configuration. + * + * @param array $configuration + * The image effect configuration to set. + * + * @return string + */ + public function setConfig(array $configuration) { + // Derive the instance ID from the configuration. + if (empty($configuration['uuid'])) { + $uuid_generator = new Uuid(); + $configuration['uuid'] = $uuid_generator->generate(); + } + $instance_id = $configuration['uuid']; + $this->configurations[$instance_id] = $configuration; + $this->get($instance_id)->setPluginConfiguration($configuration); + $this->addInstanceID($instance_id); + return $instance_id; + } + + /** + * Sorts all image effect instances in this bag. + * + * @return self + */ + public function sort() { + uasort($this->configurations, 'drupal_sort_weight'); + $this->instanceIDs = MapArray::copyValuesToKeys(array_keys($this->configurations)); + return $this; + } + +} diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBase.php b/core/modules/image/lib/Drupal/image/ImageEffectBase.php new file mode 100644 index 0000000..82901c9 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectBase.php @@ -0,0 +1,126 @@ +setPluginConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ + public function processDimensions(array &$dimensions) { + $dimensions['width'] = $dimensions['height'] = NULL; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#markup' => '', + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + return array(); + } + + /** + * {@inheritdoc} + */ + public function hasForm() { + return !$this->pluginDefinition['no_form']; + } + + /** + * {@inheritdoc} + */ + public function label() { + return $this->pluginDefinition['label']; + } + + /** + * {@inheritdoc} + */ + public function getUuid() { + return $this->uuid; + } + + /** + * {@inheritdoc} + */ + public function setWeight($weight) { + $this->weight = $weight; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getWeight() { + return $this->weight; + } + + /** + * {@inheritdoc} + */ + public function export() { + return array( + 'uuid' => $this->getUuid(), + 'id' => $this->getPluginId(), + 'weight' => $this->getWeight(), + 'data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function setPluginConfiguration(array $configuration) { + $configuration += array( + 'data' => array(), + 'uuid' => '', + 'weight' => '', + ); + $this->configuration = $configuration['data']; + $this->uuid = $configuration['uuid']; + $this->weight = $configuration['weight']; + return $this; + } + +} diff --git a/core/modules/image/lib/Drupal/image/ImageEffectInterface.php b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php new file mode 100644 index 0000000..a9ee455 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php @@ -0,0 +1,122 @@ + $namespaces['Drupal\image']); + parent::__construct('ImageEffect', $namespaces, $annotation_namespaces, 'Drupal\image\Annotation\ImageEffect'); + + $this->alterInfo($module_handler, 'image_effect_info'); + $this->setCacheBackend($cache_backend, $language_manager, 'image_effect'); + } + +} diff --git a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php index 87367d3..f808eb6 100644 --- a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php +++ b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php @@ -2,16 +2,66 @@ /** * @file - * Contains \Drupal\image\Plugin\Core\Entity\ImageStyleInterface. + * Contains \Drupal\image\ImageStyleInterface. */ namespace Drupal\image; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\image\ImageEffectInterface; /** * Provides an interface defining an image style entity. */ interface ImageStyleInterface extends ConfigEntityInterface { + /** + * Returns a specific image effect. + * + * @param string $effect + * The image effect ID. + * + * @return \Drupal\image\ImageEffectInterface + * The image effect object. + */ + public function getEffect($effect); + + /** + * Returns the image effects for this style. + * + * @return \Drupal\image\ImageEffectBag + * The image effect plugin bag. + */ + public function getEffects(); + + /** + * Flushes cached media for a style. + * + * @return self + * This image style. + */ + public function flush(); + + /** + * Saves an image effect for this style. + * + * @param array $configuration + * An array of image effect configuration. + * + * @return string + * The image effect ID. + */ + public function saveImageEffect(array $configuration); + + /** + * Deletes an image effect from this style. + * + * @param \Drupal\image\ImageEffectInterface $effect + * The image effect object. + * + * @return self + * This image style. + */ + public function deleteImageEffect(ImageEffectInterface $effect); + } diff --git a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php deleted file mode 100644 index 3f35f5b..0000000 --- a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php +++ /dev/null @@ -1,36 +0,0 @@ -effects)) { - foreach ($style->effects as $ieid => $effect) { - $definition = image_effect_definition_load($effect['name']); - $effect = array_merge($definition, $effect); - $style->effects[$ieid] = $effect; - } - // Sort effects by weight. - uasort($style->effects, 'drupal_sort_weight'); - } - } - parent::attachLoad($queried_entities, $revision_id); - } - -} diff --git a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php index 2e99768..d8163c4 100644 --- a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php +++ b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php @@ -11,6 +11,8 @@ use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\Core\Entity\EntityStorageControllerInterface; +use Drupal\image\ImageEffectBag; +use Drupal\image\ImageEffectInterface; use Drupal\image\ImageStyleInterface; /** @@ -24,7 +26,7 @@ * "form" = { * "delete" = "Drupal\image\Form\ImageStyleDeleteForm" * }, - * "storage" = "Drupal\image\ImageStyleStorageController" + * "storage" = "Drupal\Core\Config\Entity\ConfigStorageController" * }, * uri_callback = "image_style_entity_uri", * config_prefix = "image.style", @@ -68,9 +70,14 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface { /** * The array of image effects for this image style. * - * @var string + * @var array */ - public $effects; + protected $effects = array(); + + /** + * @var \Drupal\image\ImageEffectBag + */ + protected $effectsBag; /** * Overrides Drupal\Core\Entity\Entity::id(). @@ -85,7 +92,7 @@ public function id() { public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { if ($update && !empty($this->original) && $this->id() !== $this->original->id()) { // The old image style name needs flushing after a rename. - image_style_flush($this->original); + $this->original->flush(); // Update field instance settings if necessary. static::replaceImageStyle($this); } @@ -97,7 +104,7 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $ public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { foreach ($entities as $style) { // Flush cached media for the deleted style. - image_style_flush($style); + $style->flush(); // Check whether field instance settings need to be updated. // In case no replacement style was specified, all image fields that are // using the deleted style are left in a broken state. @@ -148,4 +155,76 @@ protected static function replaceImageStyle(ImageStyle $style) { } } + /** + * {@inheritdoc} + */ + public function getEffect($effect) { + return $this->getEffects()->get($effect); + } + + /** + * {@inheritdoc} + */ + public function getEffects() { + if (!$this->effectsBag) { + $this->effectsBag = new ImageEffectBag(\Drupal::service('plugin.manager.image.effect'), $this->effects); + } + return $this->effectsBag; + } + + /** + * {@inheritdoc} + */ + public function saveImageEffect(array $configuration) { + $effect_id = $this->getEffects()->setConfig($configuration); + $this->save(); + $this->flush(); + return $effect_id; + } + + /** + * {@inheritdoc} + */ + public function deleteImageEffect(ImageEffectInterface $effect) { + $this->getEffects()->removeInstanceID($effect->getUuid()); + $this->save(); + $this->flush(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getExportProperties() { + $properties = parent::getExportProperties(); + $properties['effects'] = $this->getEffects()->sort()->export(); + return $properties; + } + + /** + * {@inheritdoc} + */ + public function flush() { + // Delete the style directory in each registered wrapper. + $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE); + foreach ($wrappers as $wrapper => $wrapper_data) { + file_unmanaged_delete_recursive($wrapper . '://styles/' . $this->id()); + } + + // Let other modules update as necessary on flush. + $module_handler = \Drupal::moduleHandler(); + $module_handler->invokeAll('image_style_flush', array($this)); + + // Clear field caches so that formatters may be added for this style. + field_info_cache_clear(); + drupal_theme_rebuild(); + + // Clear page caches when flushing. + if ($module_handler->moduleExists('block')) { + \Drupal::cache('block')->deleteAll(); + } + \Drupal::cache('page')->deleteAll(); + return $this; + } + } diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php new file mode 100644 index 0000000..776099f --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php @@ -0,0 +1,85 @@ +configuration += array( + 'anchor' => 'center-center', + ); + + list($x, $y) = explode('-', $this->configuration['anchor']); + $x = image_filter_keyword($x, $image->get('width'), $this->configuration['width']); + $y = image_filter_keyword($y, $image->get('height'), $this->configuration['height']); + if (!$image->crop($x, $y, $this->configuration['width'], $this->configuration['height'])) { + watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_crop_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $this->configuration += array( + 'width' => '', + 'height' => '', + 'anchor' => 'center-center', + ); + $form = parent::getForm(); + $form['anchor'] = array( + '#type' => 'radios', + '#title' => t('Anchor'), + '#options' => array( + 'left-top' => t('Top') . ' ' . t('Left'), + 'center-top' => t('Top') . ' ' . t('Center'), + 'right-top' => t('Top') . ' ' . t('Right'), + 'left-center' => t('Center') . ' ' . t('Left'), + 'center-center' => t('Center'), + 'right-center' => t('Center') . ' ' . t('Right'), + 'left-bottom' => t('Bottom') . ' ' . t('Left'), + 'center-bottom' => t('Bottom') . ' ' . t('Center'), + 'right-bottom' => t('Bottom') . ' ' . t('Right'), + ), + '#theme' => 'image_anchor', + '#default_value' => $this->configuration['anchor'], + '#description' => t('The part of the image that will be retained during the crop.'), + ); + return $form; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php new file mode 100644 index 0000000..f2577c5 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php @@ -0,0 +1,44 @@ +desaturate()) { + watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php new file mode 100644 index 0000000..71ee0e4 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php @@ -0,0 +1,79 @@ +resize($this->configuration['width'], $this->configuration['height'])) { + watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function processDimensions(array &$dimensions) { + // The new image will have the exact dimensions defined for the effect. + $dimensions['width'] = $this->configuration['width']; + $dimensions['height'] = $this->configuration['height']; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_resize_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $form['width'] = array( + '#type' => 'number', + '#title' => t('Width'), + '#default_value' => isset($this->configuration['width']) ? $this->configuration['width'] : '', + '#field_suffix' => ' ' . t('pixels'), + '#required' => TRUE, + '#min' => 1, + ); + $form['height'] = array( + '#type' => 'number', + '#title' => t('Height'), + '#default_value' => isset($this->configuration['height']) ? $this->configuration['height'] : '', + '#field_suffix' => ' ' . t('pixels'), + '#required' => TRUE, + '#min' => 1, + ); + return $form; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php new file mode 100644 index 0000000..053eac7 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php @@ -0,0 +1,132 @@ +configuration += array( + 'degrees' => 0, + 'bgcolor' => NULL, + 'random' => FALSE, + ); + + // Convert short #FFF syntax to full #FFFFFF syntax. + if (strlen($this->configuration['bgcolor']) == 4) { + $c = $this->configuration['bgcolor']; + $this->configuration['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3]; + } + + // Convert #FFFFFF syntax to hexadecimal colors. + if ($this->configuration['bgcolor'] != '') { + $this->configuration['bgcolor'] = hexdec(str_replace('#', '0x', $this->configuration['bgcolor'])); + } + else { + $this->configuration['bgcolor'] = NULL; + } + + if (!empty($this->configuration['random'])) { + $degrees = abs((float) $this->configuration['degrees']); + $this->configuration['degrees'] = rand(-1 * $degrees, $degrees); + } + + if (!$image->rotate($this->configuration['degrees'], $this->configuration['bgcolor'])) { + watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function processDimensions(array &$dimensions) { + // If the rotate is not random and the angle is a multiple of 90 degrees, + // then the new dimensions can be determined. + if (!$this->configuration['random'] && ((int) ($this->configuration['degrees']) == $this->configuration['degrees']) && ($this->configuration['degrees'] % 90 == 0)) { + if ($this->configuration['degrees'] % 180 != 0) { + $temp = $dimensions['width']; + $dimensions['width'] = $dimensions['height']; + $dimensions['height'] = $temp; + } + } + else { + $dimensions['width'] = $dimensions['height'] = NULL; + } + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_rotate_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $form['degrees'] = array( + '#type' => 'number', + '#default_value' => (isset($this->configuration['degrees'])) ? $this->configuration['degrees'] : 0, + '#title' => t('Rotation angle'), + '#description' => t('The number of degrees the image should be rotated. Positive numbers are clockwise, negative are counter-clockwise.'), + '#field_suffix' => '°', + '#required' => TRUE, + ); + $form['bgcolor'] = array( + '#type' => 'textfield', + '#default_value' => (isset($this->configuration['bgcolor'])) ? $this->configuration['bgcolor'] : '#FFFFFF', + '#title' => t('Background color'), + '#description' => t('The background color to use for exposed areas of the image. Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave blank for transparency on image types that support it.'), + '#size' => 7, + '#maxlength' => 7, + '#element_validate' => array(array($this, 'validateColorEffect')), + ); + $form['random'] = array( + '#type' => 'checkbox', + '#default_value' => (isset($this->configuration['random'])) ? $this->configuration['random'] : 0, + '#title' => t('Randomize'), + '#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'), + ); + return $form; + } + + /** + * Validates to ensure a hexadecimal color value. + */ + public function validateColorEffect(array $element, array &$form_state) { + if ($element['#value'] != '') { + if (!preg_match('/^#[0-9A-F]{3}([0-9A-F]{3})?$/', $element['#value'])) { + form_error($element, t('!name must be a hexadecimal color value.', array('!name' => $element['#title']))); + } + } + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php new file mode 100644 index 0000000..66fddf7 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php @@ -0,0 +1,36 @@ +scaleAndCrop($this->configuration['width'], $this->configuration['height'])) { + watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + +} diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php new file mode 100644 index 0000000..577e4b7 --- /dev/null +++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php @@ -0,0 +1,89 @@ +configuration += array( + 'width' => NULL, + 'height' => NULL, + 'upscale' => FALSE, + ); + + if (!$image->scale($this->configuration['width'], $this->configuration['height'], $this->configuration['upscale'])) { + watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR); + return FALSE; + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function processDimensions(array &$dimensions) { + if ($dimensions['width'] && $dimensions['height']) { + Image::scaleDimensions($dimensions, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale']); + } + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return array( + '#theme' => 'image_scale_summary', + '#data' => $this->configuration, + ); + } + + /** + * {@inheritdoc} + */ + public function getForm() { + $form = parent::getForm(); + $form['#element_validate'] = array(array($this, 'validateScaleEffect')); + $form['width']['#required'] = FALSE; + $form['height']['#required'] = FALSE; + $form['upscale'] = array( + '#type' => 'checkbox', + '#default_value' => (isset($this->configuration['upscale'])) ? $this->configuration['upscale'] : 0, + '#title' => t('Allow Upscaling'), + '#description' => t('Let scale make images larger than their original size'), + ); + return $form; + } + + /** + * Validates to ensure that either a height or a width is specified. + */ + public function validateScaleEffect(array $element, array &$form_state) { + if (empty($element['width']['#value']) && empty($element['height']['#value'])) { + form_error($element, t('Width and height can not both be blank.')); + } + } + +} diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php index 973a8ef..62a97c4 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php @@ -129,23 +129,23 @@ function testStyle() { // Confirm that all effects on the image style have settings on the effect // edit form that match what was saved. - $ieids = array(); - foreach ($style->effects as $ieid => $effect) { - // Store the ieid for later use. - $ieids[$effect['name']] = $ieid; - $this->drupalGet($style_path . '/effects/' . $ieid); - foreach ($effect_edits[$effect['name']] as $field => $value) { - $this->assertFieldByName($field, $value, format_string('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect['name'], '%value' => $value))); + $uuids = array(); + foreach ($style->getEffects() as $uuid => $effect) { + // Store the uuid for later use. + $uuids[$effect->getPluginId()] = $uuid; + $this->drupalGet($style_path . '/effects/' . $uuid); + foreach ($effect_edits[$effect->getPluginId()] as $field => $value) { + $this->assertFieldByName($field, $value, format_string('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect->getPluginId(), '%value' => $value))); } } // Assert that every effect was saved. foreach (array_keys($effect_edits) as $effect_name) { - $this->assertTrue(isset($ieids[$effect_name]), format_string( - 'A %effect_name effect was saved with ID %ieid', + $this->assertTrue(isset($uuids[$effect_name]), format_string( + 'A %effect_name effect was saved with ID %uuid', array( '%effect_name' => $effect_name, - '%ieid' => $ieids[$effect_name], + '%uuid' => $uuids[$effect_name], ))); } @@ -154,12 +154,13 @@ function testStyle() { // Confirm the order of effects is maintained according to the order we // added the fields. $effect_edits_order = array_keys($effect_edits); - $effects_order = array_values($style->effects); $order_correct = TRUE; - foreach ($effects_order as $index => $effect) { - if ($effect_edits_order[$index] != $effect['name']) { + $index = 0; + foreach ($style->getEffects()->sort() as $effect) { + if ($effect_edits_order[$index] != $effect->getPluginId()) { $order_correct = FALSE; } + $index++; } $this->assertTrue($order_correct, 'The order of the effects is correctly set by default.'); @@ -172,8 +173,8 @@ function testStyle() { 'name' => $style_name, 'label' => $style_label, ); - foreach ($style->effects as $ieid => $effect) { - $edit['effects[' . $ieid . '][weight]'] = $weight; + foreach ($style->getEffects() as $uuid => $effect) { + $edit['effects[' . $uuid . '][weight]'] = $weight; $weight--; } @@ -200,12 +201,13 @@ function testStyle() { // Confirm the new style order was saved. $effect_edits_order = array_reverse($effect_edits_order); - $effects_order = array_values($style->effects); $order_correct = TRUE; - foreach ($effects_order as $index => $effect) { - if ($effect_edits_order[$index] != $effect['name']) { + $index = 0; + foreach ($style->getEffects()->sort() as $effect) { + if ($effect_edits_order[$index] != $effect->getPluginId()) { $order_correct = FALSE; } + $index++; } $this->assertTrue($order_correct, 'The order of the effects is correctly set by default.'); @@ -216,19 +218,20 @@ function testStyle() { $this->assertEqual($this->getImageCount($style), 1, format_string('Image style %style image %file successfully generated.', array('%style' => $style->label(), '%file' => $image_path))); // Delete the 'image_crop' effect from the style. - $this->drupalPost($style_path . '/effects/' . $ieids['image_crop'] . '/delete', array(), t('Delete')); + $this->drupalPost($style_path . '/effects/' . $uuids['image_crop'] . '/delete', array(), t('Delete')); // Confirm that the form submission was successful. $this->assertResponse(200); - $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $style->effects[$ieids['image_crop']]['label']))); + $image_crop_effect = $style->getEffect($uuids['image_crop']); + $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $image_crop_effect->label()))); // Confirm that there is no longer a link to the effect. - $this->assertNoLinkByHref($style_path . '/effects/' . $ieids['image_crop'] . '/delete'); + $this->assertNoLinkByHref($style_path . '/effects/' . $uuids['image_crop'] . '/delete'); // Refresh the image style information and verify that the effect was // actually deleted. $style = entity_load_unchanged('image_style', $style->id()); - $this->assertFalse(isset($style->effects[$ieids['image_crop']]), format_string( - 'Effect with ID %ieid no longer found on image style %style', + $this->assertFalse($style->getEffects()->has($uuids['image_crop']), format_string( + 'Effect with ID %uuid no longer found on image style %style', array( - '%ieid' => $ieids['image_crop'], + '%uuid' => $uuids['image_crop'], '%style' => $style->label, ))); diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php index e7552d2..7f45aec 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php @@ -59,7 +59,7 @@ function testImageDimensions() { // Scale an image that is wider than it is high. $effect = array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array( 'width' => 120, 'height' => 90, @@ -68,7 +68,7 @@ function testImageDimensions() { 'weight' => 0, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -81,7 +81,7 @@ function testImageDimensions() { // Rotate 90 degrees anticlockwise. $effect = array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => -90, 'random' => FALSE, @@ -89,7 +89,7 @@ function testImageDimensions() { 'weight' => 1, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -102,7 +102,7 @@ function testImageDimensions() { // Scale an image that is higher than it is wide (rotated by previous effect). $effect = array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array( 'width' => 120, 'height' => 90, @@ -111,7 +111,7 @@ function testImageDimensions() { 'weight' => 2, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -124,7 +124,7 @@ function testImageDimensions() { // Test upscale disabled. $effect = array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array( 'width' => 400, 'height' => 200, @@ -133,7 +133,7 @@ function testImageDimensions() { 'weight' => 3, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -146,12 +146,12 @@ function testImageDimensions() { // Add a desaturate effect. $effect = array( - 'name' => 'image_desaturate', + 'id' => 'image_desaturate', 'data' => array(), 'weight' => 4, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -164,7 +164,7 @@ function testImageDimensions() { // Add a random rotate effect. $effect = array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => 180, 'random' => TRUE, @@ -172,7 +172,7 @@ function testImageDimensions() { 'weight' => 5, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -183,7 +183,7 @@ function testImageDimensions() { // Add a crop effect. $effect = array( - 'name' => 'image_crop', + 'id' => 'image_crop', 'data' => array( 'width' => 30, 'height' => 30, @@ -192,7 +192,7 @@ function testImageDimensions() { 'weight' => 6, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -205,7 +205,7 @@ function testImageDimensions() { // Rotate to a non-multiple of 90 degrees. $effect = array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => 57, 'random' => FALSE, @@ -213,7 +213,7 @@ function testImageDimensions() { 'weight' => 7, ); - image_effect_save($style, $effect); + $effect_id = $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.'); @@ -221,17 +221,18 @@ function testImageDimensions() { $this->assertResponse(200, 'Image was generated at the URL.'); $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.'); - image_effect_delete($style, $effect); + $effect_plugin = $style->getEffect($effect_id); + $style->deleteImageEffect($effect_plugin); // Ensure that an effect with no dimensions callback unsets the dimensions. // This ensures compatibility with 7.0 contrib modules. $effect = array( - 'name' => 'image_module_test_null', + 'id' => 'image_module_test_null', 'data' => array(), 'weight' => 8, ); - image_effect_save($style, $effect); + $style->saveImageEffect($effect); $img_tag = theme_image_style($variables); $this->assertEqual($img_tag, ''); } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php index 7f24796..c3c3c47 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php @@ -22,6 +22,13 @@ class ImageEffectsTest extends ToolkitTestBase { */ public static $modules = array('image', 'image_test', 'image_module_test'); + /** + * The image effect manager. + * + * @var \Drupal\image\ImageEffectManager + */ + protected $manager; + public static function getInfo() { return array( 'name' => 'Image effects', @@ -30,17 +37,19 @@ public static function getInfo() { ); } - function setUp() { + public function setUp() { parent::setUp(); - - module_load_include('inc', 'image', 'image.effects'); + $this->manager = $this->container->get('plugin.manager.image.effect'); } /** * Test the image_resize_effect() function. */ function testResizeEffect() { - $this->assertTrue(image_resize_effect($this->image, array('width' => 1, 'height' => 2)), 'Function returned the expected value.'); + $this->assertImageEffect('image_resize', array( + 'width' => 1, + 'height' => 2, + )); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. @@ -54,7 +63,10 @@ function testResizeEffect() { */ function testScaleEffect() { // @todo: need to test upscaling. - $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 10)), 'Function returned the expected value.'); + $this->assertImageEffect('image_scale', array( + 'width' => 10, + 'height' => 10, + )); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. @@ -68,7 +80,11 @@ function testScaleEffect() { */ function testCropEffect() { // @todo should test the keyword offsets. - $this->assertTrue(image_crop_effect($this->image, array('anchor' => 'top-1', 'width' => 3, 'height' => 4)), 'Function returned the expected value.'); + $this->assertImageEffect('image_crop', array( + 'anchor' => 'top-1', + 'width' => 3, + 'height' => 4, + )); $this->assertToolkitOperationsCalled(array('crop')); // Check the parameters. @@ -83,7 +99,10 @@ function testCropEffect() { * Test the image_scale_and_crop_effect() function. */ function testScaleAndCropEffect() { - $this->assertTrue(image_scale_and_crop_effect($this->image, array('width' => 5, 'height' => 10)), 'Function returned the expected value.'); + $this->assertImageEffect('image_scale_and_crop', array( + 'width' => 5, + 'height' => 10, + )); $this->assertToolkitOperationsCalled(array('resize', 'crop')); // Check the parameters. @@ -98,7 +117,7 @@ function testScaleAndCropEffect() { * Test the image_desaturate_effect() function. */ function testDesaturateEffect() { - $this->assertTrue(image_desaturate_effect($this->image, array()), 'Function returned the expected value.'); + $this->assertImageEffect('image_desaturate', array()); $this->assertToolkitOperationsCalled(array('desaturate')); // Check the parameters. @@ -111,7 +130,10 @@ function testDesaturateEffect() { */ function testRotateEffect() { // @todo: need to test with 'random' => TRUE - $this->assertTrue(image_rotate_effect($this->image, array('degrees' => 90, 'bgcolor' => '#fff')), 'Function returned the expected value.'); + $this->assertImageEffect('image_rotate', array( + 'degrees' => 90, + 'bgcolor' => '#fff', + )); $this->assertToolkitOperationsCalled(array('rotate')); // Check the parameters. @@ -127,15 +149,32 @@ function testImageEffectsCaching() { $image_effect_definitions_called = &drupal_static('image_module_test_image_effect_info_alter'); // First call should grab a fresh copy of the data. - $effects = image_effect_definitions(); + $manager = $this->container->get('plugin.manager.image.effect'); + $effects = $manager->getDefinitions(); $this->assertTrue($image_effect_definitions_called === 1, 'image_effect_definitions() generated data.'); // Second call should come from cache. - drupal_static_reset('image_effect_definitions'); drupal_static_reset('image_module_test_image_effect_info_alter'); - $cached_effects = image_effect_definitions(); - $this->assertTrue(is_null($image_effect_definitions_called), 'image_effect_definitions() returned data from cache.'); + $cached_effects = $manager->getDefinitions(); + $this->assertTrue($image_effect_definitions_called === 0, 'image_effect_definitions() returned data from cache.'); $this->assertTrue($effects == $cached_effects, 'Cached effects are the same as generated effects.'); } + + /** + * Asserts the effect processing of an image effect plugin. + * + * @param string $effect_name + * The name of the image effect to test. + * @param array $data + * The data to pass to the image effect. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertImageEffect($effect_name, array $data) { + $effect = $this->manager->createInstance($effect_name, array('data' => $data)); + return $this->assertTrue($effect->processEffect($this->image), 'Function returned the expected value.'); + } + } diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php index 3b9be9f..47f7091 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php @@ -19,11 +19,6 @@ * * image.module: * image_style_options() - * image_style_flush() - * image_effect_definition_load() - * image_effect_load() - * image_effect_save() - * image_effect_delete() * image_filter_keyword() */ diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php index 298819a..67cf051 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php @@ -100,11 +100,11 @@ function testFlush() { // Remove the 'image_scale' effect and updates the style, which in turn // forces an image style flush. $style_path = 'admin/config/media/image-styles/manage/' . $style->id(); - $ieids = array(); - foreach ($style->effects as $ieid => $effect) { - $ieids[$effect['name']] = $ieid; + $uuids = array(); + foreach ($style->getEffects() as $uuid => $effect) { + $uuids[$effect->getPluginId()] = $uuid; } - $this->drupalPost($style_path . '/effects/' . $ieids['image_scale'] . '/delete', array(), t('Delete')); + $this->drupalPost($style_path . '/effects/' . $uuids['image_scale'] . '/delete', array(), t('Delete')); $this->assertResponse(200); $this->drupalPost($style_path, array(), t('Update style')); $this->assertResponse(200); diff --git a/core/modules/image/tests/image_module_test.module b/core/modules/image/tests/image_module_test.module deleted file mode 100644 index 7cc6cdd..0000000 --- a/core/modules/image/tests/image_module_test.module +++ /dev/null @@ -1,51 +0,0 @@ -get('image.test_file_download') ?: FALSE; - if ($default_uri == $uri) { - return array('X-Image-Owned-By' => 'image_module_test'); - } -} - -/** - * Implements hook_image_effect_info(). - */ -function image_module_test_image_effect_info() { - $effects = array( - 'image_module_test_null' => array( - 'effect callback' => 'image_module_test_null_effect', - ), - ); - - return $effects; -} - -/** - * Image effect callback; Null. - * - * @param $image - * An image object returned by image_load(). - * @param $data - * An array with no attributes. - * - * @return - * TRUE - */ -function image_module_test_null_effect(array &$image, array $data) { - return TRUE; -} - -/** - * Implements hook_image_effect_info_alter(). - * - * Used to keep a count of cache misses in image_effect_definitions(). - */ -function image_module_test_image_effect_info_alter(&$effects) { - $image_effects_definition_called = &drupal_static(__FUNCTION__, 0); - $image_effects_definition_called++; -} diff --git a/core/modules/image/tests/image_module_test.info.yml b/core/modules/image/tests/modules/image_module_test/image_module_test.info.yml similarity index 100% rename from core/modules/image/tests/image_module_test.info.yml rename to core/modules/image/tests/modules/image_module_test/image_module_test.info.yml diff --git a/core/modules/image/tests/modules/image_module_test/image_module_test.module b/core/modules/image/tests/modules/image_module_test/image_module_test.module new file mode 100644 index 0000000..7d64d2c --- /dev/null +++ b/core/modules/image/tests/modules/image_module_test/image_module_test.module @@ -0,0 +1,23 @@ +get('image.test_file_download') ?: FALSE; + if ($default_uri == $uri) { + return array('X-Image-Owned-By' => 'image_module_test'); + } +} + +/** + * Implements hook_image_effect_info_alter(). + * + * Used to keep a count of cache misses in \Drupal\image\ImageEffectManager. + */ +function image_module_test_image_effect_info_alter(&$effects) { + $image_effects_definition_called = &drupal_static(__FUNCTION__, 0); + $image_effects_definition_called++; +} diff --git a/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php new file mode 100644 index 0000000..c5a9ad4 --- /dev/null +++ b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php @@ -0,0 +1,33 @@ +createTmp($image, $width, $height); - if (!imagecopyresampled($res, $image->resource, 0, 0, 0, 0, $width, $height, $image->info['width'], $image->info['height'])) { + if (!imagecopyresampled($res, $image->getResource(), 0, 0, 0, 0, $width, $height, $image->get('width'), $image->get('height'))) { return FALSE; } - imagedestroy($image->resource); + imagedestroy($image->getResource()); // Update image object. - $image->resource = $res; - $image->info['width'] = $width; - $image->info['height'] = $height; + $image->setResource($res); + $image->set('width', $width); + $image->set('height', $height); return TRUE; } /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate(). */ - public function rotate($image, $degrees, $background = NULL) { + public function rotate(ImageFile $image, $degrees, $background = NULL) { // PHP installations using non-bundled GD do not have imagerotate. if (!function_exists('imagerotate')) { - watchdog('image', 'The image %file could not be rotated because the imagerotate() function is not available in this PHP installation.', array('%file' => $image->source)); + watchdog('image', 'The image %file could not be rotated because the imagerotate() function is not available in this PHP installation.', array('%file' => $image->getSource())); return FALSE; } - $width = $image->info['width']; - $height = $image->info['height']; + $width = $image->get('width'); + $height = $image->get('height'); // Convert the hexadecimal background value to a color index value. if (isset($background)) { @@ -84,88 +85,89 @@ public function rotate($image, $degrees, $background = NULL) { for ($i = 16; $i >= 0; $i -= 8) { $rgb[] = (($background >> $i) & 0xFF); } - $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0); + $background = imagecolorallocatealpha($image->getResource(), $rgb[0], $rgb[1], $rgb[2], 0); } // Set the background color as transparent if $background is NULL. else { // Get the current transparent color. - $background = imagecolortransparent($image->resource); + $background = imagecolortransparent($image->getResource()); // If no transparent colors, use white. if ($background == 0) { - $background = imagecolorallocatealpha($image->resource, 255, 255, 255, 0); + $background = imagecolorallocatealpha($image->getResource(), 255, 255, 255, 0); } } // Images are assigned a new color palette when rotating, removing any // transparency flags. For GIF images, keep a record of the transparent color. - if ($image->info['extension'] == 'gif') { - $transparent_index = imagecolortransparent($image->resource); + if ($image->get('extension') == 'gif') { + $transparent_index = imagecolortransparent($image->getResource()); if ($transparent_index != 0) { - $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index); + $transparent_gif_color = imagecolorsforindex($image->getResource(), $transparent_index); } } - $image->resource = imagerotate($image->resource, 360 - $degrees, $background); + $image->setResource(imagerotate($image->getResource(), 360 - $degrees, $background)); // GIFs need to reassign the transparent color after performing the rotate. if (isset($transparent_gif_color)) { - $background = imagecolorexactalpha($image->resource, $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']); - imagecolortransparent($image->resource, $background); + $background = imagecolorexactalpha($image->getResource(), $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']); + imagecolortransparent($image->getResource(), $background); } - $image->info['width'] = imagesx($image->resource); - $image->info['height'] = imagesy($image->resource); + $image->set('width', imagesx($image->getResource())); + $image->set('height', imagesy($image->getResource())); return TRUE; } /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop(). */ - public function crop($image, $x, $y, $width, $height) { + public function crop(ImageFile $image, $x, $y, $width, $height) { $res = $this->createTmp($image, $width, $height); - if (!imagecopyresampled($res, $image->resource, 0, 0, $x, $y, $width, $height, $width, $height)) { + if (!imagecopyresampled($res, $image->getResource(), 0, 0, $x, $y, $width, $height, $width, $height)) { return FALSE; } // Destroy the original image and return the modified image. - imagedestroy($image->resource); - $image->resource = $res; - $image->info['width'] = $width; - $image->info['height'] = $height; + imagedestroy($image->getResource()); + $image->setResource($res); + $image->set('width', $width); + $image->set('height', $height); return TRUE; } /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::desaturate(). */ - public function desaturate($image) { + public function desaturate(ImageFile $image) { // PHP installations using non-bundled GD do not have imagefilter. if (!function_exists('imagefilter')) { - watchdog('image', 'The image %file could not be desaturated because the imagefilter() function is not available in this PHP installation.', array('%file' => $image->source)); + watchdog('image', 'The image %file could not be desaturated because the imagefilter() function is not available in this PHP installation.', array('%file' => $image->getSource())); return FALSE; } - return imagefilter($image->resource, IMG_FILTER_GRAYSCALE); + return imagefilter($image->getResource(), IMG_FILTER_GRAYSCALE); } /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::load(). */ - public function load($image) { - $extension = str_replace('jpg', 'jpeg', $image->info['extension']); + public function load(ImageFile $image) { + $extension = str_replace('jpg', 'jpeg', $image->get('extension')); $function = 'imagecreatefrom' . $extension; - if (function_exists($function) && $image->resource = $function($image->source)) { - if (!imageistruecolor($image->resource)) { + if (function_exists($function) && $resource = $function($image->getSource())) { + $image->setResource($resource); + if (!imageistruecolor($resource)) { // Convert indexed images to true color, so that filters work // correctly and don't result in unnecessary dither. - $new_image = $this->createTmp($image, $image->info['width'], $image->info['height']); - imagecopy($new_image, $image->resource, 0, 0, 0, 0, $image->info['width'], $image->info['height']); - imagedestroy($image->resource); - $image->resource = $new_image; + $new_image = $this->createTmp($image, $image->get('width'), $image->get('height')); + imagecopy($new_image, $resource, 0, 0, 0, 0, $image->get('width'), $image->get('height')); + imagedestroy($resource); + $image->setResource($new_image); } - return (bool) $image->resource; + return (bool) $image->getResource(); } return FALSE; @@ -174,7 +176,7 @@ public function load($image) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::save(). */ - public function save($image, $destination) { + public function save(ImageFile $image, $destination) { $scheme = file_uri_scheme($destination); // Work around lack of stream wrapper support in imagejpeg() and imagepng(). if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { @@ -188,21 +190,21 @@ public function save($image, $destination) { $destination = drupal_realpath($destination); } - $extension = str_replace('jpg', 'jpeg', $image->info['extension']); + $extension = str_replace('jpg', 'jpeg', $image->get('extension')); $function = 'image' . $extension; if (!function_exists($function)) { return FALSE; } if ($extension == 'jpeg') { - $success = $function($image->resource, $destination, config('system.image.gd')->get('jpeg_quality')); + $success = $function($image->getResource(), $destination, config('system.image.gd')->get('jpeg_quality')); } else { // Always save PNG images with full transparency. if ($extension == 'png') { - imagealphablending($image->resource, FALSE); - imagesavealpha($image->resource, TRUE); + imagealphablending($image->getResource(), FALSE); + imagesavealpha($image->getResource(), TRUE); } - $success = $function($image->resource, $destination); + $success = $function($image->getResource(), $destination); } // Move temporary local file to remote destination. if (isset($permanent_destination) && $success) { @@ -214,9 +216,9 @@ public function save($image, $destination) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::getInfo(). */ - public function getInfo($image) { + public function getInfo(ImageFile $image) { $details = FALSE; - $data = getimagesize($image->source); + $data = getimagesize($image->getSource()); if (isset($data) && is_array($data)) { $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png'); @@ -248,13 +250,13 @@ public function getInfo($image) { public function createTmp($image, $width, $height) { $res = imagecreatetruecolor($width, $height); - if ($image->info['extension'] == 'gif') { + if ($image->get('extension') == 'gif') { // Grab transparent color index from image resource. - $transparent = imagecolortransparent($image->resource); + $transparent = imagecolortransparent($image->getResource()); if ($transparent >= 0) { // The original must have a transparent color, allocate to the new image. - $transparent_color = imagecolorsforindex($image->resource, $transparent); + $transparent_color = imagecolorsforindex($image->getResource(), $transparent); $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); // Flood with our new transparent color. @@ -262,7 +264,7 @@ public function createTmp($image, $width, $height) { imagecolortransparent($res, $transparent); } } - elseif ($image->info['extension'] == 'png') { + elseif ($image->get('extension') == 'png') { imagealphablending($res, FALSE); $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127); imagefill($res, 0, 0, $transparency); diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php index 0b22f3c..90bd15f 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php +++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php @@ -7,13 +7,16 @@ namespace Drupal\system\Plugin; +use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Image\ImageFile; + /** * Defines an interface for image toolkits. * * An image toolkit provides common image file manipulations like scaling, * cropping, and rotating. */ -interface ImageToolkitInterface { +interface ImageToolkitInterface extends PluginInspectionInterface { /** * Retrieves toolkit's settings form. @@ -32,7 +35,7 @@ function settingsFormSubmit($form, &$form_state); /** * Scales an image to the specified size. * - * @param object $image + * @param \Drupal\Core\Image\ImageFile $image * An image object. The $image->resource, $image->info['width'], and * $image->info['height'] values will be modified by this call. * @param int $width @@ -42,15 +45,13 @@ function settingsFormSubmit($form, &$form_state); * * @return bool * TRUE or FALSE, based on success. - * - * @see image_resize() */ - function resize($image, $width, $height); + function resize(ImageFile $image, $width, $height); /** * Rotates an image the given number of degrees. * - * @param object $image + * @param \Drupal\Core\Image\ImageFile $image * An image object. The $image->resource, $image->info['width'], and * $image->info['height'] values will be modified by this call. * @param int $degrees @@ -64,15 +65,13 @@ function resize($image, $width, $height); * * @return bool * TRUE or FALSE, based on success. - * - * @see image_rotate() */ - function rotate($image, $degrees, $background = NULL); + function rotate(ImageFile $image, $degrees, $background = NULL); /** * Crops an image. * - * @param object $image + * @param \Drupal\Core\Image\ImageFile $image * An image object. The $image->resource, $image->info['width'], and * $image->info['height'] values will be modified by this call. * @param int $x @@ -89,56 +88,50 @@ function rotate($image, $degrees, $background = NULL); * * @see image_crop() */ - function crop($image, $x, $y, $width, $height); + function crop(ImageFile $image, $x, $y, $width, $height); /** * Converts an image resource to grayscale. * * Note that transparent GIFs loose transparency when desaturated. * - * @param object $image + * @param \Drupal\Core\Image\ImageFile $image * An image object. The $image->resource value will be modified by this * call. * * @return bool * TRUE or FALSE, based on success. - * - * @see image_desaturate() */ - function desaturate($image); + function desaturate(ImageFile $image); /** * Creates an image resource from a file. * - * @param object $image + * @param \Drupal\Core\Image\ImageFile $image * An image object. The $image->resource value will populated by this call. * * @return bool * TRUE or FALSE, based on success. - * - * @see image_load() */ - function load($image); + function load(ImageFile $image); /** * Writes an image resource to a destination file. * - * @param object $image + * @param \Drupal\Core\Image\ImageFile $image * An image object. * @param string $destination * A string file URI or path where the image should be saved. * * @return bool * TRUE or FALSE, based on success. - * - * @see image_save() */ - function save($image, $destination); + function save(ImageFile $image, $destination); /** * Gets details about an image. * - * @param object $image + * @param \Drupal\Core\Image\ImageFile $image * An image object. * * @return array @@ -151,7 +144,7 @@ function save($image, $destination); * * @see image_get_info() */ - function getInfo($image); + function getInfo(ImageFile $image); /** * Verifies Image Toolkit is set up correctly. diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php index 624dc79..fb03c41 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Image; +use Drupal\Core\Image\ImageFile; use Drupal\simpletest\DrupalUnitTestBase; use Drupal\system\Plugin\ImageToolkitManager; @@ -79,15 +80,15 @@ function colorsAreEqual($color_a, $color_b) { /** * Function for finding a pixel's RGBa values. */ - function getPixelColor($image, $x, $y) { - $color_index = imagecolorat($image->resource, $x, $y); + function getPixelColor(ImageFile $image, $x, $y) { + $color_index = imagecolorat($image->getResource(), $x, $y); - $transparent_index = imagecolortransparent($image->resource); + $transparent_index = imagecolortransparent($image->getResource()); if ($color_index == $transparent_index) { return array(0, 0, 0, 127); } - return array_values(imagecolorsforindex($image->resource, $color_index)); + return array_values(imagecolorsforindex($image->getResource(), $color_index)); } /** @@ -110,49 +111,49 @@ function testManipulations() { // Setup a list of tests to perform on each type. $operations = array( 'resize' => array( - 'function' => 'resize', + 'method' => 'resize', 'arguments' => array(20, 10), 'width' => 20, 'height' => 10, 'corners' => $default_corners, ), 'scale_x' => array( - 'function' => 'scale', + 'method' => 'scale', 'arguments' => array(20, NULL), 'width' => 20, 'height' => 10, 'corners' => $default_corners, ), 'scale_y' => array( - 'function' => 'scale', + 'method' => 'scale', 'arguments' => array(NULL, 10), 'width' => 20, 'height' => 10, 'corners' => $default_corners, ), 'upscale_x' => array( - 'function' => 'scale', + 'method' => 'scale', 'arguments' => array(80, NULL, TRUE), 'width' => 80, 'height' => 40, 'corners' => $default_corners, ), 'upscale_y' => array( - 'function' => 'scale', + 'method' => 'scale', 'arguments' => array(NULL, 40, TRUE), 'width' => 80, 'height' => 40, 'corners' => $default_corners, ), 'crop' => array( - 'function' => 'crop', + 'method' => 'crop', 'arguments' => array(12, 4, 16, 12), 'width' => 16, 'height' => 12, 'corners' => array_fill(0, 4, $this->white), ), 'scale_and_crop' => array( - 'function' => 'scale_and_crop', + 'method' => 'scaleAndCrop', 'arguments' => array(10, 8), 'width' => 10, 'height' => 8, @@ -164,28 +165,28 @@ function testManipulations() { if (function_exists('imagerotate')) { $operations += array( 'rotate_5' => array( - 'function' => 'rotate', + 'method' => 'rotate', 'arguments' => array(5, 0xFF00FF), // Fuchsia background. 'width' => 42, 'height' => 24, 'corners' => array_fill(0, 4, $this->fuchsia), ), 'rotate_90' => array( - 'function' => 'rotate', + 'method' => 'rotate', 'arguments' => array(90, 0xFF00FF), // Fuchsia background. 'width' => 20, 'height' => 40, 'corners' => array($this->transparent, $this->red, $this->green, $this->blue), ), 'rotate_transparent_5' => array( - 'function' => 'rotate', + 'method' => 'rotate', 'arguments' => array(5), 'width' => 42, 'height' => 24, 'corners' => array_fill(0, 4, $this->transparent), ), 'rotate_transparent_90' => array( - 'function' => 'rotate', + 'method' => 'rotate', 'arguments' => array(90), 'width' => 20, 'height' => 40, @@ -198,7 +199,7 @@ function testManipulations() { if (function_exists('imagefilter')) { $operations += array( 'desaturate' => array( - 'function' => 'desaturate', + 'method' => 'desaturate', 'arguments' => array(), 'height' => 20, 'width' => 40, @@ -219,17 +220,17 @@ function testManipulations() { foreach ($files as $file) { foreach ($operations as $op => $values) { // Load up a fresh image. - $image = image_load(drupal_get_path('module', 'simpletest') . '/files/' . $file, $manager->createInstance('gd')); + $image = new ImageFile(drupal_get_path('module', 'simpletest') . '/files/' . $file, $manager->createInstance('gd')); if (!$image) { $this->fail(t('Could not load image %file.', array('%file' => $file))); continue 2; } // All images should be converted to truecolor when loaded. - $image_truecolor = imageistruecolor($image->resource); + $image_truecolor = imageistruecolor($image->getResource()); $this->assertTrue($image_truecolor, format_string('Image %file after load is a truecolor image.', array('%file' => $file))); - if ($image->info['extension'] == 'gif') { + if ($image->get('extension') == 'gif') { if ($op == 'desaturate') { // Transparent GIFs and the imagefilter function don't work together. $values['corners'][3][3] = 0; @@ -237,11 +238,7 @@ function testManipulations() { } // Perform our operation. - $function = 'image_' . $values['function']; - $arguments = array(); - $arguments[] = &$image; - $arguments = array_merge($arguments, $values['arguments']); - call_user_func_array($function, $arguments); + call_user_func_array(array($image, $values['method']), $values['arguments']); // To keep from flooding the test with assert values, make a general // value for whether each group of values fail. @@ -250,24 +247,24 @@ function testManipulations() { $correct_colors = TRUE; // Check the real dimensions of the image first. - if (imagesy($image->resource) != $values['height'] || imagesx($image->resource) != $values['width']) { + if (imagesy($image->getResource()) != $values['height'] || imagesx($image->getResource()) != $values['width']) { $correct_dimensions_real = FALSE; } // Check that the image object has an accurate record of the dimensions. - if ($image->info['width'] != $values['width'] || $image->info['height'] != $values['height']) { + if ($image->get('width') != $values['width'] || $image->get('height') != $values['height']) { $correct_dimensions_object = FALSE; } $directory = $this->public_files_directory .'/imagetest'; file_prepare_directory($directory, FILE_CREATE_DIRECTORY); - image_save($image, $directory . '/' . $op . '.' . $image->info['extension']); + $image->save($directory . '/' . $op . '.' . $image->get('extension')); $this->assertTrue($correct_dimensions_real, format_string('Image %file after %action action has proper dimensions.', array('%file' => $file, '%action' => $op))); $this->assertTrue($correct_dimensions_object, format_string('Image %file object after %action action is reporting the proper height and width values.', array('%file' => $file, '%action' => $op))); // JPEG colors will always be messed up due to compression. - if ($image->info['extension'] != 'jpg') { + if ($image->get('extension') != 'jpg') { // Now check each of the corners to ensure color correctness. foreach ($values['corners'] as $key => $corner) { // Get the location of the corner. diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php index 24e10b0..0713e3b 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Image; +use Drupal\Core\Image\ImageFile; use Drupal\system\Plugin\ImageToolkitManager; /** @@ -37,9 +38,10 @@ function testGetAvailableToolkits() { * Test the image_load() function. */ function testLoad() { - $image = image_load($this->file, $this->toolkit); + $image = new ImageFile($this->file); + $image->setToolkit($this->toolkit)->getResource(); $this->assertTrue(is_object($image), 'Returned an object.'); - $this->assertEqual($this->toolkit, $image->toolkit, t('Image had toolkit set.')); + $this->assertEqual($this->toolkit->getPluginId(), $image->getToolkitId(), t('Image had toolkit set.')); $this->assertToolkitOperationsCalled(array('load', 'get_info')); } @@ -47,7 +49,7 @@ function testLoad() { * Test the image_save() function. */ function testSave() { - $this->assertFalse(image_save($this->image), 'Function returned the expected value.'); + $this->assertFalse($this->image->save(), 'Function returned the expected value.'); $this->assertToolkitOperationsCalled(array('save')); } @@ -55,7 +57,7 @@ function testSave() { * Test the image_resize() function. */ function testResize() { - $this->assertTrue(image_resize($this->image, 1, 2), 'Function returned the expected value.'); + $this->assertTrue($this->image->resize(1, 2), 'Function returned the expected value.'); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. @@ -69,7 +71,7 @@ function testResize() { */ function testScale() { // TODO: need to test upscaling - $this->assertTrue(image_scale($this->image, 10, 10), 'Function returned the expected value.'); + $this->assertTrue($this->image->scale(10, 10), 'Function returned the expected value.'); $this->assertToolkitOperationsCalled(array('resize')); // Check the parameters. @@ -82,7 +84,7 @@ function testScale() { * Test the image_scale_and_crop() function. */ function testScaleAndCrop() { - $this->assertTrue(image_scale_and_crop($this->image, 5, 10), 'Function returned the expected value.'); + $this->assertTrue($this->image->scaleAndCrop(5, 10), 'Function returned the expected value.'); $this->assertToolkitOperationsCalled(array('resize', 'crop')); // Check the parameters. @@ -98,7 +100,7 @@ function testScaleAndCrop() { * Test the image_rotate() function. */ function testRotate() { - $this->assertTrue(image_rotate($this->image, 90, 1), 'Function returned the expected value.'); + $this->assertTrue($this->image->rotate(90, 1), 'Function returned the expected value.'); $this->assertToolkitOperationsCalled(array('rotate')); // Check the parameters. @@ -111,7 +113,7 @@ function testRotate() { * Test the image_crop() function. */ function testCrop() { - $this->assertTrue(image_crop($this->image, 1, 2, 3, 4), 'Function returned the expected value.'); + $this->assertTrue($this->image->crop(1, 2, 3, 4), 'Function returned the expected value.'); $this->assertToolkitOperationsCalled(array('crop')); // Check the parameters. @@ -126,7 +128,7 @@ function testCrop() { * Test the image_desaturate() function. */ function testDesaturate() { - $this->assertTrue(image_desaturate($this->image), 'Function returned the expected value.'); + $this->assertTrue($this->image->desaturate(), 'Function returned the expected value.'); $this->assertToolkitOperationsCalled(array('desaturate')); // Check the parameters. diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php index 80b1588..f2f8c39 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Image; +use Drupal\Core\Image\ImageFile; use Drupal\simpletest\WebTestBase; use Drupal\system\Plugin\ImageToolkitManager; use stdClass; @@ -23,8 +24,19 @@ */ public static $modules = array('image_test'); + /** + * @var \Drupal\system\Plugin\ImageToolkitInterface + */ protected $toolkit; + + /** + * @var string + */ protected $file; + + /** + * @var \Drupal\Core\Image\ImageFile + */ protected $image; function setUp() { @@ -40,10 +52,9 @@ function setUp() { // Setup a dummy image to work with, this replicate image_load() so we // can avoid calling it. - $this->image = new stdClass(); - $this->image->source = $this->file; - $this->image->info = image_get_info($this->file); - $this->image->toolkit = $this->toolkit; + $this->image = new ImageFile($this->file); + $this->image->setToolkit($this->toolkit); + $this->image->setInfo(image_get_info($this->file)); // Clear out any hook calls. $this->imageTestReset(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php index fc24813..cc5979a 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php @@ -38,7 +38,7 @@ public function testImageStyleUpgrade() { 'name' => 'test-custom', 'effects' => array( 'image_rotate' => array( - 'name' => 'image_rotate', + 'id' => 'image_rotate', 'data' => array( 'degrees' => '90', 'bgcolor' => '#FFFFFF', @@ -47,7 +47,7 @@ public function testImageStyleUpgrade() { 'weight' => '1', ), 'image_desaturate' => array( - 'name' => 'image_desaturate', + 'id' => 'image_desaturate', 'data' => array(), 'weight' => '2', ), @@ -57,7 +57,7 @@ public function testImageStyleUpgrade() { 'name' => 'thumbnail', 'effects' => array ( 'image_scale' => array( - 'name' => 'image_scale', + 'id' => 'image_scale', 'data' => array ( 'width' => '177', 'height' => '177', @@ -73,11 +73,11 @@ public function testImageStyleUpgrade() { // during by the image style upgrade functions. foreach ($config->get('effects') as $uuid => $effect) { // Copy placeholder data. - $style['effects'][$uuid] = $style['effects'][$effect['name']]; - // Set the missing ieid key as this is unknown because it is a UUID. - $style['effects'][$uuid]['ieid'] = $uuid; + $style['effects'][$uuid] = $style['effects'][$effect['id']]; + // Set the missing uuid key as this is unknown because it is a UUID. + $style['effects'][$uuid]['uuid'] = $uuid; // Remove the placeholder data. - unset($style['effects'][$effect['name']]); + unset($style['effects'][$effect['id']]); } $this->assertEqual($this->sortByKey($style), $config->get(), format_string('@first is equal to @second.', array( '@first' => var_export($this->sortByKey($style), TRUE), diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php index 12ebae7..8203b18 100644 --- a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php +++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php @@ -10,6 +10,7 @@ use Drupal\Component\Plugin\PluginBase; use Drupal\Component\Annotation\Plugin; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Image\ImageFile; use Drupal\system\Plugin\ImageToolkitInterface; /** @@ -38,7 +39,7 @@ public function settingsFormSubmit($form, &$form_state) {} /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::getInfo(). */ - public function getInfo($image) { + public function getInfo(ImageFile $image) { $this->logCall('get_info', array($image)); return array(); } @@ -46,7 +47,7 @@ public function getInfo($image) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::load(). */ - public function load($image) { + public function load(ImageFile $image) { $this->logCall('load', array($image)); return $image; } @@ -54,7 +55,7 @@ public function load($image) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::save(). */ - public function save($image, $destination) { + public function save(ImageFile $image, $destination) { $this->logCall('save', array($image, $destination)); // Return false so that image_save() doesn't try to chmod the destination // file that we didn't bother to create. @@ -64,7 +65,7 @@ public function save($image, $destination) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop(). */ - public function crop($image, $x, $y, $width, $height) { + public function crop(ImageFile $image, $x, $y, $width, $height) { $this->logCall('crop', array($image, $x, $y, $width, $height)); return TRUE; } @@ -72,7 +73,7 @@ public function crop($image, $x, $y, $width, $height) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::resize(). */ - public function resize($image, $width, $height) { + public function resize(ImageFile $image, $width, $height) { $this->logCall('resize', array($image, $width, $height)); return TRUE; } @@ -80,7 +81,7 @@ public function resize($image, $width, $height) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate(). */ - public function rotate($image, $degrees, $background = NULL) { + public function rotate(ImageFile $image, $degrees, $background = NULL) { $this->logCall('rotate', array($image, $degrees, $background)); return TRUE; } @@ -88,7 +89,7 @@ public function rotate($image, $degrees, $background = NULL) { /** * Implements \Drupal\system\Plugin\ImageToolkitInterface::desaturate(). */ - public function desaturate($image) { + public function desaturate(ImageFile $image) { $this->logCall('desaturate', array($image)); return TRUE; }