Commit 5e642cfb authored by catch's avatar catch

Issue #2196067 by mondrake, fietserwin: Remove setWidth and setHeight from ImageInterface.

parent c6e469e9
......@@ -33,20 +33,6 @@ class Image implements ImageInterface {
*/
protected $toolkit;
/**
* Height, in pixels.
*
* @var int
*/
protected $height = 0;
/**
* Width, in pixels.
*
* @var int
*/
protected $width = 0;
/**
* Image type represented by a PHP IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG).
*
......@@ -71,14 +57,18 @@ class Image implements ImageInterface {
/**
* Constructs a new Image object.
*
* @param string $source
* The path to an image file.
* @param \Drupal\Core\ImageToolkit\ImageToolkitInterface $toolkit
* The image toolkit.
* @param string|null $source
* (optional) The path to an image file, or NULL to construct the object
* with no image source.
*/
public function __construct($source, ImageToolkitInterface $toolkit) {
$this->source = $source;
public function __construct(ImageToolkitInterface $toolkit, $source = NULL) {
$this->toolkit = $toolkit;
if ($source) {
$this->source = $source;
$this->processInfo();
}
}
/**
......@@ -101,15 +91,7 @@ public function isExisting() {
*/
public function getHeight() {
$this->processInfo();
return $this->height;
}
/**
* {@inheritdoc}
*/
public function setHeight($height) {
$this->height = $height;
return $this;
return $this->toolkit->getHeight($this);
}
/**
......@@ -117,15 +99,7 @@ public function setHeight($height) {
*/
public function getWidth() {
$this->processInfo();
return $this->width;
}
/**
* {@inheritdoc}
*/
public function setWidth($width) {
$this->width = $width;
return $this;
return $this->toolkit->getWidth($this);
}
/**
......@@ -225,8 +199,6 @@ protected function processInfo() {
}
if ($details = $this->toolkit->getInfo($this)) {
$this->height = $details['height'];
$this->width = $details['width'];
$this->type = $details['type'];
$this->fileSize = filesize($destination);
......@@ -244,7 +216,7 @@ protected function processInfo() {
* interface method ImageInterface::apply(). An image operation will be
* performed as in the next example:
* @code
* $image = new Image($path, $toolkit);
* $image = new Image($toolkit, $path);
* $image->apply('scale', array('width' => 50, 'height' => 100));
* @endcode
* Also in https://drupal.org/node/2110499 operation arguments sent to toolkit
......@@ -255,11 +227,13 @@ protected function processInfo() {
*/
public function __call($method, $arguments) {
// @todo Temporary to avoid that legacy GD setResource(), getResource(),
// hasResource() methods moved to GD toolkit in #2103621 get invoked
// from this class anyway through the magic __call. Will be removed
// through https://drupal.org/node/2110499, when call_user_func_array()
// will be replaced by $this->toolkit->apply($name, $this, $arguments).
if (in_array($method, array('setResource', 'getResource', 'hasResource'))) {
// hasResource() methods moved to GD toolkit in #2103621, and setWidth(),
// setHeight() methods moved to ImageToolkitInterface in #2196067 get
// invoked from this class anyway through the magic __call. Will be
// removed through https://drupal.org/node/2110499, when
// call_user_func_array() will be replaced by
// $this->toolkit->apply($name, $this, $arguments).
if (in_array($method, array('setResource', 'getResource', 'hasResource', 'setWidth', 'setHeight'))) {
throw new \BadMethodCallException();
}
if (is_callable(array($this->toolkit, $method))) {
......
......@@ -36,13 +36,14 @@ class ImageFactory {
*/
public function __construct(ImageToolkitManager $toolkit_manager) {
$this->toolkitManager = $toolkit_manager;
$this->toolkitId = $this->toolkitManager->getDefaultToolkitId();
}
/**
* Sets this factory image toolkit ID.
* Sets the ID of the image toolkit.
*
* @param string $toolkit_id
* The image toolkit ID to use for this image factory.
* The ID of the image toolkit to use for this image factory.
*
* @return self
* Returns this image.
......@@ -52,20 +53,41 @@ public function setToolkitId($toolkit_id) {
return $this;
}
/**
* Gets the ID of the image toolkit currently in use.
*
* @return string
* The ID of the image toolkit in use by the image factory.
*/
public function getToolkitId() {
return $this->toolkitId;
}
/**
* Constructs a new Image object.
*
* @param string $source
* The path to an image file.
* Normally, the toolkit set as default in the admin UI is used by the
* factory to create new Image objects. This can be overridden through
* \Drupal\Core\Image\ImageInterface::setToolkitId() so that any new Image
* object created will use the new toolkit specified. Finally, a single
* Image object can be created using a specific toolkit, regardless of the
* current factory settings, by passing its plugin ID in the $toolkit_id
* argument.
*
* @param string|null $source
* (optional) The path to an image file, or NULL to construct the object
* with no image source.
* @param string|null $toolkit_id
* (optional) The ID of the image toolkit to use for this image, or NULL
* to use the current toolkit.
*
* @return \Drupal\Core\Image\ImageInterface
* The new Image object.
* An Image object.
*
* @see ImageFactory::setToolkitId()
*/
public function get($source) {
if (!$this->toolkitId) {
$this->toolkitId = $this->toolkitManager->getDefaultToolkitId();
}
return new Image($source, $this->toolkitManager->createInstance($this->toolkitId));
public function get($source = NULL, $toolkit_id = NULL) {
$toolkit_id = $toolkit_id ?: $this->toolkitId;
return new Image($this->toolkitManager->createInstance($toolkit_id), $source);
}
}
......@@ -29,41 +29,21 @@ public function isSupported();
public function isExisting();
/**
* Returns the height of the image file.
* Returns the height of the image.
*
* @return int
* The height of the file, or 0 if the file is invalid.
* @return int|null
* The height of the image, or NULL if the image is invalid.
*/
public function getHeight();
/**
* Sets the height of the image file.
*
* @param int $height
*
* @return self
* Returns this image file.
*/
public function setHeight($height);
/**
* Returns the width of the image file.
* Returns the width of the image.
*
* @return int
* The width of the file, or 0 if the file is invalid.
* @return int|null
* The width of the image, or NULL if the image is invalid.
*/
public function getWidth();
/**
* Sets the width of the image file.
*
* @param int $width
*
* @return self
* Returns this image file.
*/
public function setWidth($width);
/**
* Returns the size of the image file.
*
......
......@@ -63,8 +63,7 @@ public function settingsFormSubmit($form, &$form_state);
* Scales an image to the specified size.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object. The $image->resource, $image->info['width'], and
* $image->info['height'] values will be modified by this call.
* An image object.
* @param int $width
* The new width of the resized image, in pixels.
* @param int $height
......@@ -79,8 +78,7 @@ public function resize(ImageInterface $image, $width, $height);
* Rotates an image the given number of degrees.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object. The $image->resource, $image->info['width'], and
* $image->info['height'] values will be modified by this call.
* An image object.
* @param int $degrees
* The number of (clockwise) degrees to rotate the image.
* @param string $background
......@@ -99,8 +97,7 @@ public function rotate(ImageInterface $image, $degrees, $background = NULL);
* Crops an image.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object. The $image->resource, $image->info['width'], and
* $image->info['height'] values will be modified by this call.
* An image object.
* @param int $x
* The starting x offset at which to start the crop, in pixels.
* @param int $y
......@@ -194,16 +191,36 @@ public function scaleAndCrop(ImageInterface $image, $width, $height);
* An image object.
*
* @return array
* FALSE, if the file could not be found or is not an image. Otherwise, a
* keyed array containing information about the image:
* - "width": Width, in pixels.
* - "height": Height, in pixels.
* If the file could not be found or is not an image, an empty array;
* otherwise, a keyed array containing information about the image:
* - "type": Image type represented as an IMAGETYPE_* constant.
*
* @see \Drupal\Core\Image\ImageInterface::processInfo()
*/
public function getInfo(ImageInterface $image);
/**
* Returns the height of the image.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object.
*
* @return int|null
* The height of the image, or NULL if the image is invalid.
*/
public function getHeight(ImageInterface $image);
/**
* Returns the width of the image.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object.
*
* @return int|null
* The width of the image, or NULL if the image is invalid.
*/
public function getWidth(ImageInterface $image);
/**
* Gets toolkit requirements in a format suitable for hook_requirements().
*
......
......@@ -407,9 +407,8 @@ function file_validate_is_image(File $file) {
$image = \Drupal::service('image.factory')->get($file->getFileUri());
if (!$image->isSupported()) {
$toolkit = \Drupal::service('image.toolkit.manager')->getDefaultToolkit();
$extensions = array();
foreach ($toolkit->supportedTypes() as $image_type) {
foreach ($image->getToolkit()->supportedTypes() as $image_type) {
$extensions[] = Unicode::strtoupper(image_type_to_extension($image_type));
}
$errors[] = t('Image type not supported. Allowed types: @types.', array('@types' => implode(', ', $extensions)));
......
......@@ -79,7 +79,7 @@ function testFileValidateImageResolution() {
$this->assertEqual(count($errors), 1, 'Small images report an error.', 'File');
// Maximum size.
if ($this->container->has('image.toolkit.manager')) {
if ($this->container->get('image.factory')->getToolkitId()) {
// Copy the image so that the original doesn't get resized.
copy('core/misc/druplicon.png', 'temporary://druplicon.png');
$this->image->setFileUri('temporary://druplicon.png');
......
......@@ -88,16 +88,13 @@ public function resize(ImageInterface $image, $width, $height) {
$res = $this->createTmp($image->getType(), $width, $height);
if (!imagecopyresampled($res, $this->getResource(), 0, 0, 0, 0, $width, $height, $image->getWidth(), $image->getHeight())) {
if (!imagecopyresampled($res, $this->getResource(), 0, 0, 0, 0, $width, $height, $this->getWidth($image), $this->getHeight($image))) {
return FALSE;
}
imagedestroy($this->getResource());
// Update image object.
$this->setResource($res);
$image
->setWidth($width)
->setHeight($height);
return TRUE;
}
......@@ -147,9 +144,6 @@ public function rotate(ImageInterface $image, $degrees, $background = NULL) {
imagecolortransparent($this->getResource(), $background);
}
$image
->setWidth(imagesx($this->getResource()))
->setHeight(imagesy($this->getResource()));
return TRUE;
}
......@@ -159,7 +153,7 @@ public function rotate(ImageInterface $image, $degrees, $background = NULL) {
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();
$aspect = $this->getHeight($image) / $this->getWidth($image);
$height = empty($height) ? $width * $aspect : $height;
$width = empty($width) ? $height / $aspect : $width;
$width = (int) round($width);
......@@ -174,9 +168,6 @@ public function crop(ImageInterface $image, $x, $y, $width, $height) {
// Destroy the original image and return the modified image.
imagedestroy($this->getResource());
$this->setResource($res);
$image
->setWidth($width)
->setHeight($height);
return TRUE;
}
......@@ -200,8 +191,8 @@ public function scale(ImageInterface $image, $width = NULL, $height = NULL, $ups
// @todo Dimensions computation will be moved into a dedicated functionality
// in https://drupal.org/node/2108307.
$dimensions = array(
'width' => $image->getWidth(),
'height' => $image->getHeight(),
'width' => $this->getWidth($image),
'height' => $this->getHeight($image),
);
// Scale the dimensions - if they don't change then just return success.
......@@ -218,11 +209,11 @@ public function scale(ImageInterface $image, $width = NULL, $height = NULL, $ups
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;
$scale = max($width / $this->getWidth($image), $height / $this->getHeight($image));
$x = ($this->getWidth($image) * $scale - $width) / 2;
$y = ($this->getHeight($image) * $scale - $height) / 2;
if ($this->resize($image, $image->getWidth() * $scale, $image->getHeight() * $scale)) {
if ($this->resize($image, $this->getWidth($image) * $scale, $this->getHeight($image) * $scale)) {
return $this->crop($image, $x, $y, $width, $height);
}
......@@ -247,8 +238,8 @@ protected function load($source, array $details) {
if (!imageistruecolor($resource)) {
// Convert indexed images to true color, so that filters work
// correctly and don't result in unnecessary dither.
$new_image = $this->createTmp($details['type'], $details['width'], $details['height']);
imagecopy($new_image, $resource, 0, 0, 0, 0, $details['width'], $details['height']);
$new_image = $this->createTmp($details['type'], imagesx($resource), imagesy($resource));
imagecopy($new_image, $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource));
imagedestroy($resource);
$this->setResource($new_image);
}
......@@ -301,18 +292,11 @@ public function save(ImageInterface $image, $destination) {
* {@inheritdoc}
*/
public function getInfo(ImageInterface $image) {
$details = FALSE;
$details = array();
$data = getimagesize($image->getSource());
if (isset($data) && is_array($data) && in_array($data[2], $this->supportedTypes())) {
$details = array(
'width' => $data[0],
'height' => $data[1],
'type' => $data[2],
);
}
if ($details) {
if (isset($data) && is_array($data) && in_array($data[2], static::supportedTypes())) {
$details['type'] = $data[2];
$this->load($image->getSource(), $details);
}
return $details;
......@@ -363,6 +347,20 @@ public function createTmp($type, $width, $height) {
return $res;
}
/**
* {@inheritdoc}
*/
public function getWidth(ImageInterface $image) {
return $this->getResource() ? imagesx($this->getResource()) : NULL;
}
/**
* {@inheritdoc}
*/
public function getHeight(ImageInterface $image) {
return $this->getResource() ? imagesy($this->getResource()) : NULL;
}
/**
* {@inheritdoc}
*/
......
......@@ -15,6 +15,14 @@
* Test the core GD image manipulation functions.
*/
class ToolkitGdTest extends DrupalUnitTestBase {
/**
* The image factory service.
*
* @var \Drupal\Core\Image\ImageFactory
*/
protected $imageFactory;
// Colors that are used in testing.
protected $black = array(0, 0, 0, 0);
protected $red = array(255, 0, 0, 0);
......@@ -43,6 +51,16 @@ public static function getInfo() {
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Set the image factory service.
$this->imageFactory = $this->container->get('image.factory');
}
protected function checkRequirements() {
// GD2 support is available.
if (!function_exists('imagegd2')) {
......@@ -92,6 +110,10 @@ function getPixelColor(ImageInterface $image, $x, $y) {
* the expected height and widths for the final images.
*/
function testManipulations() {
// Test that the image factory is set to use the GD toolkit.
$this->assertEqual($this->imageFactory->getToolkitId(), 'gd', 'The image factory is set to use the \'gd\' image toolkit.');
// Typically the corner colors will be unchanged. These colors are in the
// order of top-left, top-right, bottom-right, bottom-left.
$default_corners = array($this->red, $this->green, $this->blue, $this->transparent);
......@@ -211,11 +233,10 @@ function testManipulations() {
);
}
$image_factory = $this->container->get('image.factory')->setToolkitId('gd');
foreach ($files as $file) {
foreach ($operations as $op => $values) {
// Load up a fresh image.
$image = $image_factory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file);
$image = $this->imageFactory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file);
$toolkit = $image->getToolkit();
if (!$image) {
$this->fail(String::format('Could not load image %file.', array('%file' => $file)));
......
......@@ -29,6 +29,13 @@ abstract class ToolkitTestBase extends WebTestBase {
*/
protected $file;
/**
* The image factory service.
*
* @var \Drupal\Core\Image\ImageFactory
*/
protected $imageFactory;
/**
* The image object for the test file.
*
......@@ -39,6 +46,9 @@ abstract class ToolkitTestBase extends WebTestBase {
function setUp() {
parent::setUp();
// Set the image factory service.
$this->imageFactory = $this->container->get('image.factory');
// Pick a file for testing.
$file = current($this->drupalGetTestFiles('image'));
$this->file = $file->uri;
......@@ -57,9 +67,7 @@ function setUp() {
* The image object.
*/
protected function getImage() {
$image = $this->container->get('image.factory')
->setToolkitId('test')
->get($this->file);
$image = $this->imageFactory->get($this->file, 'test');
$this->assertTrue($image->isExisting(), 'Image was loaded.');
return $image;
}
......
......@@ -20,6 +20,20 @@
*/
class TestToolkit extends ImageToolkitBase {
/**
* The width of the image.
*
* @var int
*/
protected $width;
/**
* The height of the image.
*
* @var int
*/
protected $height;
/**
* {@inheritdoc}
*/
......@@ -51,25 +65,20 @@ public function settingsFormSubmit($form, &$form_state) {
public function getInfo(ImageInterface $image) {
$this->logCall('get_info', array($image));
$details = FALSE;
$details = array();
$data = getimagesize($image->getSource());
if (isset($data) && is_array($data)) {
$details = array(
'width' => $data[0],
'height' => $data[1],
'type' => $data[2],
);
}
if ($details) {
if (isset($data) && is_array($data) && in_array($data[2], static::supportedTypes())) {
$details['type'] = $data[2];
$this->width = $data[0];
$this->height = $data[1];
$this->load($image->getSource(), $details);
}
return $details;
}
/**
* Creates a resource from a file.
* Mimick loading the image from a file.
*
* @param string $source
* String specifying the path of the image file.
......@@ -160,6 +169,20 @@ protected function logCall($op, $args) {
\Drupal::state()->set('image_test.results', $results);
}
/**
* {@inheritdoc}
*/
public function getWidth(ImageInterface $image) {
return $this->width;
}
/**
* {@inheritdoc}
*/
public function getHeight(ImageInterface $image) {
return $this->height;
}
/**
* {@inheritdoc}
*/
......
......@@ -46,17 +46,7 @@ protected function setUp() {
->method('getPluginId')
->will($this->returnValue('gd'));
$this->toolkit->expects($this->any())
->method('getInfo')
->will($this->returnValue(array(
'width' => 88,
'height' => 100,
'extension' => 'png',
'type' => IMAGETYPE_PNG,
'mime_type' => 'image/png',
)));
$this->image = new Image($this->source, $this->toolkit);
$this->image = new Image($this->toolkit, $this->source);
}
/**
......@@ -65,15 +55,14 @@ protected function setUp() {
* @param array $stubs
* (optional) Array containing methods to be replaced with stubs.
*
* @return PHPUnit_Framework_MockObject_MockObject
* @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);
}
$stubs += array('getPluginId', 'save');
return $mock_builder
->disableOriginalConstructor()
->setMethods($stubs)
->getMock();
}
......@@ -84,15 +73,6 @@ public function testGetHeight() {
$this->assertEquals($this->image->getHeight(), 100);
}
/**
* Tests \Drupal\Core\Image\Image::setHeight().
*/
public function testSetHeight() {
$this->image->getHeight();
$this->image->setHeight(400);
$this->assertEquals($this->image->getHeight(), 400);
}
/**
* Tests \Drupal\Core\Image\Image::getWidth().
*/
......@@ -100,15 +80,6 @@ public function testGetWidth() {
$this->assertEquals($this->image->getWidth(), 88);
}
/**
* Tests \Drupal\Core\Image\Image::setWidth().
*/
public function testSetWidth() {
$this->image->getHeight();
$this->image->setWidth(337);
$this->assertEquals($this->image->getWidth(), 337);
}
/**
* Tests \Drupal\Core\Image\Image::getFileSize
*/
......@@ -163,7 +134,7 @@ public function testSave() {
->method('save')
->will($this->returnValue(TRUE));
$image = $this->getMock('Drupal\Core\Image\Image', array('chmod'), array($this->image->getSource(), $this->toolkit));
$image = $this->getMock('Drupal\Core\Image\Image', array('chmod'), array($this->toolkit, $this->image->getSource()));
$image->expects($this->any())
->method('chmod')
->will($this->returnValue(TRUE));
......@@ -192,7 +163,7 @@ public function testChmodFails() {
->method('save')
->will($this->returnValue(TRUE));
$image = $this->getMock('Drupal\Core\Image\Image', array('chmod'), array($this->image->getSource(), $this->toolkit));
$image = $this->getMock('Drupal\Core\Image\Image', array('chmod'), array($this->toolkit, $this->image->getSource()));
$image->expects($this->any())
->method('chmod')
->will($this->returnValue(FALSE));
......@@ -201,11 +172,13 @@ public function testChmodFails() {
}
/**
* Tests \Drupal\Core\Image\Image::save().
* Tests \Drupal\Core\Image\Image::processInfo().
*/
public function testProcessInfoFails() {
$this->image->setSource('magic-foobars.png');
$this->assertFalse((bool) $this->image->getWidth());
$toolkit = $this->getToolkitMock();
$image = new Image($toolkit, 'magic-foobars.png');
$this->assertFalse($image->isExisting());
}
/**
......@@ -213,7 +186,7 @@ public function testProcessInfoFails() {