Commit e5b98c30 authored by catch's avatar catch
Browse files

Issue #2103635 by claudiu.cristea | fietserwin: Remove effects from ImageInterface.

parent 172cd265
......@@ -8,7 +8,6 @@
namespace Drupal\Core\Image;
use Drupal\Core\ImageToolkit\ImageToolkitInterface;
use Drupal\Component\Utility\Image as ImageUtility;
/**
* Defines an image object to represent an image file.
......@@ -284,73 +283,31 @@ protected function processInfo() {
}
/**
* {@inheritdoc}
*/
public function scale($width = NULL, $height = NULL, $upscale = FALSE) {
$dimensions = array(
'width' => $this->getWidth(),
'height' => $this->getHeight(),
);
// Scale the dimensions - if they don't change then just return success.
if (!ImageUtility::scaleDimensions($dimensions, $width, $height, $upscale)) {
return TRUE;
}
return $this->resize($dimensions['width'], $dimensions['height']);
}
/**
* {@inheritdoc}
*/
public function scaleAndCrop($width, $height) {
$scale = max($width / $this->getWidth(), $height / $this->getHeight());
$x = ($this->getWidth() * $scale - $width) / 2;
$y = ($this->getHeight() * $scale - $height) / 2;
if ($this->resize($this->getWidth() * $scale, $this->getHeight() * $scale)) {
return $this->crop($x, $y, $width, $height);
* Passes through calls that represent image toolkit operations onto the
* image toolkit.
*
* This is a temporary solution to keep patches reviewable. The __call()
* method will be replaced in https://drupal.org/node/2073759 with a new
* interface method ImageInterface::apply(). An image operation will be
* performed as in the next example:
* @code
* $image = new Image($path, $toolkit);
* $image->apply('scale', array('width' => 50, 'height' => 100));
* @endcode
* Also in https://drupal.org/node/2073759 operation arguments sent to toolkit
* will be moved to a keyed array, unifying the interface of toolkit
* operations.
*
* @todo Drop this in https://drupal.org/node/2073759 in favor of new apply().
*/
public function __call($method, $arguments) {
if (is_callable(array($this->toolkit, $method))) {
// @todo In https://drupal.org/node/2073759, call_user_func_array() will
// be replaced by $this->toolkit->apply($name, $this, $arguments).
array_unshift($arguments, $this);
return call_user_func_array(array($this->toolkit, $method), $arguments);
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function crop($x, $y, $width, $height) {
$aspect = $this->getHeight() / $this->getWidth();
if (empty($height)) $height = $width * $aspect;
if (empty($width)) $width = $height / $aspect;
$width = (int) round($width);
$height = (int) round($height);
return $this->toolkit->crop($this, $x, $y, $width, $height);
}
/**
* {@inheritdoc}
*/
public function resize($width, $height) {
$width = (int) round($width);
$height = (int) round($height);
return $this->toolkit->resize($this, $width, $height);
}
/**
* {@inheritdoc}
*/
public function desaturate() {
return $this->toolkit->desaturate($this);
}
/**
* {@inheritdoc}
*/
public function rotate($degrees, $background = NULL) {
return $this->toolkit->rotate($this, $degrees, $background);
throw new \BadMethodCallException();
}
/**
......
......@@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\Core\Image\ImageInterface.
* Contains \Drupal\Core\Image\ImageInterface.
*/
namespace Drupal\Core\Image;
......@@ -157,106 +157,4 @@ public function getToolkitId();
*/
public function save($destination = NULL);
/**
* 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);
/**
* 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);
/**
* 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\Core\ImageToolkit\ImageToolkitInterface::crop()
*/
public function crop($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\Core\ImageToolkit\ImageToolkitInterface::resize()
*/
public function resize($width, $height);
/**
* Converts an image to grayscale.
*
* @return bool
* TRUE on success, FALSE on failure.
*
* @see \Drupal\Core\ImageToolkit\ImageToolkitInterface::desaturate()
*/
public function desaturate();
/**
* 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\Core\ImageToolkit\ImageToolkitInterface::rotate()
*/
public function rotate($degrees, $background = NULL);
}
......@@ -155,6 +155,49 @@ public function load(ImageInterface $image);
*/
public function save(ImageInterface $image, $destination);
/**
* Scales an image while maintaining aspect ratio.
*
* The resulting image can be smaller for one or both target dimensions.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object.
* @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(ImageInterface $image, $width = NULL, $height = NULL, $upscale = FALSE);
/**
* 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 \Drupal\Core\Image\ImageInterface $image
* An image object.
* @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(ImageInterface $image, $width, $height);
/**
* Gets details about an image.
*
......
......@@ -67,12 +67,12 @@ function testScaleEffect() {
'width' => 10,
'height' => 10,
));
$this->assertToolkitOperationsCalled(array('resize'));
$this->assertToolkitOperationsCalled(array('scale'));
// Check the parameters.
$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['resize'][0][1], 10, 'Width was passed correctly');
$this->assertEqual($calls['resize'][0][2], 5, 'Height was based off aspect ratio and passed correctly');
$this->assertEqual($calls['scale'][0][1], 10, 'Width was passed correctly');
$this->assertEqual($calls['scale'][0][2], 10, 'Height was based off aspect ratio and passed correctly');
}
/**
......@@ -103,14 +103,12 @@ function testScaleAndCropEffect() {
'width' => 5,
'height' => 10,
));
$this->assertToolkitOperationsCalled(array('resize', 'crop'));
$this->assertToolkitOperationsCalled(array('scaleAndCrop'));
// Check the parameters.
$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['crop'][0][1], 7.5, 'X was computed and passed correctly');
$this->assertEqual($calls['crop'][0][2], 0, 'Y was computed and passed correctly');
$this->assertEqual($calls['crop'][0][3], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['crop'][0][4], 10, 'Height was computed and passed correctly');
$this->assertEqual($calls['scaleAndCrop'][0][1], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['scaleAndCrop'][0][2], 10, 'Height was computed and passed correctly');
}
/**
......
......@@ -10,6 +10,7 @@
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Image\ImageInterface;
use Drupal\Core\ImageToolkit\ImageToolkitInterface;
use Drupal\Component\Utility\Image as ImageUtility;
/**
* Defines the GD2 toolkit for image manipulation within Drupal.
......@@ -50,6 +51,11 @@ public function settingsFormSubmit($form, &$form_state) {
* {@inheritdoc}
*/
public function resize(ImageInterface $image, $width, $height) {
// @todo Dimensions computation will be moved into a dedicated functionality
// in https://drupal.org/node/2108307.
$width = (int) round($width);
$height = (int) round($height);
$res = $this->createTmp($image, $width, $height);
if (!imagecopyresampled($res, $image->getResource(), 0, 0, 0, 0, $width, $height, $image->getWidth(), $image->getHeight())) {
......@@ -121,6 +127,14 @@ public function rotate(ImageInterface $image, $degrees, $background = NULL) {
* {@inheritdoc}
*/
public function crop(ImageInterface $image, $x, $y, $width, $height) {
// @todo Dimensions computation will be moved into a dedicated functionality
// in https://drupal.org/node/2108307.
$aspect = $image->getHeight() / $image->getWidth();
$height = empty($height) ? $width * $aspect : $height;
$width = empty($width) ? $height / $aspect : $width;
$width = (int) round($width);
$height = (int) round($height);
$res = $this->createTmp($image, $width, $height);
if (!imagecopyresampled($res, $image->getResource(), 0, 0, $x, $y, $width, $height, $width, $height)) {
......@@ -149,6 +163,42 @@ public function desaturate(ImageInterface $image) {
return imagefilter($image->getResource(), IMG_FILTER_GRAYSCALE);
}
/**
* {@inheritdoc}
*/
public function scale(ImageInterface $image, $width = NULL, $height = NULL, $upscale = FALSE) {
// @todo Dimensions computation will be moved into a dedicated functionality
// in https://drupal.org/node/2108307.
$dimensions = array(
'width' => $image->getWidth(),
'height' => $image->getHeight(),
);
// Scale the dimensions - if they don't change then just return success.
if (!ImageUtility::scaleDimensions($dimensions, $width, $height, $upscale)) {
return TRUE;
}
return $this->resize($image, $dimensions['width'], $dimensions['height']);
}
/**
* {@inheritdoc}
*/
public function scaleAndCrop(ImageInterface $image, $width, $height) {
// @todo Dimensions computation will be moved into a dedicated functionality
// in https://drupal.org/node/2108307.
$scale = max($width / $image->getWidth(), $height / $image->getHeight());
$x = ($image->getWidth() * $scale - $width) / 2;
$y = ($image->getHeight() * $scale - $height) / 2;
if ($this->resize($image, $image->getWidth() * $scale, $image->getHeight() * $scale)) {
return $this->crop($image, $x, $y, $width, $height);
}
return FALSE;
}
/**
* {@inheritdoc}
*/
......
......@@ -68,12 +68,12 @@ function testResize() {
function testScale() {
// TODO: need to test upscaling
$this->assertTrue($this->image->scale(10, 10), 'Function returned the expected value.');
$this->assertToolkitOperationsCalled(array('resize'));
$this->assertToolkitOperationsCalled(array('scale'));
// Check the parameters.
$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['resize'][0][1], 10, 'Width was passed correctly');
$this->assertEqual($calls['resize'][0][2], 5, 'Height was based off aspect ratio and passed correctly');
$this->assertEqual($calls['scale'][0][1], 10, 'Width was passed correctly');
$this->assertEqual($calls['scale'][0][2], 10, 'Height was based off aspect ratio and passed correctly');
}
/**
......@@ -81,15 +81,13 @@ function testScale() {
*/
function testScaleAndCrop() {
$this->assertTrue($this->image->scaleAndCrop(5, 10), 'Function returned the expected value.');
$this->assertToolkitOperationsCalled(array('resize', 'crop'));
$this->assertToolkitOperationsCalled(array('scaleAndCrop'));
// Check the parameters.
$calls = $this->imageTestGetAllCalls();
$this->assertEqual($calls['crop'][0][1], 7.5, 'X was computed and passed correctly');
$this->assertEqual($calls['crop'][0][2], 0, 'Y was computed and passed correctly');
$this->assertEqual($calls['crop'][0][3], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['crop'][0][4], 10, 'Height was computed and passed correctly');
$this->assertEqual($calls['scaleAndCrop'][0][1], 5, 'Width was computed and passed correctly');
$this->assertEqual($calls['scaleAndCrop'][0][2], 10, 'Height was computed and passed correctly');
}
/**
......
......@@ -105,6 +105,22 @@ public function desaturate(ImageInterface $image) {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function scale(ImageInterface $image, $width = NULL, $height = NULL, $upscale = FALSE) {
$this->logCall('scale', array($image, $width, $height, $upscale));
return TRUE;
}
/**
* {@inheritdoc}
*/
public function scaleAndCrop(ImageInterface $image, $width, $height) {
$this->logCall('scaleAndCrop', array($image, $width, $height));
return TRUE;
}
/**
* Stores the values passed to a toolkit call.
*
......
......@@ -39,10 +39,8 @@ public static function getInfo() {
protected function setUp() {
// Use the Druplicon image.
$source = __DIR__ . '/../../../../../misc/druplicon.png';
$this->toolkit = $this->getMockBuilder('Drupal\system\Plugin\ImageToolkit\GDToolkit')
->disableOriginalConstructor()
->getMock();
$this->source = __DIR__ . '/../../../../../misc/druplicon.png';
$this->toolkit = $this->getToolkitMock();
$this->toolkit->expects($this->any())
->method('getPluginId')
......@@ -58,7 +56,25 @@ protected function setUp() {
'mime_type' => 'image/png',
)));
$this->image = new Image($source, $this->toolkit);
$this->image = new Image($this->source, $this->toolkit);
}
/**
* Mocks a toolkit.
*
* @param array $stubs
* (optional) Array containing methods to be replaced with stubs.
*
* @return PHPUnit_Framework_MockObject_MockObject
*/
protected function getToolkitMock(array $stubs = array()) {
$mock_builder = $this->getMockBuilder('Drupal\system\Plugin\ImageToolkit\GDToolkit');
if ($stubs && is_array($stubs)) {
$mock_builder->setMethods($stubs);
}
return $mock_builder
->disableOriginalConstructor()
->getMock();
}
/**
......@@ -218,10 +234,13 @@ public function testProcessInfoFails() {
* Tests \Drupal\Core\Image\Image::scale().
*/
public function testScaleWidth() {
$this->toolkit->expects($this->once())
$toolkit = $this->getToolkitMock(array('resize'));
$image = new Image($this->source, $toolkit);
$toolkit->expects($this->any())
->method('resize')
->will($this->returnArgument(2));
$height = $this->image->scale(44);
$height = $image->scale(44);
$this->assertEquals($height, 50);
}
......@@ -229,11 +248,13 @@ public function testScaleWidth() {
* Tests \Drupal\Core\Image\Image::scale().
*/
public function testScaleHeight() {
$this->toolkit->expects($this->once())
$toolkit = $this->getToolkitMock(array('resize'));
$image = new Image($this->source, $toolkit);
$toolkit->expects($this->any())
->method('resize')
->will($this->returnArgument(1));
$width = $this->image->scale(NULL, 50);
$width = $image->scale(NULL, 50);
$this->assertEquals($width, 44);
}
......@@ -241,12 +262,15 @@ public function testScaleHeight() {
* Tests \Drupal\Core\Image\Image::scale().
*/
public function testScaleSame() {
$toolkit = $this->getToolkitMock(array('resize'));
$image = new Image($this->source, $toolkit);
// Dimensions are the same, resize should not be called.
$this->toolkit->expects($this->never())
$toolkit->expects($this->never())
->method('resize')
->will($this->returnArgument(1));
$width = $this->image->scale(88, 100);
$width = $image->scale(88, 100);
$this->assertEquals($width, 88);
}
......@@ -254,15 +278,18 @@ public function testScaleSame() {
* Tests \Drupal\Core\Image\Image::scaleAndCrop().
*/
public function testScaleAndCropWidth() {
$this->toolkit->expects($this->once())
$toolkit = $this->getToolkitMock(array('resize', 'crop'));
$image = new Image($this->source, $toolkit);
$toolkit->expects($this->once())
->method('resize')
->will($this->returnValue(TRUE));
$this->toolkit->expects($this->once())
$toolkit->expects($this->once())
->method('crop')
->will($this->returnArgument(1));
$x = $this->image->scaleAndCrop(34, 50);
$x = $image->scaleAndCrop(34, 50);
$this->assertEquals($x, 5);
}
......@@ -270,15 +297,18 @@ public function testScaleAndCropWidth() {
* Tests \Drupal\Core\Image\Image::scaleAndCrop().
*/
public function testScaleAndCropHeight() {
$this->toolkit->expects($this->once())
$toolkit = $this->getToolkitMock(array('resize', 'crop'));
$image = new Image($this->source, $toolkit);
$toolkit->expects($this->once())
->method('resize')
->will($this->returnValue(TRUE));
$this->toolkit->expects($this->once())
$toolkit->expects($this->once())
->method('crop')
->will($this->returnArgument(2));
$y = $this->image->scaleAndCrop(44, 40);
$y = $image->scaleAndCrop(44, 40);
$this->assertEquals($y, 5);
}
......@@ -286,38 +316,37 @@ public function testScaleAndCropHeight() {
* Tests \Drupal\Core\Image\Image::scaleAndCrop().
*/
public function testScaleAndCropFails() {
$this->toolkit->expects($this->once())
$toolkit = $this->getToolkitMock(array('resize', 'crop'));
$image = new Image($this->source, $toolkit);
$toolkit->expects($this->once())
->method('resize')
->will($this->returnValue(FALSE));
$this->toolkit->expects($this->never())
$toolkit->expects($this->never())
->method('crop');
$this->image->scaleAndCrop(44, 40);
$image->scaleAndCrop(44, 40);
}
/**
* Tests \Drupal\Core\Image\Image::crop().
*
* @todo Because \Drupal\Tests\Core\Image\ImageTest::testCropWidth() tests
* image geometry conversions (like dimensions, coordinates, etc) and has
* lost its scope in https://drupal.org/node/2103635, it was temporarily