Chris@0: streamWrapperManager = $stream_wrapper_manager; Chris@18: if (!$file_system) { Chris@18: @trigger_error('The file_system service must be passed to GDToolkit::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/3006851.', E_USER_DEPRECATED); Chris@18: $file_system = \Drupal::service('file_system'); Chris@18: } Chris@18: $this->fileSystem = $file_system; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Destructs a GDToolkit object. Chris@0: * Chris@0: * Frees memory associated with a GD image resource. Chris@0: */ Chris@0: public function __destruct() { Chris@0: if (is_resource($this->resource)) { Chris@0: imagedestroy($this->resource); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { Chris@0: return new static( Chris@0: $configuration, Chris@0: $plugin_id, Chris@0: $plugin_definition, Chris@0: $container->get('image.toolkit.operation.manager'), Chris@0: $container->get('logger.channel.image'), Chris@0: $container->get('config.factory'), Chris@18: $container->get('stream_wrapper_manager'), Chris@18: $container->get('file_system') Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the GD image resource. Chris@0: * Chris@0: * @param resource $resource Chris@0: * The GD image resource. Chris@0: * Chris@0: * @return \Drupal\system\Plugin\ImageToolkit\GDToolkit Chris@0: * An instance of the current toolkit object. Chris@0: */ Chris@0: public function setResource($resource) { Chris@0: if (!is_resource($resource) || get_resource_type($resource) != 'gd') { Chris@0: throw new \InvalidArgumentException('Invalid resource argument'); Chris@0: } Chris@0: $this->preLoadInfo = NULL; Chris@0: $this->resource = $resource; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves the GD image resource. Chris@0: * Chris@0: * @return resource|null Chris@0: * The GD image resource, or NULL if not available. Chris@0: */ Chris@0: public function getResource() { Chris@0: if (!is_resource($this->resource)) { Chris@0: $this->load(); Chris@0: } Chris@0: return $this->resource; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function buildConfigurationForm(array $form, FormStateInterface $form_state) { Chris@0: $form['image_jpeg_quality'] = [ Chris@0: '#type' => 'number', Chris@0: '#title' => t('JPEG quality'), Chris@0: '#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: '#min' => 0, Chris@0: '#max' => 100, Chris@0: '#default_value' => $this->configFactory->getEditable('system.image.gd')->get('jpeg_quality', FALSE), Chris@0: '#field_suffix' => t('%'), Chris@0: ]; Chris@0: return $form; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { Chris@0: $this->configFactory->getEditable('system.image.gd') Chris@0: ->set('jpeg_quality', $form_state->getValue(['gd', 'image_jpeg_quality'])) Chris@0: ->save(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Loads a GD resource from a file. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE or FALSE, based on success. Chris@0: */ Chris@0: protected function load() { Chris@0: // Return immediately if the image file is not valid. Chris@0: if (!$this->isValid()) { Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: $function = 'imagecreatefrom' . image_type_to_extension($this->getType(), FALSE); Chris@0: if (function_exists($function) && $resource = $function($this->getSource())) { Chris@0: $this->setResource($resource); Chris@0: if (imageistruecolor($resource)) { Chris@0: return TRUE; Chris@0: } Chris@0: else { Chris@0: // Convert indexed images to truecolor, copying the image to a new Chris@0: // truecolor resource, so that filters work correctly and don't result Chris@0: // in unnecessary dither. Chris@0: $data = [ Chris@0: 'width' => imagesx($resource), Chris@0: 'height' => imagesy($resource), Chris@0: 'extension' => image_type_to_extension($this->getType(), FALSE), Chris@0: 'transparent_color' => $this->getTransparentColor(), Chris@0: 'is_temp' => TRUE, Chris@0: ]; Chris@0: if ($this->apply('create_new', $data)) { Chris@0: imagecopy($this->getResource(), $resource, 0, 0, 0, 0, imagesx($resource), imagesy($resource)); Chris@0: imagedestroy($resource); Chris@0: } Chris@0: } Chris@0: return (bool) $this->getResource(); Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isValid() { Chris@0: return ((bool) $this->preLoadInfo || (bool) $this->resource); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function save($destination) { Chris@0: $scheme = file_uri_scheme($destination); Chris@0: // Work around lack of stream wrapper support in imagejpeg() and imagepng(). Chris@0: if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { Chris@0: // If destination is not local, save image to temporary local file. Chris@0: $local_wrappers = $this->streamWrapperManager->getWrappers(StreamWrapperInterface::LOCAL); Chris@0: if (!isset($local_wrappers[$scheme])) { Chris@0: $permanent_destination = $destination; Chris@18: $destination = $this->fileSystem->tempnam('temporary://', 'gd_'); Chris@0: } Chris@0: // Convert stream wrapper URI to normal path. Chris@18: $destination = $this->fileSystem->realpath($destination); Chris@0: } Chris@0: Chris@0: $function = 'image' . image_type_to_extension($this->getType(), FALSE); Chris@0: if (!function_exists($function)) { Chris@0: return FALSE; Chris@0: } Chris@0: if ($this->getType() == IMAGETYPE_JPEG) { Chris@0: $success = $function($this->getResource(), $destination, $this->configFactory->get('system.image.gd')->get('jpeg_quality')); Chris@0: } Chris@0: else { Chris@0: // Always save PNG images with full transparency. Chris@0: if ($this->getType() == IMAGETYPE_PNG) { Chris@0: imagealphablending($this->getResource(), FALSE); Chris@0: imagesavealpha($this->getResource(), TRUE); Chris@0: } Chris@0: $success = $function($this->getResource(), $destination); Chris@0: } Chris@0: // Move temporary local file to remote destination. Chris@0: if (isset($permanent_destination) && $success) { Chris@18: try { Chris@18: $this->fileSystem->move($destination, $permanent_destination, FileSystemInterface::EXISTS_REPLACE); Chris@18: return TRUE; Chris@18: } Chris@18: catch (FileException $e) { Chris@18: return FALSE; Chris@18: } Chris@0: } Chris@0: return $success; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function parseFile() { Chris@0: $data = @getimagesize($this->getSource()); Chris@0: if ($data && in_array($data[2], static::supportedTypes())) { Chris@0: $this->setType($data[2]); Chris@0: $this->preLoadInfo = $data; Chris@0: return TRUE; Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the color set for transparency in GIF images. Chris@0: * Chris@0: * @return string|null Chris@0: * A color string like '#rrggbb', or NULL if not set or not relevant. Chris@0: */ Chris@0: public function getTransparentColor() { Chris@0: if (!$this->getResource() || $this->getType() != IMAGETYPE_GIF) { Chris@0: return NULL; Chris@0: } Chris@0: // Find out if a transparent color is set, will return -1 if no Chris@0: // transparent color has been defined in the image. Chris@0: $transparent = imagecolortransparent($this->getResource()); Chris@0: if ($transparent >= 0) { Chris@0: // Find out the number of colors in the image palette. It will be 0 for Chris@0: // truecolor images. Chris@0: $palette_size = imagecolorstotal($this->getResource()); Chris@0: if ($palette_size == 0 || $transparent < $palette_size) { Chris@0: // Return the transparent color, either if it is a truecolor image Chris@0: // or if the transparent color is part of the palette. Chris@0: // Since the index of the transparent color is a property of the Chris@0: // image rather than of the palette, it is possible that an image Chris@0: // could be created with this index set outside the palette size. Chris@0: // (see http://stackoverflow.com/a/3898007). Chris@0: $rgb = imagecolorsforindex($this->getResource(), $transparent); Chris@0: unset($rgb['alpha']); Chris@0: return Color::rgbToHex($rgb); Chris@0: } Chris@0: } Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getWidth() { Chris@0: if ($this->preLoadInfo) { Chris@0: return $this->preLoadInfo[0]; Chris@0: } Chris@0: elseif ($res = $this->getResource()) { Chris@0: return imagesx($res); Chris@0: } Chris@0: else { Chris@0: return NULL; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getHeight() { Chris@0: if ($this->preLoadInfo) { Chris@0: return $this->preLoadInfo[1]; Chris@0: } Chris@0: elseif ($res = $this->getResource()) { Chris@0: return imagesy($res); Chris@0: } Chris@0: else { Chris@0: return NULL; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the PHP type of the image. Chris@0: * Chris@0: * @return int Chris@0: * The image type represented by a PHP IMAGETYPE_* constant (e.g. Chris@0: * IMAGETYPE_JPEG). Chris@0: */ Chris@0: public function getType() { Chris@0: return $this->type; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the PHP type of the image. Chris@0: * Chris@0: * @param int $type Chris@0: * The image type represented by a PHP IMAGETYPE_* constant (e.g. Chris@0: * IMAGETYPE_JPEG). Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setType($type) { Chris@0: if (in_array($type, static::supportedTypes())) { Chris@0: $this->type = $type; Chris@0: } Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getMimeType() { Chris@0: return $this->getType() ? image_type_to_mime_type($this->getType()) : ''; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getRequirements() { Chris@0: $requirements = []; Chris@0: Chris@0: $info = gd_info(); Chris@0: $requirements['version'] = [ Chris@0: 'title' => t('GD library'), Chris@0: 'value' => $info['GD Version'], Chris@0: ]; Chris@0: Chris@0: // Check for filter and rotate support. Chris@0: if (!function_exists('imagefilter') || !function_exists('imagerotate')) { Chris@0: $requirements['version']['severity'] = REQUIREMENT_WARNING; Chris@0: $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 the PHP manual.'); Chris@0: } Chris@0: Chris@0: return $requirements; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function isAvailable() { Chris@0: // GD2 support is available. Chris@0: return function_exists('imagegd2'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function getSupportedExtensions() { Chris@0: $extensions = []; Chris@0: foreach (static::supportedTypes() as $image_type) { Chris@0: // @todo Automatically fetch possible extensions for each mime type. Chris@0: // @see https://www.drupal.org/node/2311679 Chris@17: $extension = mb_strtolower(image_type_to_extension($image_type, FALSE)); Chris@0: $extensions[] = $extension; Chris@0: // Add some known similar extensions. Chris@0: if ($extension === 'jpeg') { Chris@0: $extensions[] = 'jpg'; Chris@0: $extensions[] = 'jpe'; Chris@0: } Chris@0: } Chris@0: return $extensions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the IMAGETYPE_xxx constant for the given extension. Chris@0: * Chris@0: * This is the reverse of the image_type_to_extension() function. Chris@0: * Chris@0: * @param string $extension Chris@0: * The extension to get the IMAGETYPE_xxx constant for. Chris@0: * Chris@0: * @return int Chris@0: * The IMAGETYPE_xxx constant for the given extension, or IMAGETYPE_UNKNOWN Chris@0: * for unsupported extensions. Chris@0: * Chris@0: * @see image_type_to_extension() Chris@0: */ Chris@0: public function extensionToImageType($extension) { Chris@0: if (in_array($extension, ['jpe', 'jpg'])) { Chris@0: $extension = 'jpeg'; Chris@0: } Chris@0: foreach ($this->supportedTypes() as $type) { Chris@0: if (image_type_to_extension($type, FALSE) === $extension) { Chris@0: return $type; Chris@0: } Chris@0: } Chris@0: return IMAGETYPE_UNKNOWN; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a list of image types supported by the toolkit. Chris@0: * Chris@0: * @return array Chris@0: * An array of available image types. An image type is represented by a PHP Chris@0: * IMAGETYPE_* constant (e.g. IMAGETYPE_JPEG, IMAGETYPE_PNG, etc.). Chris@0: */ Chris@0: protected static function supportedTypes() { Chris@0: return [IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF]; Chris@0: } Chris@0: Chris@0: }