Commit fd0be046 authored by webchick's avatar webchick

Issue #2244359 by mondrake: Lazy-load GD resource in GDToolkit.

parent b5ade79e
......@@ -40,13 +40,6 @@ class Image implements ImageInterface {
*/
protected $fileSize;
/**
* If this image object is valid.
*
* @var bool
*/
protected $valid = FALSE;
/**
* Constructs a new Image object.
*
......@@ -61,7 +54,10 @@ public function __construct(ImageToolkitInterface $toolkit, $source = NULL) {
$this->getToolkit()->setImage($this);
if ($source) {
$this->source = $source;
$this->parseFile();
// Defer image file validity check to the toolkit.
if ($this->getToolkit()->parseFile()) {
$this->fileSize = filesize($this->source);
}
}
}
......@@ -69,7 +65,7 @@ public function __construct(ImageToolkitInterface $toolkit, $source = NULL) {
* {@inheritdoc}
*/
public function isValid() {
return $this->valid;
return $this->getToolkit()->isValid();
}
/**
......@@ -145,24 +141,6 @@ public function save($destination = NULL) {
return FALSE;
}
/**
* Determines if a file contains a valid image.
*
* Drupal supports GIF, JPG and PNG file formats when used with the GD
* toolkit, and may support others, depending on which toolkits are
* installed.
*
* @return bool
* FALSE, if the file could not be found or is not an image. Otherwise, the
* image information is populated.
*/
protected function parseFile() {
if ($this->valid = $this->getToolkit()->parseFile()) {
$this->fileSize = filesize($this->source);
}
return $this->valid;
}
/**
* {@inheritdoc}
*/
......
......@@ -84,6 +84,15 @@ public function setImage(ImageInterface $image);
*/
public function getImage();
/**
* Checks if the image is valid.
*
* @return bool
* TRUE if the image toolkit is currently handling a valid image, FALSE
* otherwise.
*/
public function isValid();
/**
* Writes an image resource to a destination file.
*
......@@ -98,6 +107,10 @@ public function save($destination);
/**
* Determines if a file contains a valid image.
*
* Drupal supports GIF, JPG and PNG file formats when used with the GD
* toolkit, and may support others, depending on which toolkits are
* installed.
*
* @return bool
* TRUE if the file could be found and is an image, FALSE otherwise.
*/
......
......@@ -24,9 +24,9 @@ class GDToolkit extends ImageToolkitBase {
/**
* A GD image resource.
*
* @var resource
* @var resource|null
*/
protected $resource;
protected $resource = NULL;
/**
* Image type represented by a PHP IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG).
......@@ -35,6 +35,21 @@ class GDToolkit extends ImageToolkitBase {
*/
protected $type;
/**
* Image information from a file, available prior to loading the GD resource.
*
* This contains a copy of the array returned by executing getimagesize()
* on the image file when the image object is instantiated. It gets reset
* to NULL as soon as the GD resource is loaded.
*
* @var array|null
*
* @see \Drupal\system\Plugin\ImageToolkit\GDToolkit::parseFile()
* @see \Drupal\system\Plugin\ImageToolkit\GDToolkit::setResource()
* @see http://php.net/manual/en/function.getimagesize.php
*/
protected $preLoadInfo = NULL;
/**
* Sets the GD image resource.
*
......@@ -44,6 +59,7 @@ class GDToolkit extends ImageToolkitBase {
* @return $this
*/
public function setResource($resource) {
$this->preLoadInfo = NULL;
$this->resource = $resource;
return $this;
}
......@@ -51,10 +67,13 @@ public function setResource($resource) {
/**
* Retrieves the GD image resource.
*
* @return resource
* The GD image resource.
* @return resource|null
* The GD image resource, or NULL if not available.
*/
public function getResource() {
if (!$this->resource) {
$this->load();
}
return $this->resource;
}
......@@ -90,22 +109,39 @@ public function settingsFormSubmit($form, FormStateInterface $form_state) {
* TRUE or FALSE, based on success.
*/
protected function load() {
// Return immediately if the image file is not valid.
if (!$this->isValid()) {
return FALSE;
}
$function = 'imagecreatefrom' . image_type_to_extension($this->getType(), FALSE);
if (function_exists($function) && $resource = $function($this->getImage()->getSource())) {
$this->setResource($resource);
if (!imageistruecolor($resource)) {
if (imageistruecolor($resource)) {
return TRUE;
}
else {
// Convert indexed images to true color, so that filters work
// correctly and don't result in unnecessary dither.
$new_image = $this->createTmp($this->getType(), imagesx($resource), imagesy($resource));
imagecopy($new_image, $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource));
imagedestroy($resource);
$this->setResource($new_image);
if ($ret = (bool) $new_image) {
imagecopy($new_image, $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource));
imagedestroy($resource);
$this->setResource($new_image);
}
return $ret;
}
return (bool) $this->getResource();
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isValid() {
return ((bool) $this->preLoadInfo || (bool) $this->resource);
}
/**
* {@inheritdoc}
*/
......@@ -152,8 +188,8 @@ public function parseFile() {
$data = @getimagesize($this->getImage()->getSource());
if ($data && in_array($data[2], static::supportedTypes())) {
$this->setType($data[2]);
$this->load();
return (bool) $this->getResource();
$this->preLoadInfo = $data;
return TRUE;
}
return FALSE;
}
......@@ -216,14 +252,30 @@ public function createTmp($type, $width, $height) {
* {@inheritdoc}
*/
public function getWidth() {
return $this->getResource() ? imagesx($this->getResource()) : NULL;
if ($this->preLoadInfo) {
return $this->preLoadInfo[0];
}
elseif ($res = $this->getResource()) {
return imagesx($res);
}
else {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function getHeight() {
return $this->getResource() ? imagesy($this->getResource()) : NULL;
if ($this->preLoadInfo) {
return $this->preLoadInfo[1];
}
elseif ($res = $this->getResource()) {
return imagesy($res);
}
else {
return NULL;
}
}
/**
......
......@@ -68,7 +68,7 @@ protected function setUp() {
*/
protected function getImage() {
$image = $this->imageFactory->get($this->file, 'test');
$this->assertTrue($image->isValid(), 'Image was loaded.');
$this->assertTrue($image->isValid(), 'Image file was parsed.');
return $image;
}
......
......@@ -67,6 +67,13 @@ public function settingsFormSubmit($form, FormStateInterface $form_state) {
->save();
}
/**
* {@inheritdoc}
*/
public function isValid() {
return isset($this->type);
}
/**
* {@inheritdoc}
*/
......
......@@ -64,7 +64,7 @@ protected function setUp() {
*/
protected function getToolkitMock(array $stubs = array()) {
$mock_builder = $this->getMockBuilder('Drupal\system\Plugin\ImageToolkit\GDToolkit');
$stubs += array('getPluginId', 'save');
$stubs = array_merge(array('getPluginId', 'save'), $stubs);
return $mock_builder
->disableOriginalConstructor()
->setMethods($stubs)
......@@ -93,19 +93,31 @@ protected function getToolkitOperationMock($class_name, ImageToolkitInterface $t
/**
* Get an image with a mocked toolkit, for testing.
*
* @param bool $load_expected
* (optional) Whether the load() method is expected to be called. Defaults
* to TRUE.
* @param array $stubs
* (optional) Array containing toolkit methods to be replaced with stubs.
*
* @return \Drupal\Core\Image\Image
* An image object.
*/
protected function getTestImage(array $stubs = array()) {
protected function getTestImage($load_expected = TRUE, array $stubs = array()) {
if (!$load_expected && !in_array('load', $stubs)) {
$stubs = array_merge(array('load'), $stubs);
}
$this->toolkit = $this->getToolkitMock($stubs);
$this->toolkit->expects($this->any())
->method('getPluginId')
->will($this->returnValue('gd'));
if (!$load_expected) {
$this->toolkit->expects($this->never())
->method('load');
}
$this->image = new Image($this->toolkit, $this->source);
}
......@@ -119,7 +131,7 @@ protected function getTestImage(array $stubs = array()) {
* An image object.
*/
protected function getTestImageForOperation($class_name) {
$this->toolkit = $this->getToolkitMock(array('getToolkitOperation', 'getPluginId'));
$this->toolkit = $this->getToolkitMock(array('getToolkitOperation'));
$this->toolkitOperation = $this->getToolkitOperationMock($class_name, $this->toolkit);
$this->toolkit->expects($this->any())
......@@ -137,7 +149,7 @@ protected function getTestImageForOperation($class_name) {
* Tests \Drupal\Core\Image\Image::getHeight().
*/
public function testGetHeight() {
$this->getTestImage();
$this->getTestImage(FALSE);
$this->assertEquals(100, $this->image->getHeight());
}
......@@ -145,7 +157,7 @@ public function testGetHeight() {
* Tests \Drupal\Core\Image\Image::getWidth().
*/
public function testGetWidth() {
$this->getTestImage();
$this->getTestImage(FALSE);
$this->assertEquals(88, $this->image->getWidth());
}
......@@ -153,7 +165,7 @@ public function testGetWidth() {
* Tests \Drupal\Core\Image\Image::getFileSize
*/
public function testGetFileSize() {
$this->getTestImage();
$this->getTestImage(FALSE);
$this->assertEquals(3905, $this->image->getFileSize());
}
......@@ -161,7 +173,7 @@ public function testGetFileSize() {
* Tests \Drupal\Core\Image\Image::getToolkit()->getType().
*/
public function testGetType() {
$this->getTestImage();
$this->getTestImage(FALSE);
$this->assertEquals(IMAGETYPE_PNG, $this->image->getToolkit()->getType());
}
......@@ -169,7 +181,7 @@ public function testGetType() {
* Tests \Drupal\Core\Image\Image::getMimeType().
*/
public function testGetMimeType() {
$this->getTestImage();
$this->getTestImage(FALSE);
$this->assertEquals('image/png', $this->image->getMimeType());
}
......@@ -177,7 +189,7 @@ public function testGetMimeType() {
* Tests \Drupal\Core\Image\Image::isValid().
*/
public function testIsValid() {
$this->getTestImage();
$this->getTestImage(FALSE);
$this->assertTrue($this->image->isValid());
$this->assertTrue(is_readable($this->image->getSource()));
}
......@@ -186,7 +198,7 @@ public function testIsValid() {
* Tests \Drupal\Core\Image\Image::getToolkitId().
*/
public function testGetToolkitId() {
$this->getTestImage();
$this->getTestImage(FALSE);
$this->assertEquals('gd', $this->image->getToolkitId());
}
......
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