annotate core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php @ 14:1fec387a4317

Update Drupal core to 8.5.2 via Composer
author Chris Cannam
date Mon, 23 Apr 2018 09:46:53 +0100
parents 4c8ae668cc8c
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\system\Plugin\ImageToolkit;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\Color;
Chris@0 6 use Drupal\Component\Utility\Unicode;
Chris@0 7 use Drupal\Core\Config\ConfigFactoryInterface;
Chris@0 8 use Drupal\Core\Form\FormStateInterface;
Chris@0 9 use Drupal\Core\ImageToolkit\ImageToolkitBase;
Chris@0 10 use Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface;
Chris@0 11 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
Chris@0 12 use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
Chris@0 13 use Psr\Log\LoggerInterface;
Chris@0 14 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 15
Chris@0 16 /**
Chris@0 17 * Defines the GD2 toolkit for image manipulation within Drupal.
Chris@0 18 *
Chris@0 19 * @ImageToolkit(
Chris@0 20 * id = "gd",
Chris@0 21 * title = @Translation("GD2 image manipulation toolkit")
Chris@0 22 * )
Chris@0 23 */
Chris@0 24 class GDToolkit extends ImageToolkitBase {
Chris@0 25
Chris@0 26 /**
Chris@0 27 * A GD image resource.
Chris@0 28 *
Chris@0 29 * @var resource|null
Chris@0 30 */
Chris@0 31 protected $resource = NULL;
Chris@0 32
Chris@0 33 /**
Chris@0 34 * Image type represented by a PHP IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG).
Chris@0 35 *
Chris@0 36 * @var int
Chris@0 37 */
Chris@0 38 protected $type;
Chris@0 39
Chris@0 40 /**
Chris@0 41 * Image information from a file, available prior to loading the GD resource.
Chris@0 42 *
Chris@0 43 * This contains a copy of the array returned by executing getimagesize()
Chris@0 44 * on the image file when the image object is instantiated. It gets reset
Chris@0 45 * to NULL as soon as the GD resource is loaded.
Chris@0 46 *
Chris@0 47 * @var array|null
Chris@0 48 *
Chris@0 49 * @see \Drupal\system\Plugin\ImageToolkit\GDToolkit::parseFile()
Chris@0 50 * @see \Drupal\system\Plugin\ImageToolkit\GDToolkit::setResource()
Chris@0 51 * @see http://php.net/manual/function.getimagesize.php
Chris@0 52 */
Chris@0 53 protected $preLoadInfo = NULL;
Chris@0 54
Chris@0 55 /**
Chris@0 56 * The StreamWrapper manager.
Chris@0 57 *
Chris@0 58 * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
Chris@0 59 */
Chris@0 60 protected $streamWrapperManager;
Chris@0 61
Chris@0 62 /**
Chris@0 63 * Constructs a GDToolkit object.
Chris@0 64 *
Chris@0 65 * @param array $configuration
Chris@0 66 * A configuration array containing information about the plugin instance.
Chris@0 67 * @param string $plugin_id
Chris@0 68 * The plugin_id for the plugin instance.
Chris@0 69 * @param array $plugin_definition
Chris@0 70 * The plugin implementation definition.
Chris@0 71 * @param \Drupal\Core\ImageToolkit\ImageToolkitOperationManagerInterface $operation_manager
Chris@0 72 * The toolkit operation manager.
Chris@0 73 * @param \Psr\Log\LoggerInterface $logger
Chris@0 74 * A logger instance.
Chris@0 75 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
Chris@0 76 * The config factory.
Chris@0 77 * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
Chris@0 78 * The StreamWrapper manager.
Chris@0 79 */
Chris@0 80 public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImageToolkitOperationManagerInterface $operation_manager, LoggerInterface $logger, ConfigFactoryInterface $config_factory, StreamWrapperManagerInterface $stream_wrapper_manager) {
Chris@0 81 parent::__construct($configuration, $plugin_id, $plugin_definition, $operation_manager, $logger, $config_factory);
Chris@0 82 $this->streamWrapperManager = $stream_wrapper_manager;
Chris@0 83 }
Chris@0 84
Chris@0 85 /**
Chris@0 86 * Destructs a GDToolkit object.
Chris@0 87 *
Chris@0 88 * Frees memory associated with a GD image resource.
Chris@0 89 */
Chris@0 90 public function __destruct() {
Chris@0 91 if (is_resource($this->resource)) {
Chris@0 92 imagedestroy($this->resource);
Chris@0 93 }
Chris@0 94 }
Chris@0 95
Chris@0 96 /**
Chris@0 97 * {@inheritdoc}
Chris@0 98 */
Chris@0 99 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
Chris@0 100 return new static(
Chris@0 101 $configuration,
Chris@0 102 $plugin_id,
Chris@0 103 $plugin_definition,
Chris@0 104 $container->get('image.toolkit.operation.manager'),
Chris@0 105 $container->get('logger.channel.image'),
Chris@0 106 $container->get('config.factory'),
Chris@0 107 $container->get('stream_wrapper_manager')
Chris@0 108 );
Chris@0 109 }
Chris@0 110
Chris@0 111 /**
Chris@0 112 * Sets the GD image resource.
Chris@0 113 *
Chris@0 114 * @param resource $resource
Chris@0 115 * The GD image resource.
Chris@0 116 *
Chris@0 117 * @return \Drupal\system\Plugin\ImageToolkit\GDToolkit
Chris@0 118 * An instance of the current toolkit object.
Chris@0 119 */
Chris@0 120 public function setResource($resource) {
Chris@0 121 if (!is_resource($resource) || get_resource_type($resource) != 'gd') {
Chris@0 122 throw new \InvalidArgumentException('Invalid resource argument');
Chris@0 123 }
Chris@0 124 $this->preLoadInfo = NULL;
Chris@0 125 $this->resource = $resource;
Chris@0 126 return $this;
Chris@0 127 }
Chris@0 128
Chris@0 129 /**
Chris@0 130 * Retrieves the GD image resource.
Chris@0 131 *
Chris@0 132 * @return resource|null
Chris@0 133 * The GD image resource, or NULL if not available.
Chris@0 134 */
Chris@0 135 public function getResource() {
Chris@0 136 if (!is_resource($this->resource)) {
Chris@0 137 $this->load();
Chris@0 138 }
Chris@0 139 return $this->resource;
Chris@0 140 }
Chris@0 141
Chris@0 142 /**
Chris@0 143 * {@inheritdoc}
Chris@0 144 */
Chris@0 145 public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
Chris@0 146 $form['image_jpeg_quality'] = [
Chris@0 147 '#type' => 'number',
Chris@0 148 '#title' => t('JPEG quality'),
Chris@0 149 '#description' => t('Define the image quality for JPEG manipulations. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
Chris@0 150 '#min' => 0,
Chris@0 151 '#max' => 100,
Chris@0 152 '#default_value' => $this->configFactory->getEditable('system.image.gd')->get('jpeg_quality', FALSE),
Chris@0 153 '#field_suffix' => t('%'),
Chris@0 154 ];
Chris@0 155 return $form;
Chris@0 156 }
Chris@0 157
Chris@0 158 /**
Chris@0 159 * {@inheritdoc}
Chris@0 160 */
Chris@0 161 public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
Chris@0 162 $this->configFactory->getEditable('system.image.gd')
Chris@0 163 ->set('jpeg_quality', $form_state->getValue(['gd', 'image_jpeg_quality']))
Chris@0 164 ->save();
Chris@0 165 }
Chris@0 166
Chris@0 167 /**
Chris@0 168 * Loads a GD resource from a file.
Chris@0 169 *
Chris@0 170 * @return bool
Chris@0 171 * TRUE or FALSE, based on success.
Chris@0 172 */
Chris@0 173 protected function load() {
Chris@0 174 // Return immediately if the image file is not valid.
Chris@0 175 if (!$this->isValid()) {
Chris@0 176 return FALSE;
Chris@0 177 }
Chris@0 178
Chris@0 179 $function = 'imagecreatefrom' . image_type_to_extension($this->getType(), FALSE);
Chris@0 180 if (function_exists($function) && $resource = $function($this->getSource())) {
Chris@0 181 $this->setResource($resource);
Chris@0 182 if (imageistruecolor($resource)) {
Chris@0 183 return TRUE;
Chris@0 184 }
Chris@0 185 else {
Chris@0 186 // Convert indexed images to truecolor, copying the image to a new
Chris@0 187 // truecolor resource, so that filters work correctly and don't result
Chris@0 188 // in unnecessary dither.
Chris@0 189 $data = [
Chris@0 190 'width' => imagesx($resource),
Chris@0 191 'height' => imagesy($resource),
Chris@0 192 'extension' => image_type_to_extension($this->getType(), FALSE),
Chris@0 193 'transparent_color' => $this->getTransparentColor(),
Chris@0 194 'is_temp' => TRUE,
Chris@0 195 ];
Chris@0 196 if ($this->apply('create_new', $data)) {
Chris@0 197 imagecopy($this->getResource(), $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource));
Chris@0 198 imagedestroy($resource);
Chris@0 199 }
Chris@0 200 }
Chris@0 201 return (bool) $this->getResource();
Chris@0 202 }
Chris@0 203 return FALSE;
Chris@0 204 }
Chris@0 205
Chris@0 206 /**
Chris@0 207 * {@inheritdoc}
Chris@0 208 */
Chris@0 209 public function isValid() {
Chris@0 210 return ((bool) $this->preLoadInfo || (bool) $this->resource);
Chris@0 211 }
Chris@0 212
Chris@0 213 /**
Chris@0 214 * {@inheritdoc}
Chris@0 215 */
Chris@0 216 public function save($destination) {
Chris@0 217 $scheme = file_uri_scheme($destination);
Chris@0 218 // Work around lack of stream wrapper support in imagejpeg() and imagepng().
Chris@0 219 if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
Chris@0 220 // If destination is not local, save image to temporary local file.
Chris@0 221 $local_wrappers = $this->streamWrapperManager->getWrappers(StreamWrapperInterface::LOCAL);
Chris@0 222 if (!isset($local_wrappers[$scheme])) {
Chris@0 223 $permanent_destination = $destination;
Chris@0 224 $destination = drupal_tempnam('temporary://', 'gd_');
Chris@0 225 }
Chris@0 226 // Convert stream wrapper URI to normal path.
Chris@14 227 $destination = \Drupal::service('file_system')->realpath($destination);
Chris@0 228 }
Chris@0 229
Chris@0 230 $function = 'image' . image_type_to_extension($this->getType(), FALSE);
Chris@0 231 if (!function_exists($function)) {
Chris@0 232 return FALSE;
Chris@0 233 }
Chris@0 234 if ($this->getType() == IMAGETYPE_JPEG) {
Chris@0 235 $success = $function($this->getResource(), $destination, $this->configFactory->get('system.image.gd')->get('jpeg_quality'));
Chris@0 236 }
Chris@0 237 else {
Chris@0 238 // Always save PNG images with full transparency.
Chris@0 239 if ($this->getType() == IMAGETYPE_PNG) {
Chris@0 240 imagealphablending($this->getResource(), FALSE);
Chris@0 241 imagesavealpha($this->getResource(), TRUE);
Chris@0 242 }
Chris@0 243 $success = $function($this->getResource(), $destination);
Chris@0 244 }
Chris@0 245 // Move temporary local file to remote destination.
Chris@0 246 if (isset($permanent_destination) && $success) {
Chris@0 247 return (bool) file_unmanaged_move($destination, $permanent_destination, FILE_EXISTS_REPLACE);
Chris@0 248 }
Chris@0 249 return $success;
Chris@0 250 }
Chris@0 251
Chris@0 252 /**
Chris@0 253 * {@inheritdoc}
Chris@0 254 */
Chris@0 255 public function parseFile() {
Chris@0 256 $data = @getimagesize($this->getSource());
Chris@0 257 if ($data && in_array($data[2], static::supportedTypes())) {
Chris@0 258 $this->setType($data[2]);
Chris@0 259 $this->preLoadInfo = $data;
Chris@0 260 return TRUE;
Chris@0 261 }
Chris@0 262 return FALSE;
Chris@0 263 }
Chris@0 264
Chris@0 265 /**
Chris@0 266 * Gets the color set for transparency in GIF images.
Chris@0 267 *
Chris@0 268 * @return string|null
Chris@0 269 * A color string like '#rrggbb', or NULL if not set or not relevant.
Chris@0 270 */
Chris@0 271 public function getTransparentColor() {
Chris@0 272 if (!$this->getResource() || $this->getType() != IMAGETYPE_GIF) {
Chris@0 273 return NULL;
Chris@0 274 }
Chris@0 275 // Find out if a transparent color is set, will return -1 if no
Chris@0 276 // transparent color has been defined in the image.
Chris@0 277 $transparent = imagecolortransparent($this->getResource());
Chris@0 278 if ($transparent >= 0) {
Chris@0 279 // Find out the number of colors in the image palette. It will be 0 for
Chris@0 280 // truecolor images.
Chris@0 281 $palette_size = imagecolorstotal($this->getResource());
Chris@0 282 if ($palette_size == 0 || $transparent < $palette_size) {
Chris@0 283 // Return the transparent color, either if it is a truecolor image
Chris@0 284 // or if the transparent color is part of the palette.
Chris@0 285 // Since the index of the transparent color is a property of the
Chris@0 286 // image rather than of the palette, it is possible that an image
Chris@0 287 // could be created with this index set outside the palette size.
Chris@0 288 // (see http://stackoverflow.com/a/3898007).
Chris@0 289 $rgb = imagecolorsforindex($this->getResource(), $transparent);
Chris@0 290 unset($rgb['alpha']);
Chris@0 291 return Color::rgbToHex($rgb);
Chris@0 292 }
Chris@0 293 }
Chris@0 294 return NULL;
Chris@0 295 }
Chris@0 296
Chris@0 297 /**
Chris@0 298 * {@inheritdoc}
Chris@0 299 */
Chris@0 300 public function getWidth() {
Chris@0 301 if ($this->preLoadInfo) {
Chris@0 302 return $this->preLoadInfo[0];
Chris@0 303 }
Chris@0 304 elseif ($res = $this->getResource()) {
Chris@0 305 return imagesx($res);
Chris@0 306 }
Chris@0 307 else {
Chris@0 308 return NULL;
Chris@0 309 }
Chris@0 310 }
Chris@0 311
Chris@0 312 /**
Chris@0 313 * {@inheritdoc}
Chris@0 314 */
Chris@0 315 public function getHeight() {
Chris@0 316 if ($this->preLoadInfo) {
Chris@0 317 return $this->preLoadInfo[1];
Chris@0 318 }
Chris@0 319 elseif ($res = $this->getResource()) {
Chris@0 320 return imagesy($res);
Chris@0 321 }
Chris@0 322 else {
Chris@0 323 return NULL;
Chris@0 324 }
Chris@0 325 }
Chris@0 326
Chris@0 327 /**
Chris@0 328 * Gets the PHP type of the image.
Chris@0 329 *
Chris@0 330 * @return int
Chris@0 331 * The image type represented by a PHP IMAGETYPE_* constant (e.g.
Chris@0 332 * IMAGETYPE_JPEG).
Chris@0 333 */
Chris@0 334 public function getType() {
Chris@0 335 return $this->type;
Chris@0 336 }
Chris@0 337
Chris@0 338 /**
Chris@0 339 * Sets the PHP type of the image.
Chris@0 340 *
Chris@0 341 * @param int $type
Chris@0 342 * The image type represented by a PHP IMAGETYPE_* constant (e.g.
Chris@0 343 * IMAGETYPE_JPEG).
Chris@0 344 *
Chris@0 345 * @return $this
Chris@0 346 */
Chris@0 347 public function setType($type) {
Chris@0 348 if (in_array($type, static::supportedTypes())) {
Chris@0 349 $this->type = $type;
Chris@0 350 }
Chris@0 351 return $this;
Chris@0 352 }
Chris@0 353
Chris@0 354 /**
Chris@0 355 * {@inheritdoc}
Chris@0 356 */
Chris@0 357 public function getMimeType() {
Chris@0 358 return $this->getType() ? image_type_to_mime_type($this->getType()) : '';
Chris@0 359 }
Chris@0 360
Chris@0 361 /**
Chris@0 362 * {@inheritdoc}
Chris@0 363 */
Chris@0 364 public function getRequirements() {
Chris@0 365 $requirements = [];
Chris@0 366
Chris@0 367 $info = gd_info();
Chris@0 368 $requirements['version'] = [
Chris@0 369 'title' => t('GD library'),
Chris@0 370 'value' => $info['GD Version'],
Chris@0 371 ];
Chris@0 372
Chris@0 373 // Check for filter and rotate support.
Chris@0 374 if (!function_exists('imagefilter') || !function_exists('imagerotate')) {
Chris@0 375 $requirements['version']['severity'] = REQUIREMENT_WARNING;
Chris@0 376 $requirements['version']['description'] = t('The GD Library for PHP is enabled, but was compiled without support for functions used by the rotate and desaturate effects. It was probably compiled using the official GD libraries from http://www.libgd.org instead of the GD library bundled with PHP. You should recompile PHP --with-gd using the bundled GD library. See <a href="http://php.net/manual/book.image.php">the PHP manual</a>.');
Chris@0 377 }
Chris@0 378
Chris@0 379 return $requirements;
Chris@0 380 }
Chris@0 381
Chris@0 382 /**
Chris@0 383 * {@inheritdoc}
Chris@0 384 */
Chris@0 385 public static function isAvailable() {
Chris@0 386 // GD2 support is available.
Chris@0 387 return function_exists('imagegd2');
Chris@0 388 }
Chris@0 389
Chris@0 390 /**
Chris@0 391 * {@inheritdoc}
Chris@0 392 */
Chris@0 393 public static function getSupportedExtensions() {
Chris@0 394 $extensions = [];
Chris@0 395 foreach (static::supportedTypes() as $image_type) {
Chris@0 396 // @todo Automatically fetch possible extensions for each mime type.
Chris@0 397 // @see https://www.drupal.org/node/2311679
Chris@0 398 $extension = Unicode::strtolower(image_type_to_extension($image_type, FALSE));
Chris@0 399 $extensions[] = $extension;
Chris@0 400 // Add some known similar extensions.
Chris@0 401 if ($extension === 'jpeg') {
Chris@0 402 $extensions[] = 'jpg';
Chris@0 403 $extensions[] = 'jpe';
Chris@0 404 }
Chris@0 405 }
Chris@0 406 return $extensions;
Chris@0 407 }
Chris@0 408
Chris@0 409 /**
Chris@0 410 * Returns the IMAGETYPE_xxx constant for the given extension.
Chris@0 411 *
Chris@0 412 * This is the reverse of the image_type_to_extension() function.
Chris@0 413 *
Chris@0 414 * @param string $extension
Chris@0 415 * The extension to get the IMAGETYPE_xxx constant for.
Chris@0 416 *
Chris@0 417 * @return int
Chris@0 418 * The IMAGETYPE_xxx constant for the given extension, or IMAGETYPE_UNKNOWN
Chris@0 419 * for unsupported extensions.
Chris@0 420 *
Chris@0 421 * @see image_type_to_extension()
Chris@0 422 */
Chris@0 423 public function extensionToImageType($extension) {
Chris@0 424 if (in_array($extension, ['jpe', 'jpg'])) {
Chris@0 425 $extension = 'jpeg';
Chris@0 426 }
Chris@0 427 foreach ($this->supportedTypes() as $type) {
Chris@0 428 if (image_type_to_extension($type, FALSE) === $extension) {
Chris@0 429 return $type;
Chris@0 430 }
Chris@0 431 }
Chris@0 432 return IMAGETYPE_UNKNOWN;
Chris@0 433 }
Chris@0 434
Chris@0 435 /**
Chris@0 436 * Returns a list of image types supported by the toolkit.
Chris@0 437 *
Chris@0 438 * @return array
Chris@0 439 * An array of available image types. An image type is represented by a PHP
Chris@0 440 * IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG, IMAGETYPE_PNG, etc.).
Chris@0 441 */
Chris@0 442 protected static function supportedTypes() {
Chris@0 443 return [IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF];
Chris@0 444 }
Chris@0 445
Chris@0 446 }