Unverified Commit bf864999 authored by alexpott's avatar alexpott

Issue #1252606 by rlhawk, dsnopek, attiks, alansaviolobo, MariaDenysyuk, j0rd,...

Issue #1252606 by rlhawk, dsnopek, attiks, alansaviolobo, MariaDenysyuk, j0rd, sushyl, Adysone, Alan Evans, mondrake, andypost, alexpott, sun, tstoeckler: Add crop anchor option to Scale and Crop image effect
parent a7fdfb64
......@@ -76,6 +76,10 @@ image.effect.image_desaturate:
image.effect.image_scale_and_crop:
type: image_size
label: 'Image scale and crop'
mapping:
anchor:
label: 'Anchor'
type: string
image.settings:
type: config_object
......
......@@ -152,6 +152,9 @@ function image_theme() {
'image_crop_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
'image_scale_and_crop_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
'image_rotate_summary' => [
'variables' => ['data' => NULL, 'effect' => []],
],
......
......@@ -5,6 +5,7 @@
* Post-update functions for Image.
*/
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
......@@ -20,3 +21,19 @@ function image_post_update_image_style_dependencies() {
$display->save();
}
}
/**
* Add 'anchor' setting to 'Scale and crop' effects.
*/
function image_post_update_scale_and_crop_effect_add_anchor(&$sandbox = NULL) {
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'image_style', function ($image_style) {
/** @var \Drupal\image\ImageStyleInterface $image_style */
$effects = $image_style->getEffects();
foreach ($effects as $effect) {
if ($effect->getPluginId() === 'image_scale_and_crop') {
return TRUE;
}
}
return FALSE;
});
}
......@@ -13,17 +13,38 @@
* description = @Translation("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.")
* )
*/
class ScaleAndCropImageEffect extends ResizeImageEffect {
class ScaleAndCropImageEffect extends CropImageEffect {
/**
* {@inheritdoc}
*/
public function applyEffect(ImageInterface $image) {
if (!$image->scaleAndCrop($this->configuration['width'], $this->configuration['height'])) {
$width = $this->configuration['width'];
$height = $this->configuration['height'];
$scale = max($width / $image->getWidth(), $height / $image->getHeight());
list($x, $y) = explode('-', $this->configuration['anchor']);
$x = image_filter_keyword($x, $image->getWidth() * $scale, $width);
$y = image_filter_keyword($y, $image->getHeight() * $scale, $height);
if (!$image->apply('scale_and_crop', ['x' => $x, 'y' => $y, 'width' => $width, 'height' => $height])) {
$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 getSummary() {
$summary = [
'#theme' => 'image_scale_and_crop_summary',
'#data' => $this->configuration,
];
$summary += parent::getSummary();
return $summary;
}
}
<?php
namespace Drupal\image\Tests\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests adding an 'anchor' setting to existing scale and crop image effects.
*
* @see image_post_update_scale_and_crop_effect_add_anchor()
*
* @group Update
*/
class ScaleAndCropAddAnchorUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
__DIR__ . '/../../../tests/fixtures/update/test_scale_and_crop_add_anchor.php',
];
}
/**
* Tests that 'anchor' setting is properly added.
*/
public function testImagePostUpdateScaleAndCropEffectAddAnchor() {
// Test that the first effect does not have an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.8c7170c9-5bcc-40f9-8698-f88a8be6d434.data');
$this->assertFalse(array_key_exists('anchor', $effect_data));
// Test that the second effect has an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.a8d83b12-abc6-40c8-9c2f-78a4e421cf97.data');
$this->assertTrue(array_key_exists('anchor', $effect_data));
// Test that the third effect does not have an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.1bffd475-19d0-439a-b6a1-7e5850ce40f9.data');
$this->assertFalse(array_key_exists('anchor', $effect_data));
$this->runUpdates();
// Test that the first effect now has an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.8c7170c9-5bcc-40f9-8698-f88a8be6d434.data');
$this->assertTrue(array_key_exists('anchor', $effect_data));
$this->assertEquals('center-center', $effect_data['anchor']);
// Test that the second effect's 'anchor' setting is unchanged.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.a8d83b12-abc6-40c8-9c2f-78a4e421cf97.data');
$this->assertTrue(array_key_exists('anchor', $effect_data));
$this->assertEquals('left-top', $effect_data['anchor']);
// Test that the third effect still does not have an 'anchor' setting.
$effect_data = $this->config('image.style.test_scale_and_crop_add_anchor')->get('effects.1bffd475-19d0-439a-b6a1-7e5850ce40f9.data');
$this->assertFalse(array_key_exists('anchor', $effect_data));
}
}
{#
/**
* @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.
* - anchor: The part of the image that will be retained after cropping.
* - anchor_label: The translated label of the crop anchor.
* - 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 }}×{{ data.height }}
{%- else -%}
{% if data.width %}
{% trans %}
width {{ data.width }}
{% endtrans %}
{% elseif data.height %}
{% trans %}
height {{ data.height }}
{% endtrans %}
{% endif %}
{%- endif %}
langcode: en
status: true
name: test_scale_and_crop_add_anchor
label: test_scale_and_crop_add_anchor
effects:
8c7170c9-5bcc-40f9-8698-f88a8be6d434:
uuid: 8c7170c9-5bcc-40f9-8698-f88a8be6d434
id: image_scale_and_crop
weight: 1
data:
width: 100
height: 100
a8d83b12-abc6-40c8-9c2f-78a4e421cf97:
uuid: a8d83b12-abc6-40c8-9c2f-78a4e421cf97
id: image_scale_and_crop
weight: 2
data:
width: 100
height: 100
anchor: left-top
1bffd475-19d0-439a-b6a1-7e5850ce40f9:
uuid: 1bffd475-19d0-439a-b6a1-7e5850ce40f9
id: image_rotate
weight: 3
data:
degrees: 180
bgcolor: ''
random: false
<?php
/**
* @file
* Test fixture.
*/
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
$connection->insert('config')
->fields([
'collection' => '',
'name' => 'image.style.test_scale_and_crop_add_anchor',
'data' => serialize(Yaml::decode(file_get_contents('core/modules/image/tests/fixtures/update/image.image_style.test_scale_and_crop_add_anchor.yml'))),
])
->execute();
......@@ -111,8 +111,29 @@ public function testScaleAndCropEffect() {
// Check the parameters.
$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->assertEqual($calls['scale_and_crop'][0][0], 7.5, 'X was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][1], 0, 'Y was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][2], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][3], 10, 'Height was computed and passed correctly');
}
/**
* Test the image_scale_and_crop_effect() function with an anchor.
*/
public function testScaleAndCropEffectWithAnchor() {
$this->assertImageEffect('image_scale_and_crop', [
'anchor' => 'top-1',
'width' => 5,
'height' => 10,
]);
$this->assertToolkitOperationsCalled(['scale_and_crop']);
// Check the parameters.
$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['scale_and_crop'][0][0], 0, 'X was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][1], 1, 'Y was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][2], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['scale_and_crop'][0][3], 10, 'Height was computed and passed correctly');
}
/**
......
......@@ -55,6 +55,7 @@ protected function createEntity() {
$effect = [
'id' => 'image_scale_and_crop',
'data' => [
'anchor' => 'center-center',
'width' => 120,
'height' => 121,
],
......@@ -79,6 +80,7 @@ protected function getExpectedNormalizedEntity() {
'id' => 'image_scale_and_crop',
'weight' => 0,
'data' => [
'anchor' => 'center-center',
'width' => 120,
'height' => 121,
],
......
......@@ -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, 'anchor' => 'center-center'], []]);
$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']]);
}
......
......@@ -20,6 +20,16 @@ class ScaleAndCrop extends GDImageToolkitOperationBase {
*/
protected function arguments() {
return [
'x' => [
'description' => 'The horizontal offset for the start of the crop, in pixels',
'required' => FALSE,
'default' => NULL,
],
'y' => [
'description' => 'The vertical offset for the start the crop, in pixels',
'required' => FALSE,
'default' => NULL,
],
'width' => [
'description' => 'The target width, in pixels',
],
......@@ -38,8 +48,12 @@ protected function validateArguments(array $arguments) {
$scaleFactor = max($arguments['width'] / $actualWidth, $arguments['height'] / $actualHeight);
$arguments['x'] = (int) round(($actualWidth * $scaleFactor - $arguments['width']) / 2);
$arguments['y'] = (int) round(($actualHeight * $scaleFactor - $arguments['height']) / 2);
$arguments['x'] = isset($arguments['x']) ?
(int) round($arguments['x']) :
(int) round(($actualWidth * $scaleFactor - $arguments['width']) / 2);
$arguments['y'] = isset($arguments['y']) ?
(int) round($arguments['y']) :
(int) round(($actualHeight * $scaleFactor - $arguments['height']) / 2);
$arguments['resize'] = [
'width' => (int) round($actualWidth * $scaleFactor),
'height' => (int) round($actualHeight * $scaleFactor),
......
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 1440
height: 620
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 2880
height: 1240
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 1536
height: 1024
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 768
height: 512
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 1024
height: 440
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 1200
height: 800
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 600
height: 400
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 266
height: 236
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 1440
height: 617
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 768
height: 330
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 900
height: 900
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 600
height: 600
......@@ -9,5 +9,6 @@ effects:
id: image_scale_and_crop
weight: 1
data:
anchor: center-center
width: 300
height: 300
File mode changed from 100644 to 100755
{#
/**
* @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.
* - anchor: The part of the image that will be retained after cropping.
* - anchor_label: The translated label of the crop anchor.
* - 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 }}×{{ data.height }}
{%- else -%}
{% if data.width %}
{% trans %}
width {{ data.width }}
{% endtrans %}
{% elseif data.height %}
{% trans %}
height {{ data.height }}
{% endtrans %}
{% endif %}
{%- endif %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment