Change record status: 
Project: 
Introduced in branch: 
8.x
Introduced in version: 
8.0-alpha11
Description: 

In earlier D8 development cycle, image operations (scale, resize, desaturate, etc.) were hardcoded methods of Image (\Drupal\Core\Image\Image) object. Those methods were calling the corresponding methods from the active image toolkit. Toolkit image operations were hardcoded too. Later in the cycle, the hardcoding was removed from the Image object but remained at the ImageToolkit level.

Developers were not able to add custom operations to an image toolkit. Based on that design, only extending a toolkit was possible in order to implement new operations. For example if they wanted to add new operations to GD toolkit, they needed to extend the GD toolkit. Other modules might have wanted to add other operations to the same GD toolkit, too, and they needed to extend the GD, too (but which one?).

In Drupal 7 this was achieved also in a non-canonical way: contrib modules were declaring functions like image_{toolkit}_{effect}() but code was able to call image_toolkit_invoke() and get rid of what toolkit was active.

This issue was solved by transforming the image operations in plugins. In this way, a contrib module is able to hook into the system rather than extend the system. Instead using a dedicated, hardcoded method, the Image object will call the operation using a single proxy method: ImageInterface::apply().

In any case, for DX convenience, the core methods scale(), scaleAndCrop(), resize(), crop(), rotate(), desaturate() remain as separate methods in ImageInterface and Image, but are just forwarding the arguments to a call to the apply() method.

Before

$image->scale(20, 50);

Now

$image->scale(20, 50);

or

$image->apply('scale', array('x' => 20, 'y' => 50));

A custom operation 'my_op' would be called like

$image->apply('my_op', array('param1' => $p1, 'param2' => $p2));

The call is passed to the toolkit, and that one loads the proper operation plugin.

  • An image toolkit operation plugin must extend ImageToolkitOperationBase base class and must provide in its arguments() method an array of arguments having the following keys: description, required, default. Each argument needs to be documented through the 'description' key.
  • Arguments may be declared optional and defaults can be provided. The toolkit validates all arguments before passing them to the operation.
  • When arguments are not conforming the validation exceptions are thrown.
  • Each image operation toolkit is able to extend this validation/preprocess and provide additional processing to arguments.

Example from the 'rotate' operation plugin:

/**
 * Defines GD2 rotate operation.
 *
 * @ImageToolkitOperation(
 *   id = "gd_rotate",
 *   toolkit = "gd",
 *   operation = "rotate",
 *   label = @Translation("Rotate"),
 *   description = @Translation("Rotates an image by the given number of degrees.")
 * )
 */
class Rotate extends GDImageToolkitOperationBase {

  /**
   * {@inheritdoc}
   */
  protected function arguments() {
    return array(
      'degrees' => array(
        'description' => 'The number of (clockwise) degrees to rotate the image',
      ),
      'background' => array(
        'description' => 'An hexadecimal integer specifying the background color to use for the uncovered area of the image after the rotation. E.g. 0x000000 for black, 0xff00ff for magenta, and 0xffffff for white. For images that support transparency, this will default to transparent. Otherwise it will be white',
        'required' => FALSE,
        'default' => NULL,
      ),
    );
  }

  /**
   * {@inheritdoc}
   */
  protected function execute(array $arguments) {
    ...
  }

}
Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Not done