Skip to content

Commit

Permalink
Merge pull request #254 from openeuropa/OPENEUROPA-2025
Browse files Browse the repository at this point in the history
OPENEUROPA-2025: Use custom image effect to prevent retina image reducction.
  • Loading branch information
imanoleguskiza authored Jul 8, 2019
2 parents 678fa60 + 9c88428 commit eb2582f
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 2 deletions.
3 changes: 2 additions & 1 deletion config/install/image.style.oe_theme_medium_2x_no_crop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ label: 'Medium 2x (no crop)'
effects:
e998d44c-2855-435c-a564-e1fa92af8705:
uuid: e998d44c-2855-435c-a564-e1fa92af8705
id: image_scale
id: retina_image_scale
weight: 1
data:
width: 1984
height: null
upscale: false
multiplier: 2
3 changes: 2 additions & 1 deletion config/install/image.style.oe_theme_small_2x_no_crop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ label: 'Small 2x (no crop)'
effects:
19bc771f-1b42-474c-b300-a82b4fa41de9:
uuid: 19bc771f-1b42-474c-b300-a82b4fa41de9
id: image_scale
id: retina_image_scale
weight: 1
data:
width: 1534
height: null
upscale: false
multiplier: 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
image.effect.retina_image_scale:
type: image.effect.image_scale
label: 'Retina Image scale'
mapping:
multiplier:
type: integer
label: 'Multiplier'
43 changes: 43 additions & 0 deletions modules/oe_theme_helper/oe_theme_helper.post_update.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

/**
* @file
* OpenEuropa theme helper post updates.
*/

declare(strict_types = 1);

use Drupal\image\Entity\ImageStyle;

/**
* Use retina image styles on medium and small image styles.
*/
function oe_theme_helper_post_update_use_retina_image_styles(array &$sandbox): void {
\Drupal::service('plugin.manager.image.effect')->clearCachedDefinitions();

$image_styles = [
'oe_theme_medium_2x_no_crop',
'oe_theme_small_2x_no_crop',
];

foreach ($image_styles as $image_style_name) {
$image_style = ImageStyle::load($image_style_name);
$effects = $image_style->getEffects();
/** @var \Drupal\image\ImageEffectInterface $effect */
foreach ($effects as $effect) {
if ($effect->getPluginId() == 'image_scale') {
$image_style->deleteImageEffect($effect);
$configuration = $effect->getConfiguration();
$new_configuration = [
'id' => 'retina_image_scale',
'data' => $configuration['data'],
'weight' => $configuration['weight'],
];
$new_configuration['data']['multiplier'] = 2;
$image_style->addImageEffect($new_configuration);
$image_style->save();
break;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types = 1);

namespace Drupal\oe_theme_helper\Plugin\ImageEffect;

use Drupal\Component\Utility\Image;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Image\ImageInterface;
use Drupal\image\Plugin\ImageEffect\ScaleImageEffect;

/**
* Scales an image and upscales it for retina screens if needed.
*
* @ImageEffect(
* id = "retina_image_scale",
* label = @Translation("Retina Image Scale"),
* description = @Translation("Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated. If the image is smaller than the specified dimensions, it will be upscaled according to the multiplier value.")
* )
*/
class RetinaScaleImageEffect extends ScaleImageEffect {

/**
* {@inheritdoc}
*/
public function applyEffect(ImageInterface $image) {
// If we are not upscaling the image, check to see if it's smaller
// than the defined dimensions.
$upscale = $this->configuration['upscale'];
$target_width = $this->configuration['width'];
$target_height = $this->configuration['height'];
if (!$upscale) {
if ((!empty($target_width) && $target_width > $image->getWidth()) || (!empty($target_height) && $target_height > $image->getHeight())) {
// If the image is smaller than the defined dimensions,
// upscale it according to the defined multiplier.
$target_width = $image->getWidth() * $this->configuration['multiplier'];
$target_height = $image->getHeight() * $this->configuration['multiplier'];
$upscale = TRUE;
}
}

if (!$image->scale($target_width, $target_height, $upscale)) {
$this->logger->error('Image scale 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 (!$dimensions['width'] || !$dimensions['height']) {
return;
}

// If we are not upscaling the image, check to see if it's smaller
// than the defined dimensions.
$upscale = $this->configuration['upscale'];
$target_width = $this->configuration['width'];
$target_height = $this->configuration['height'];

if (!$upscale) {
if ((!empty($target_width) && $this->configuration['width'] > $dimensions['width']) || (!empty($target_height) && $this->configuration['height'] > $dimensions['height'])) {
// If the image is smaller than the defined dimensions,
// upscale it according to the defined multiplier.
$target_width = $dimensions['width'] * $this->configuration['multiplier'];
$target_height = $dimensions['height'] * $this->configuration['multiplier'];
$upscale = TRUE;
}
}

Image::scaleDimensions($dimensions, $target_width, $target_height, $upscale);
}

/**
* {@inheritdoc}
*/
public function getSummary() {
// Since we are extending the image scale effect and not altering it in any
// major way, we use the same theme.
$summary = [
'#theme' => 'image_scale_summary',
'#data' => $this->configuration,
];
$summary += parent::getSummary();

return $summary;
}

/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'multiplier' => 2,
];
}

/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['multiplier'] = [
'#type' => 'select',
'#title' => $this->t('Multiplier'),
'#options' => [
2 => '2x',
3 => '3x',
],
'#default_value' => $this->configuration['multiplier'],
'#required' => TRUE,
'#description' => $this->t('The image will be upscaled (regardless of the value of the "Upscale" option) according to this multiplier if it is smaller than the dimensions defined in the "Width" and "Height" properties.'),
];

return $form;
}

/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
$this->configuration['multiplier'] = $form_state->getValue('multiplier');
}

}
110 changes: 110 additions & 0 deletions modules/oe_theme_helper/tests/src/Functional/RetinaScaleEffectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types = 1);

namespace Drupal\Tests\oe_theme_helper\Functional;

use Drupal\FunctionalTests\Image\ToolkitTestBase;

/**
* Tests that the Retina Scale effect upscales images appropriately.
*
* @group image
*/
class RetinaScaleEffectTest extends ToolkitTestBase {

/**
* {@inheritdoc}
*/
public static $modules = ['image', 'oe_theme_helper'];

/**
* The image effect manager.
*
* @var \Drupal\image\ImageEffectManager
*/
protected $manager;

/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->manager = $this->container->get('plugin.manager.image.effect');
}

/**
* Test the retina scale effect using a big enough image.
*/
public function testRetinaScaleEffect(): void {
$this->assertImageEffect('retina_image_scale', [
// Set the desired width to be smaller than the image width.
'width' => 10,
'height' => 10,
]);
$this->assertToolkitOperationsCalled(['scale']);

$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');
}

/**
* Test the retina scale effect using upscaling.
*/
public function testScaleEffectDefaultUpscaling(): void {
$this->assertImageEffect('retina_image_scale', [
// Set the desired width to be higher than the image width.
'width' => $this->image->getWidth() * 4,
'upscale' => TRUE,
]);
$this->assertToolkitOperationsCalled(['scale']);

$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['scale'][0][0], $this->image->getWidth() * 4, 'Width was passed correctly');
}

/**
* Test the retina scale effect using an image smaller than desired.
*/
public function testRetinaScaleEffectForcedUpscaling(): void {
$this->assertImageEffect('retina_image_scale', [
// Set the desired width to be higher than the image width.
'width' => $this->image->getWidth() * 10,
]);
$this->assertToolkitOperationsCalled(['scale']);

$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['scale'][0][0], $this->image->getWidth() * 2, 'Width is double the original size.');
}

/**
* Test the retina scale effect using a multiplier of 3.
*/
public function testTripleMultiplierRetinaScaleEffect(): void {
$this->assertImageEffect('retina_image_scale', [
// Set the desired width to be higher than the image width.
'width' => $this->image->getWidth() * 10,
'multiplier' => 3,
]);
$this->assertToolkitOperationsCalled(['scale']);

$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['scale'][0][0], $this->image->getWidth() * 3, 'Width is triple the original size.');
}

/**
* 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.
*/
protected function assertImageEffect($effect_name, array $data): void {
/** @var \Drupal\image\ImageEffectInterface $effect */
$effect = $this->manager->createInstance($effect_name, ['data' => $data]);
$this->assertTrue($effect->applyEffect($this->image), 'Function returned the expected value.');
}

}
1 change: 1 addition & 0 deletions tests/Kernel/BlockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class BlockTest extends EntityKernelTestBase {
'image',
'breakpoint',
'responsive_image',
'oe_theme_helper',
];

/**
Expand Down

0 comments on commit eb2582f

Please sign in to comment.