Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OPENEUROPA-2025: Use custom image effect to prevent retina image reducction. #254

Merged
merged 16 commits into from
Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
dxvargas marked this conversation as resolved.
Show resolved Hide resolved
mapping:
multiplier:
type: integer
label: 'Multiplier'
41 changes: 41 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,41 @@
<?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 {
$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,128 @@
<?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.
if (!$upscale = $this->configuration['upscale']) {
if (
(!empty($target_with = $this->configuration['width']) && $target_with > $image->getWidth()) ||
(!empty($target_height = $this->configuration['height']) && $target_height > $image->getHeight())
) {
// If the image is smaller than the defined dimensions,
// upscale it according to the defined multiplier.
$target_with = $image->getWidth() * $this->configuration['multiplier'];
$target_height = $image->getHeight() * $this->configuration['multiplier'];
$upscale = TRUE;
upchuk marked this conversation as resolved.
Show resolved Hide resolved
}
}
if (!$image->scale($target_with, $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) {
imanoleguskiza marked this conversation as resolved.
Show resolved Hide resolved
if ($dimensions['width'] && $dimensions['height']) {
// If we are not upscaling the image, check to see if it's smaller
// than the defined dimensions.
if (!$upscale = $this->configuration['upscale']) {
if (
(!empty($target_with = $this->configuration['width']) && $this->configuration['width'] > $dimensions['width']) ||
(!empty($target_height = $this->configuration['height']) && $this->configuration['height'] > $dimensions['height'])
) {
upchuk marked this conversation as resolved.
Show resolved Hide resolved
// If the image is smaller than the defined dimensions,
// upscale it according to the defined multiplier.
$target_with = $dimensions['width'] * $this->configuration['multiplier'];
$target_height = $dimensions['height'] * $this->configuration['multiplier'];
$upscale = TRUE;
}
}
Image::scaleDimensions($dimensions, $target_with, $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'],
imanoleguskiza marked this conversation as resolved.
Show resolved Hide resolved
'#required' => TRUE,
'#description' => $this->t("The image will be upscaled according to this multiplier if it is smaller than the dimensions defined in the 'Width' and 'Height' properties."),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single quotes.

Also we should mention that it only gets upscaled if the upscale is not unchecked no?

];

return $form;
}

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

}
115 changes: 115 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,115 @@
<?php

declare(strict_types = 1);
upchuk marked this conversation as resolved.
Show resolved Hide resolved

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 {
upchuk marked this conversation as resolved.
Show resolved Hide resolved

/**
upchuk marked this conversation as resolved.
Show resolved Hide resolved
* {@inheritdoc}
*/
public static $modules = ['image', 'oe_theme_helper'];

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

/**
* {@inheritdoc}
*/
protected function setUp(): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setUp is not ours so we shouldn't really put the void.

parent::setUp();
$this->manager = $this->container->get('plugin.manager.image.effect');
}

/**
* Test the retina_image_scale_effect() function.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What function is this?

*/
public function testRetinaScaleEffect(): void {
$this->assertImageEffect('retina_image_scale', [
// Set the desired width to be much higher than the image width.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

. By default, it uses a multiplier of 2.

'width' => $this->image->getWidth() * 10,
]);
$this->assertToolkitOperationsCalled(['scale']);

// Check the parameters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this comment.

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

/**
* Test the image_scale_effect() function using a multiplier of 3.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What function?

*/
public function testTripleMultiplierRetinaScaleEffect(): void {
$this->assertImageEffect('retina_image_scale', [
// Set the desired width to be much higher than the image width.
'width' => $this->image->getWidth() * 10,
// Set a multiplier of three for the test.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this comment.

'multiplier' => 3,
]);
$this->assertToolkitOperationsCalled(['scale']);

// Check the parameters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment

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

/**
* Test the image_scale_effect() function using a big enough image.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What function?

*/
public function testScaleEffect(): void {
$this->assertImageEffect('image_scale', [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we testing the image_scale effect or the retina_image_scale effect?

// Set the image width to be smaller than the image width.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment sounds weird. Maybe keep it in line with the same comment in the previous methods?

'width' => 10,
'height' => 10,
]);
$this->assertToolkitOperationsCalled(['scale']);

// Check the parameters.
$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 image_scale_effect() function using upscaling.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, copy paste from core I see...

*/
public function testScaleEffectWithUpscaling(): void {
$this->assertImageEffect('image_scale', [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we testing here?

// Set the desired width to be higher than the image width.
'width' => $this->image->getWidth() * 4,
'upscale' => TRUE,
]);
$this->assertToolkitOperationsCalled(['scale']);

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

/**
* 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',
upchuk marked this conversation as resolved.
Show resolved Hide resolved
];

/**
Expand Down