diff --git a/core/lib/Drupal/Core/Image/Image.php b/core/lib/Drupal/Core/Image/Image.php index 42cdc92..585a1e6 100644 --- a/core/lib/Drupal/Core/Image/Image.php +++ b/core/lib/Drupal/Core/Image/Image.php @@ -188,8 +188,8 @@ public function rotate($degrees, $background = NULL) { /** * {@inheritdoc} */ - public function scaleAndCrop($width, $height) { - return $this->apply('scale_and_crop', array('width' => $width, 'height' => $height)); + public function scaleAndCrop($width, $height, $upscale = FALSE) { + return $this->apply('scale_and_crop', ['width' => $width, 'height' => $height, 'upscale' => $upscale]); } /** diff --git a/core/lib/Drupal/Core/Image/ImageInterface.php b/core/lib/Drupal/Core/Image/ImageInterface.php index 9e112c6..ab8cad8 100644 --- a/core/lib/Drupal/Core/Image/ImageInterface.php +++ b/core/lib/Drupal/Core/Image/ImageInterface.php @@ -140,10 +140,11 @@ public function createNew($width, $height, $extension = 'png', $transparent_colo * be based only on the height value. * @param int|null $height * (optional) The target height, in pixels. If this value is null then the - * scaling will be based only on the width value. + * scaling will be based only on the width value. Defaults to NULL. * @param bool $upscale * (optional) Boolean indicating that files smaller than the dimensions will * be scaled up. This generally results in a low quality image. + * Defaults to FALSE. * * @return bool * TRUE on success, FALSE on failure. @@ -163,11 +164,15 @@ public function scale($width, $height = NULL, $upscale = FALSE); * The target width, in pixels. * @param int $height * The target height, in pixels. + * @param bool $upscale + * (optional) Boolean indicating that files smaller than the dimensions will + * be scaled up. This generally results in a low quality image. + * Defaults to FALSE. * * @return bool * TRUE on success, FALSE on failure. */ - public function scaleAndCrop($width, $height); + public function scaleAndCrop($width, $height, $upscale = FALSE); /** * Instructs the toolkit to save the image in the format specified by the diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml index 0006ad7..0abbb67 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -76,6 +76,10 @@ image.effect.image_desaturate: image.effect.image_scale_and_crop: type: image_size label: 'Image scale and crop' + mapping: + upscale: + type: boolean + label: 'Upscale' image.settings: type: config_object diff --git a/core/modules/image/image.install b/core/modules/image/image.install index fd14d03..11573b9 100644 --- a/core/modules/image/image.install +++ b/core/modules/image/image.install @@ -5,6 +5,8 @@ * Install, update and uninstall functions for the image module. */ +use Drupal\image\Entity\ImageStyle; + /** * Implements hook_install(). */ @@ -61,3 +63,32 @@ function image_requirements($phase) { return $requirements; } + +/** + * @addtogroup updates-8.3.0 + * @{ + */ + +/** + * Add 'upscale' parameters to 'Scale and crop' effects. + */ +function image_update_8301() { + foreach (ImageStyle::loadMultiple() as $image_style) { + $edited = FALSE; + foreach ($image_style->getEffects() as $effect) { + if ($effect->getPluginId() === 'image_scale_and_crop') { + $configuration = $effect->getConfiguration(); + $configuration['data']['upscale'] = FALSE; + $effect->setConfiguration($configuration); + $edited = TRUE; + } + } + if ($edited) { + $image_style->save(); + } + } +} + +/** + * @} End of "addtogroup updates-8.3.0". + */ diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 7f8314a..b9c29e8 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -142,6 +142,9 @@ function image_theme() { 'image_scale_summary' => array( 'variables' => array('data' => NULL, 'effect' => array()), ), + 'image_scale_and_crop_summary' => array( + 'variables' => array('data' => NULL, 'effect' => array()), + ), 'image_crop_summary' => array( 'variables' => array('data' => NULL, 'effect' => array()), ), diff --git a/core/modules/image/src/Plugin/ImageEffect/ScaleAndCropImageEffect.php b/core/modules/image/src/Plugin/ImageEffect/ScaleAndCropImageEffect.php index f2ab323..56dcf70 100644 --- a/core/modules/image/src/Plugin/ImageEffect/ScaleAndCropImageEffect.php +++ b/core/modules/image/src/Plugin/ImageEffect/ScaleAndCropImageEffect.php @@ -2,6 +2,8 @@ namespace Drupal\image\Plugin\ImageEffect; +use Drupal\Component\Utility\Image; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Image\ImageInterface; /** @@ -19,11 +21,84 @@ class ScaleAndCropImageEffect extends ResizeImageEffect { * {@inheritdoc} */ public function applyEffect(ImageInterface $image) { - if (!$image->scaleAndCrop($this->configuration['width'], $this->configuration['height'])) { - $this->logger->error('Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight())); + if (!$image->scaleAndCrop($this->configuration['width'], $this->configuration['height'], $this->configuration['upscale'])) { + $this->logger->error('Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', [ + '%toolkit' => $image->getToolkitId(), + '%path' => $image->getSource(), + '%mimetype' => $image->getMimeType(), + '%dimensions' => $image->getWidth() . 'x' . $image->getHeight(), + ]); return FALSE; } return TRUE; } + /** + * {@inheritdoc} + */ + public function transformDimensions(array &$dimensions, $uri) { + if (isset($dimensions['width'], $dimensions['height'])) { + $is_scaled = Image::scaleDimensions($dimensions, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale']); + + // If image is not scaled save the original dimensions. + if (!$is_scaled) { + return; + } + } + $dimensions['width'] = $this->configuration['width']; + $dimensions['height'] = $this->configuration['height']; + } + + /** + * {@inheritdoc} + */ + public function getSummary() { + return [ + '#theme' => 'image_scale_and_crop_summary', + ] + parent::getSummary(); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + $configuration = parent::defaultConfiguration(); + $configuration['upscale'] = FALSE; + return $configuration; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + $form['width']['#required'] = FALSE; + $form['height']['#required'] = FALSE; + $form['upscale'] = [ + '#type' => 'checkbox', + '#default_value' => $this->configuration['upscale'], + '#title' => $this->t('Allow Upscaling'), + '#description' => $this->t('Let scale make images larger than their original size'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::validateConfigurationForm($form, $form_state); + if ($form_state->isValueEmpty('width') && $form_state->isValueEmpty('height')) { + $form_state->setErrorByName('data', $this->t('Width and height can not both be blank.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + $this->configuration['upscale'] = $form_state->getValue('upscale'); + } + } diff --git a/core/modules/image/src/Tests/ImageAdminStylesTest.php b/core/modules/image/src/Tests/ImageAdminStylesTest.php index 4b7ff43..f97f371 100644 --- a/core/modules/image/src/Tests/ImageAdminStylesTest.php +++ b/core/modules/image/src/Tests/ImageAdminStylesTest.php @@ -80,6 +80,7 @@ function testStyle() { 'image_scale_and_crop' => array( 'width' => 120, 'height' => 121, + 'upscale' => 1, ), 'image_crop' => array( 'width' => 130, diff --git a/core/modules/image/src/Tests/ImageEffectsTest.php b/core/modules/image/src/Tests/ImageEffectsTest.php index 895457f..298f134 100644 --- a/core/modules/image/src/Tests/ImageEffectsTest.php +++ b/core/modules/image/src/Tests/ImageEffectsTest.php @@ -51,10 +51,10 @@ function testResizeEffect() { * Test the image_scale_effect() function. */ function testScaleEffect() { - // @todo: need to test upscaling. $this->assertImageEffect('image_scale', array( 'width' => 10, 'height' => 10, + 'upscale' => 1, )); $this->assertToolkitOperationsCalled(array('scale')); @@ -62,6 +62,7 @@ function testScaleEffect() { $calls = $this->imageTestGetAllCalls(); $this->assertEqual($calls['scale'][0][0], 10, 'Width was passed correctly'); $this->assertEqual($calls['scale'][0][1], 10, 'Height was based off aspect ratio and passed correctly'); + $this->assertTrue($calls['scale'][0][2], 'Upscale was passed correctly'); } /** @@ -106,6 +107,7 @@ function testScaleAndCropEffect() { $this->assertImageEffect('image_scale_and_crop', array( 'width' => 5, 'height' => 10, + 'upscale' => 1, )); $this->assertToolkitOperationsCalled(array('scale_and_crop')); @@ -113,6 +115,7 @@ function testScaleAndCropEffect() { $calls = $this->imageTestGetAllCalls(); $this->assertEqual($calls['scale_and_crop'][0][0], 5, 'Width was computed and passed correctly'); $this->assertEqual($calls['scale_and_crop'][0][1], 10, 'Height was computed and passed correctly'); + $this->assertTrue($calls['scale_and_crop'][0][2], 'Upscale was passed correctly'); } /** diff --git a/core/modules/image/templates/image-scale-and-crop-summary.html.twig b/core/modules/image/templates/image-scale-and-crop-summary.html.twig new file mode 100644 index 0000000..50b4406 --- /dev/null +++ b/core/modules/image/templates/image-scale-and-crop-summary.html.twig @@ -0,0 +1,37 @@ +{# +/** + * @file + * Default theme implementation for a summary of an image scale and crop effect. + * + * Available variables: + * - data: The current configuration for this resize effect, including: + * - width: The width of the resized image. + * - height: The height of the resized image. + * - upscale: If images larger than their original size can scale. + * - effect: The effect information, including: + * - id: The effect identifier. + * - label: The effect name. + * - description: The effect description. + * + * @ingroup themeable + */ +#} +{% if data.width and data.height -%} + {{ data.width|e }}×{{ data.height|e }} +{%- else -%} + {% if data.width %} + {% trans %} + width {{ data.width|e }} + {% endtrans %} + {% elseif data.height %} + {% trans %} + height {{ data.height|e }} + {% endtrans %} + {% endif %} +{%- endif %} + +{% if data.upscale %} + {% trans %} + (upscaling allowed) + {% endtrans %} +{% endif %} diff --git a/core/modules/image/tests/src/Kernel/Migrate/d7/MigrateImageStylesTest.php b/core/modules/image/tests/src/Kernel/Migrate/d7/MigrateImageStylesTest.php index 0903218..bd18a9d 100644 --- a/core/modules/image/tests/src/Kernel/Migrate/d7/MigrateImageStylesTest.php +++ b/core/modules/image/tests/src/Kernel/Migrate/d7/MigrateImageStylesTest.php @@ -32,7 +32,7 @@ protected function setUp() { * Test the image styles migration. */ public function testImageStylesMigration() { - $this->assertEntity('custom_image_style_1', "Custom image style 1", ['image_scale_and_crop', 'image_desaturate'], [['width' => 55, 'height' => 55], []]); + $this->assertEntity('custom_image_style_1', "Custom image style 1", ['image_scale_and_crop', 'image_desaturate'], [['width' => 55, 'height' => 55, 'upscale' => FALSE], []]); $this->assertEntity('custom_image_style_2', "Custom image style 2", ['image_resize', 'image_rotate'], [['width' => 55, 'height' => 100], ['degrees' => 45, 'bgcolor' => '#FFFFFF', 'random' => FALSE]]); $this->assertEntity('custom_image_style_3', "Custom image style 3", ['image_scale', 'image_crop'], [['width' => 150, 'height' => NULL, 'upscale' => FALSE], ['width' => 50, 'height' => 50, 'anchor' => 'left-top']]); } @@ -45,9 +45,9 @@ public function testImageStylesMigration() { * @param string $label * The expected image style label. * @param array $expected_effect_plugins - * An array of expected plugins attached to the image style entity + * An array of expected plugins attached to the image style entity. * @param array $expected_effect_config - * An array of expected configuration for each effect in the image style + * An array of expected configuration for each effect in the image style. */ protected function assertEntity($id, $label, array $expected_effect_plugins, array $expected_effect_config) { $style = ImageStyle::load($id); diff --git a/core/themes/stable/templates/admin/image-scale-and-crop-summary.html.twig b/core/themes/stable/templates/admin/image-scale-and-crop-summary.html.twig new file mode 100644 index 0000000..a5190e2 --- /dev/null +++ b/core/themes/stable/templates/admin/image-scale-and-crop-summary.html.twig @@ -0,0 +1,35 @@ +{# +/** + * @file + * Theme override for a summary of an image scale and crop effect. + * + * Available variables: + * - data: The current configuration for this resize effect, including: + * - width: The width of the resized image. + * - height: The height of the resized image. + * - upscale: If images larger than their original size can scale. + * - effect: The effect information, including: + * - id: The effect identifier. + * - label: The effect name. + * - description: The effect description. + */ +#} +{% if data.width and data.height -%} + {{ data.width|e }}×{{ data.height|e }} +{%- else -%} + {% if data.width %} + {% trans %} + width {{ data.width|e }} + {% endtrans %} + {% elseif data.height %} + {% trans %} + height {{ data.height|e }} + {% endtrans %} + {% endif %} +{%- endif %} + +{% if data.upscale %} + {% trans %} + (upscaling allowed) + {% endtrans %} +{% endif %}