Issue #2103621 by mondrake | fietserwin: Move GD logic from ImageInterface to toolkit, tie toolkit instance to Image instance, toolkits should no longer be instantiated separately.

8.0.x
Alex Pott 2014-03-11 01:00:44 +00:00
parent 3b3cb6f9ae
commit f437807590
15 changed files with 203 additions and 193 deletions

View File

@ -608,13 +608,9 @@ services:
image.toolkit.manager:
class: Drupal\Core\ImageToolkit\ImageToolkitManager
arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@config.factory', '@module_handler']
image.toolkit:
class: Drupal\Core\ImageToolkit\ImageToolkitInterface
factory_method: getDefaultToolkit
factory_service: image.toolkit.manager
image.factory:
class: Drupal\Core\Image\ImageFactory
arguments: ['@image.toolkit']
arguments: ['@image.toolkit.manager']
breadcrumb:
class: Drupal\Core\Breadcrumb\BreadcrumbManager
arguments: ['@module_handler']

View File

@ -33,13 +33,6 @@ class Image implements ImageInterface {
*/
protected $toolkit;
/**
* An image file handle.
*
* @var resource
*/
protected $resource;
/**
* Height, in pixels.
*
@ -109,6 +102,14 @@ class Image implements ImageInterface {
return in_array($this->getType(), $this->toolkit->supportedTypes());
}
/**
* {@inheritdoc}
*/
public function isExisting() {
$this->processInfo();
return $this->processed;
}
/**
* {@inheritdoc}
*/
@ -173,32 +174,6 @@ class Image implements ImageInterface {
return $this->mimeType;
}
/**
* {@inheritdoc}
*/
public function setResource($resource) {
$this->resource = $resource;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasResource() {
return (bool) $this->resource;
}
/**
* {@inheritdoc}
*/
public function getResource() {
if (!$this->hasResource()) {
$this->processInfo();
$this->toolkit->load($this);
}
return $this->resource;
}
/**
* {@inheritdoc}
*/
@ -221,6 +196,14 @@ class Image implements ImageInterface {
return $this->toolkit->getPluginId();
}
/**
* {@inheritdoc}
*/
public function getToolkit() {
$this->processInfo();
return $this->toolkit;
}
/**
* {@inheritdoc}
*/
@ -287,22 +270,30 @@ class Image implements ImageInterface {
* 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
* method will be replaced in https://drupal.org/node/2110499 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
* Also in https://drupal.org/node/2110499 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().
* @todo Drop this in https://drupal.org/node/2110499 in favor of new apply().
*/
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'))) {
throw new \BadMethodCallException();
}
if (is_callable(array($this->toolkit, $method))) {
// @todo In https://drupal.org/node/2073759, call_user_func_array() will
// @todo In https://drupal.org/node/2110499, 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);

View File

@ -7,7 +7,7 @@
namespace Drupal\Core\Image;
use Drupal\Core\ImageToolkit\ImageToolkitInterface;
use Drupal\Core\ImageToolkit\ImageToolkitManager;
/**
* Provides a factory for image objects.
@ -15,33 +15,40 @@ use Drupal\Core\ImageToolkit\ImageToolkitInterface;
class ImageFactory {
/**
* The image toolkit to use for this factory.
* The image toolkit plugin manager.
*
* @var \Drupal\Core\ImageToolkit\ImageToolkitInterface
* @var \Drupal\Core\ImageToolkit\ImageToolkitManager
*/
protected $toolkit;
protected $toolkitManager;
/**
* The image toolkit ID to use for this factory.
*
* @var string
*/
protected $toolkitId;
/**
* Constructs a new ImageFactory object.
*
* @param \Drupal\Core\ImageToolkit\ImageToolkitInterface $toolkit
* The image toolkit to use for this image factory.
* @param \Drupal\Core\ImageToolkit\ImageToolkitManager $toolkit_manager
* The image toolkit plugin manager.
*/
public function __construct(ImageToolkitInterface $toolkit) {
$this->toolkit = $toolkit;
public function __construct(ImageToolkitManager $toolkit_manager) {
$this->toolkitManager = $toolkit_manager;
}
/**
* Sets a custom image toolkit.
* Sets this factory image toolkit ID.
*
* @param \Drupal\Core\ImageToolkit\ImageToolkitInterface $toolkit
* The image toolkit to use for this image factory.
* @param string $toolkit_id
* The image toolkit ID to use for this image factory.
*
* @return self
* Returns this image.
*/
public function setToolkit(ImageToolkitInterface $toolkit) {
$this->toolkit = $toolkit;
public function setToolkitId($toolkit_id) {
$this->toolkitId = $toolkit_id;
return $this;
}
@ -55,7 +62,10 @@ class ImageFactory {
* The new Image object.
*/
public function get($source) {
return new Image($source, $this->toolkit);
if (!$this->toolkitId) {
$this->toolkitId = $this->toolkitManager->getDefaultToolkitId();
}
return new Image($source, $this->toolkitManager->createInstance($this->toolkitId));
}
}

View File

@ -20,6 +20,14 @@ interface ImageInterface {
*/
public function isSupported();
/**
* Checks if the image is existing.
*
* @return bool
* TRUE if the image exists and is a valid image, FALSE otherwise.
*/
public function isExisting();
/**
* Returns the extension of the image file.
*
@ -89,33 +97,6 @@ interface ImageInterface {
*/
public function getMimeType();
/**
* Sets the image file resource.
*
* @param resource $resource
* The image file handle.
*
* @return self
* Returns this image file.
*/
public function setResource($resource);
/**
* Determines if this image file has a resource set.
*
* @return bool
* TRUE if this image file has a resource set, FALSE otherwise.
*/
public function hasResource();
/**
* Retrieves the image file resource.
*
* @return resource
* The image file handle.
*/
public function getResource();
/**
* Sets the source path of the image file.
*
@ -135,6 +116,14 @@ interface ImageInterface {
*/
public function getSource();
/**
* Returns the image toolkit used for this image file.
*
* @return string
* The image toolkit.
*/
public function getToolkit();
/**
* Returns the ID of the image toolkit used for this image file.
*

View File

@ -131,17 +131,6 @@ interface ImageToolkitInterface extends PluginInspectionInterface {
*/
public function desaturate(ImageInterface $image);
/**
* Creates an image resource from a file.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object. The $image->resource value will populated by this call.
*
* @return bool
* TRUE or FALSE, based on success.
*/
public function load(ImageInterface $image);
/**
* Writes an image resource to a destination file.
*

View File

@ -48,12 +48,12 @@ class ImageToolkitManager extends DefaultPluginManager {
}
/**
* Gets the default image toolkit.
* Gets the default image toolkit ID.
*
* @return \Drupal\Core\ImageToolkit\ImageToolkitInterface
* Object of the default toolkit, or FALSE on error.
* @return string|bool
* ID of the default toolkit, or FALSE on error.
*/
public function getDefaultToolkit() {
public function getDefaultToolkitId() {
$toolkit_id = $this->configFactory->get('system.image')->get('toolkit');
$toolkits = $this->getAvailableToolkits();
@ -64,14 +64,20 @@ class ImageToolkitManager extends DefaultPluginManager {
$toolkit_id = key($toolkits);
}
if ($toolkit_id) {
$toolkit = $this->createInstance($toolkit_id);
}
else {
$toolkit = FALSE;
}
return $toolkit_id;
}
return $toolkit;
/**
* Gets the default image toolkit.
*
* @return \Drupal\Core\ImageToolkit\ImageToolkitInterface
* Object of the default toolkit, or FALSE on error.
*/
public function getDefaultToolkit() {
if ($toolkit_id = $this->getDefaultToolkitId()) {
return $this->createInstance($toolkit_id);
}
return FALSE;
}
/**

View File

@ -406,7 +406,7 @@ function file_validate_is_image(File $file) {
$image = \Drupal::service('image.factory')->get($file->getFileUri());
if (!$image->isSupported()) {
$toolkit = \Drupal::service('image.toolkit');
$toolkit = \Drupal::service('image.toolkit.manager')->getDefaultToolkit();
$extensions = array();
foreach ($toolkit->supportedTypes() as $image_type) {
$extensions[] = Unicode::strtoupper(image_type_to_extension($image_type));
@ -453,7 +453,7 @@ function file_validate_image_resolution(File $file, $maximum_dimensions = 0, $mi
if ($image->getWidth() > $width || $image->getHeight() > $height) {
// Try to resize the image to fit the dimensions.
$image = $image_factory->get($file->getFileUri());
if ($image->getResource()) {
if ($image->isExisting()) {
$image->scale($width, $height);
$image->save();
$file->filesize = $image->getFileSize();

View File

@ -79,7 +79,7 @@ class ValidatorTest extends FileManagedUnitTestBase {
$this->assertEqual(count($errors), 1, 'Small images report an error.', 'File');
// Maximum size.
if ($this->container->has('image.toolkit')) {
if ($this->container->has('image.toolkit.manager')) {
// 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');

View File

@ -278,7 +278,7 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
}
$image = \Drupal::service('image.factory')->get($original_uri);
if (!$image->getResource()) {
if (!$image->isExisting()) {
return FALSE;
}

View File

@ -21,6 +21,37 @@ use Drupal\Component\Utility\Image as ImageUtility;
*/
class GDToolkit extends ImageToolkitBase {
/**
* A GD image resource.
*
* @var resource
*/
protected $resource;
/**
* Sets the GD image resource.
*
* @param resource $resource
* The GD image resource.
*
* @return self
* Returns this toolkit object.
*/
public function setResource($resource) {
$this->resource = $resource;
return $this;
}
/**
* Retrieves the GD image resource.
*
* @return resource
* The GD image resource.
*/
public function getResource() {
return $this->resource;
}
/**
* {@inheritdoc}
*/
@ -55,16 +86,16 @@ class GDToolkit extends ImageToolkitBase {
$width = (int) round($width);
$height = (int) round($height);
$res = $this->createTmp($image, $width, $height);
$res = $this->createTmp($image->getType(), $width, $height);
if (!imagecopyresampled($res, $image->getResource(), 0, 0, 0, 0, $width, $height, $image->getWidth(), $image->getHeight())) {
if (!imagecopyresampled($res, $this->getResource(), 0, 0, 0, 0, $width, $height, $image->getWidth(), $image->getHeight())) {
return FALSE;
}
imagedestroy($image->getResource());
imagedestroy($this->getResource());
// Update image object.
$this->setResource($res);
$image
->setResource($res)
->setWidth($width)
->setHeight($height);
return TRUE;
@ -86,39 +117,39 @@ class GDToolkit extends ImageToolkitBase {
for ($i = 16; $i >= 0; $i -= 8) {
$rgb[] = (($background >> $i) & 0xFF);
}
$background = imagecolorallocatealpha($image->getResource(), $rgb[0], $rgb[1], $rgb[2], 0);
$background = imagecolorallocatealpha($this->getResource(), $rgb[0], $rgb[1], $rgb[2], 0);
}
// Set the background color as transparent if $background is NULL.
else {
// Get the current transparent color.
$background = imagecolortransparent($image->getResource());
$background = imagecolortransparent($this->getResource());
// If no transparent colors, use white.
if ($background == 0) {
$background = imagecolorallocatealpha($image->getResource(), 255, 255, 255, 0);
$background = imagecolorallocatealpha($this->getResource(), 255, 255, 255, 0);
}
}
// Images are assigned a new color palette when rotating, removing any
// transparency flags. For GIF images, keep a record of the transparent color.
if ($image->getType() == IMAGETYPE_GIF) {
$transparent_index = imagecolortransparent($image->getResource());
$transparent_index = imagecolortransparent($this->getResource());
if ($transparent_index != 0) {
$transparent_gif_color = imagecolorsforindex($image->getResource(), $transparent_index);
$transparent_gif_color = imagecolorsforindex($this->getResource(), $transparent_index);
}
}
$image->setResource(imagerotate($image->getResource(), 360 - $degrees, $background));
$this->setResource(imagerotate($this->getResource(), 360 - $degrees, $background));
// GIFs need to reassign the transparent color after performing the rotate.
if (isset($transparent_gif_color)) {
$background = imagecolorexactalpha($image->getResource(), $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']);
imagecolortransparent($image->getResource(), $background);
$background = imagecolorexactalpha($this->getResource(), $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']);
imagecolortransparent($this->getResource(), $background);
}
$image
->setWidth(imagesx($image->getResource()))
->setHeight(imagesy($image->getResource()));
->setWidth(imagesx($this->getResource()))
->setHeight(imagesy($this->getResource()));
return TRUE;
}
@ -134,16 +165,16 @@ class GDToolkit extends ImageToolkitBase {
$width = (int) round($width);
$height = (int) round($height);
$res = $this->createTmp($image, $width, $height);
$res = $this->createTmp($image->getType(), $width, $height);
if (!imagecopyresampled($res, $image->getResource(), 0, 0, $x, $y, $width, $height, $width, $height)) {
if (!imagecopyresampled($res, $this->getResource(), 0, 0, $x, $y, $width, $height, $width, $height)) {
return FALSE;
}
// Destroy the original image and return the modified image.
imagedestroy($image->getResource());
imagedestroy($this->getResource());
$this->setResource($res);
$image
->setResource($res)
->setWidth($width)
->setHeight($height);
return TRUE;
@ -159,7 +190,7 @@ class GDToolkit extends ImageToolkitBase {
return FALSE;
}
return imagefilter($image->getResource(), IMG_FILTER_GRAYSCALE);
return imagefilter($this->getResource(), IMG_FILTER_GRAYSCALE);
}
/**
@ -199,21 +230,29 @@ class GDToolkit extends ImageToolkitBase {
}
/**
* {@inheritdoc}
* Creates a resource from a file.
*
* @param string $source
* String specifying the path of the image file.
* @param array $details
* An array of image details.
*
* @return bool
* TRUE or FALSE, based on success.
*/
public function load(ImageInterface $image) {
$function = 'imagecreatefrom' . image_type_to_extension($image->getType(), FALSE);
if (function_exists($function) && $resource = $function($image->getSource())) {
$image->setResource($resource);
protected function load($source, array $details) {
$function = 'imagecreatefrom' . image_type_to_extension($details['type'], FALSE);
if (function_exists($function) && $resource = $function($source)) {
$this->setResource($resource);
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($image, $image->getWidth(), $image->getHeight());
imagecopy($new_image, $resource, 0, 0, 0, 0, $image->getWidth(), $image->getHeight());
$new_image = $this->createTmp($details['type'], $details['width'], $details['height']);
imagecopy($new_image, $resource, 0, 0, 0, 0, $details['width'], $details['height']);
imagedestroy($resource);
$image->setResource($new_image);
$this->setResource($new_image);
}
return (bool) $image->getResource();
return (bool) $this->getResource();
}
return FALSE;
@ -241,15 +280,15 @@ class GDToolkit extends ImageToolkitBase {
return FALSE;
}
if ($image->getType() == IMAGETYPE_JPEG) {
$success = $function($image->getResource(), $destination, \Drupal::config('system.image.gd')->get('jpeg_quality'));
$success = $function($this->getResource(), $destination, \Drupal::config('system.image.gd')->get('jpeg_quality'));
}
else {
// Always save PNG images with full transparency.
if ($image->getType() == IMAGETYPE_PNG) {
imagealphablending($image->getResource(), FALSE);
imagesavealpha($image->getResource(), TRUE);
imagealphablending($this->getResource(), FALSE);
imagesavealpha($this->getResource(), TRUE);
}
$success = $function($image->getResource(), $destination);
$success = $function($this->getResource(), $destination);
}
// Move temporary local file to remote destination.
if (isset($permanent_destination) && $success) {
@ -265,7 +304,7 @@ class GDToolkit extends ImageToolkitBase {
$details = FALSE;
$data = getimagesize($image->getSource());
if (isset($data) && is_array($data)) {
if (isset($data) && is_array($data) && in_array($data[2], $this->supportedTypes())) {
$details = array(
'width' => $data[0],
'height' => $data[1],
@ -274,14 +313,18 @@ class GDToolkit extends ImageToolkitBase {
);
}
if ($details) {
$this->load($image->getSource(), $details);
}
return $details;
}
/**
* Creates a truecolor image preserving transparency from a provided image.
*
* @param \Drupal\Core\Image\ImageInterface $image
* An image object.
* @param int $type
* An image type represented by a PHP IMAGETYPE_* constant (e.g.
* IMAGETYPE_JPEG, IMAGETYPE_PNG, etc.).
* @param int $width
* The new width of the new image, in pixels.
* @param int $height
@ -290,16 +333,16 @@ class GDToolkit extends ImageToolkitBase {
* @return resource
* A GD image handle.
*/
public function createTmp(ImageInterface $image, $width, $height) {
public function createTmp($type, $width, $height) {
$res = imagecreatetruecolor($width, $height);
if ($image->getType() == IMAGETYPE_GIF) {
if ($type == IMAGETYPE_GIF) {
// Grab transparent color index from image resource.
$transparent = imagecolortransparent($image->getResource());
$transparent = imagecolortransparent($this->getResource());
if ($transparent >= 0) {
// The original must have a transparent color, allocate to the new image.
$transparent_color = imagecolorsforindex($image->getResource(), $transparent);
$transparent_color = imagecolorsforindex($this->getResource(), $transparent);
$transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
// Flood with our new transparent color.
@ -307,7 +350,7 @@ class GDToolkit extends ImageToolkitBase {
imagecolortransparent($res, $transparent);
}
}
elseif ($image->getType() == IMAGETYPE_PNG) {
elseif ($type == IMAGETYPE_PNG) {
imagealphablending($res, FALSE);
$transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
imagefill($res, 0, 0, $transparency);

View File

@ -75,14 +75,15 @@ class ToolkitGdTest extends DrupalUnitTestBase {
* Function for finding a pixel's RGBa values.
*/
function getPixelColor(ImageInterface $image, $x, $y) {
$color_index = imagecolorat($image->getResource(), $x, $y);
$toolkit = $image->getToolkit();
$color_index = imagecolorat($toolkit->getResource(), $x, $y);
$transparent_index = imagecolortransparent($image->getResource());
$transparent_index = imagecolortransparent($toolkit->getResource());
if ($color_index == $transparent_index) {
return array(0, 0, 0, 127);
}
return array_values(imagecolorsforindex($image->getResource(), $color_index));
return array_values(imagecolorsforindex($toolkit->getResource(), $color_index));
}
/**
@ -210,19 +211,19 @@ class ToolkitGdTest extends DrupalUnitTestBase {
);
}
$toolkit = $this->container->get('image.toolkit.manager')->createInstance('gd');
$image_factory = $this->container->get('image.factory')->setToolkit($toolkit);
$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);
$toolkit = $image->getToolkit();
if (!$image) {
$this->fail(String::format('Could not load image %file.', array('%file' => $file)));
continue 2;
}
// All images should be converted to truecolor when loaded.
$image_truecolor = imageistruecolor($image->getResource());
$image_truecolor = imageistruecolor($toolkit->getResource());
$this->assertTrue($image_truecolor, String::format('Image %file after load is a truecolor image.', array('%file' => $file)));
if ($image->getType() == IMAGETYPE_GIF) {
@ -241,7 +242,7 @@ class ToolkitGdTest extends DrupalUnitTestBase {
$correct_dimensions_object = TRUE;
// Check the real dimensions of the image first.
if (imagesy($image->getResource()) != $values['height'] || imagesx($image->getResource()) != $values['width']) {
if (imagesy($toolkit->getResource()) != $values['height'] || imagesx($toolkit->getResource()) != $values['width']) {
$correct_dimensions_real = FALSE;
}

View File

@ -37,7 +37,7 @@ class ToolkitTest extends ToolkitTestBase {
function testLoad() {
$image = $this->getImage();
$this->assertTrue(is_object($image), 'Returned an object.');
$this->assertEqual($this->toolkit->getPluginId(), $image->getToolkitId(), 'Image had toolkit set.');
$this->assertEqual($image->getToolkitId(), 'test', 'Image had toolkit set.');
$this->assertToolkitOperationsCalled(array('load', 'get_info'));
}

View File

@ -22,13 +22,6 @@ abstract class ToolkitTestBase extends WebTestBase {
*/
public static $modules = array('image_test');
/**
* The image toolkit.
*
* @var \Drupal\Core\ImageToolkit\ImageToolkitInterface
*/
protected $toolkit;
/**
* The URI for the file.
*
@ -46,10 +39,6 @@ abstract class ToolkitTestBase extends WebTestBase {
function setUp() {
parent::setUp();
// Use the image_test.module's test toolkit.
$manager = $this->container->get('image.toolkit.manager');
$this->toolkit = $manager->createInstance('test');
// Pick a file for testing.
$file = current($this->drupalGetTestFiles('image'));
$this->file = $file->uri;
@ -69,9 +58,9 @@ abstract class ToolkitTestBase extends WebTestBase {
*/
protected function getImage() {
$image = $this->container->get('image.factory')
->setToolkit($this->toolkit)
->setToolkitId('test')
->get($this->file);
$image->getResource();
$this->assertTrue($image->isExisting(), 'Image was loaded.');
return $image;
}

View File

@ -63,15 +63,26 @@ class TestToolkit extends ImageToolkitBase {
);
}
if ($details) {
$this->load($image->getSource(), $details);
}
return $details;
}
/**
* {@inheritdoc}
* Creates a resource from a file.
*
* @param string $source
* String specifying the path of the image file.
* @param array $details
* An array of image details.
*
* @return bool
* TRUE or FALSE, based on success.
*/
public function load(ImageInterface $image) {
$this->logCall('load', array($image));
return $image;
protected function load($source, array $details) {
$this->logCall('load', array($source, $details));
return TRUE;
}
/**

View File

@ -138,26 +138,11 @@ class ImageTest extends UnitTestCase {
}
/**
* Tests \Drupal\Core\Image\Image::setResource().
* Tests \Drupal\Core\Image\Image::isExisting().
*/
public function testSetResource() {
$resource = fopen($this->image->getSource(), 'r');
$this->image->setResource($resource);
$this->assertEquals($this->image->getResource(), $resource);
// Force \Drupal\Core\Image\Image::hasResource() to return FALSE.
$this->image->setResource(FALSE);
$this->assertNotNull($this->image->getResource());
}
/**
* Tests \Drupal\Core\Image\Image::hasResource().
*/
public function testHasResource() {
$this->assertFalse($this->image->hasResource());
$resource = fopen($this->image->getSource(), 'r');
$this->image->setResource($resource);
$this->assertTrue($this->image->hasResource());
public function testIsExisting() {
$this->assertTrue($this->image->isExisting());
$this->assertTrue(is_readable($this->image->getSource()));
}
/**