Drupal 8/9 Image Effects

February 18, 2022 — Sergiu Nagailic (Nikro)
Drupal 8/9 Image Effects
Tags:#Drupal#Planet#Image
Categories:#General
ABOUT THE AUTHOR
Sergiu Nagailic (Nikro) — CTO & Co-founder at HumanFace Tech
Hands-on technical leader with 16+ years across CTO, tech-lead, and product architecture roles. Specializes in AI product delivery, RAG and agentic systems, and technical leadership.
Connect: Twitter | GitHub | LinkedIn | HumanFace Tech

Problem / Motivation:

The problem I wanted to solve was - to apply image styles (especially with the Image Effects module), on images, but dynamically & on-demand, and not use dozens of pre-configured Image Styles.

Why would anyone want something like this? I can give you a couple of examples:

  • Facebook / Twitter Preview image, for every post. Let’s say you want Drupal to generate an image, by using Post Cover, Post Date, Title and potentially slap on some tags - and compile a Facebook compatible final image.
  • Gift Card Generator 🎴 - where users have a set of pre-configured options they may choose, colors, fonts, texts and use some pre-defined layouts to put everything together.
  • Dynamic Watermarks 🖼 - allow users to upload their own watermarks and apply those on top of their images - without creating a custom Image Style per user.
  • Banners - allow users to choose predefined layouts and craft their own banners.
  • Automate SMM - if you have some content that once created should automatically post a Social Media article with an image - you can use this approach as well.
  • Customization of Avatars 🗿 - similar way Facebook does - you can stitch them with text, images, canvases, etc.
  • NFTs! Yeah, you could define traits and use Drupal to assemble vast collections, by programmatically overlaying the traits.
  • Many other ideas, that I’m not currently aware of 😀

There are many ways one might want to achieve these:

  1. [low level] - Use PHP’s GD or ImageMagick library - here’s the full reference.
  2. [mid level] - use an implementation of GDImageToolkitOperationBase (which extends ImageToolkitOperation). The implementations of this class define an execute method that handles the transformation.
  3. [high level] - use an implementation of ConfigurableImageEffectBase (which extends ImageEffectBase). The implementations of these plugins have an applyEffect method.

Me being lazy, I didn’t want to use neither low level nor mid-level implementations. I wanted to use something that an Image Style would use - higher level. I know some devs like to have more control and would pick a lower level approach, but that’s not me (one of the reasons why I use Drupal in 1st place).

The fun part is that “high level”, I mean, using ConfigurableImageEffectBase - means using plugins - now let’s do a quick recap on Plugins and Plugin Managers. 

About Plugins & Plugin Managers:

I’m definitely not the best person to explain what Plugins and Plugin Managers in Drupal are - but here’s an excellent explainer and video 🎥 from Joe Shindelar, on Drupalize.me.

Here’s also a full write-up on api.drupal.org & official docs.

“Plugins are small pieces of functionality that are swappable. Plugins that perform similar functionality are of the same plugin type.”

Drupal uses Plugins & Plugin Managers to create little reusable bits of code, think: Blocks, Entities, Search, etc - and yes, Image Effects (that are used in the Image Styles).

Image Effects - are plugins too:

Yep, Image Effects are plugins tpp, and they have a Plugin Manager as well, that manages them. Here’s an example of all the classes that implement ConfigurableImageEffectBase.

Image
Plugins

We can use all of these. But you can’t just make a new instance of the class and work with it - you have to use a Plugin Manager as a proxy for that. Assuming you’re using a dependency injection (in a service, controller or a custom form):

use Drupal\image\ImageEffectManager;

// [...]

  /**
   * Image effect manager - used to fetch image effects.
   *
   * @var \Drupal\Core\Image\ImageEffectManager
   */
  protected $imageEffectManager;
  
  /**
   * The overridden default constructor.
   */
  public function __construct(ImageEffectManager $image_effect_manager) {
    $this->imageEffectManager = $image_effect_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('plugin.manager.image.effect'),
    );
  }

Once you have your plugin manager (instance of ImageEffectManager), you can request your plugins this way, and apply the effects on a given image:

// Set the options for a given effect.
$extend_image_options = [
  'data' => []
];
    
/** @var \Drupal\image_effects\Plugin\ImageEffect\SetCanvasImageEffect $extend_image */
$extend_image = $this->imageEffectManager->createInstance('image_effects_set_canvas', $extend_image_options);
if (!$extend_image->applyEffect($image)) {
  \Drupal::messenger()->addError('Could not apply the effect - something went wrong');
}

Using Image Effects & Textimage, programmatically & dynamically:

Now, let’s see this in practice - let’s apply some Image Effects & Textimage effects. Let’s install and enable Image Effects first.

Then, we can use something like this:

$extend_image_options = [
  'data' => [
    'canvas_size' => 'exact',
    'canvas_color' => '#000000',
    'exact' => [
      'width' => '800',
      'height' => '600',
      'placement' => 'top-left',
      'x_offset' => 0,
      'y_offset' => 0,
    ],
  ]
];
​​​​​​​
/** @var \Drupal\image_effects\Plugin\ImageEffect\SetCanvasImageEffect $extend_image */
$extend_image = $this->imageEffectManager->createInstance('image_effects_set_canvas', $extend_image_options);
if (!$extend_image->applyEffect($image)) {
  \Drupal::messenger()->addError('Could not apply the effect - something went wrong');
}

This will create a rectangle image 800x600 with a given canvas color. All of these are values so we can set these dynamically.

Now let’s play with text overlays - we need to install and enable the Textimage module. The $data variable for these effects are a bit bigger.

// Set these dynamically, via a form-submit or some other method.
$details = [
  'font_uri' => $fonts_path . '/OpenSans.woff',
  'font_size' => '80',
  'color' => '#ff0000',
  'x' => '50',
  'y' => '100'
];

// Then you can use these below.
$text_overlay_options = [
  'data' => [
    'font'          => [
      'name'                  => 'OpenSans',
      'uri'                   => $details['font_uri'],
      'size'                  => $details['font_size'],
      'angle'                 => 0,
      'color'                 => $details['color'],
      'stroke_mode'           => FALSE,
      'stroke_color'          => '#000000FF',
      'outline_top'           => 0,
      'outline_right'         => 0,
      'outline_bottom'        => 0,
      'outline_left'          => 0,
      'shadow_x_offset'       => 1,
      'shadow_y_offset'       => 1,
      'shadow_width'          => 0,
      'shadow_height'         => 0,
    ],
    'layout'       => [
      'padding_top'           => 0,
      'padding_right'         => 20,
      'padding_bottom'        => 0,
      'padding_left'          => 0,
      'x_offset'              => 0,
      'y_offset'              => 0,
      'background_color'      => NULL,
      'overflow_action'       => 'extend',
      'extended_color'        => NULL,
      'x_pos'                 => $details['x'],
      'y_pos'                 => $details['y'],
    ],
    'text_string'             => $string,
  ]
];
/** @var \Drupal\image_effects\Plugin\ImageEffect\TextOverlayImageEffect $text_overlay */
$text_overlay = $this->imageEffectManager->createInstance('image_effects_text_overlay', $text_overlay_options);
$text_overlay->applyEffect($image);

You can surely make more changes and adopt these to your own needs 🙃 these are just some random examples.

Putting it all together: a tiny demo module:

Okay this definitely took some time - but I’ve finally finished working on a demo module. If you want to see this in action - just head to - https://github.com/Nikro/blog-drupal-demos 

Make sure you have docksal (and hence - docker) installed, and then just clone and run: fin init

Image
Testing Image Effects - Demo

You will be able to navigate to: /admin/config/demo/nikro_image_effects/form-demo - to play with the image effects demo.

This is a small demo:

  • It allows users to select a format that they want - this applies initial cropping, pre-defined image-style (derivative).
  • It allows users to enter a Headline (which will be overlaid on the bottom of the image).
  • It allows users to select a color for text and a color for the background of the text.
  • It will overlay a Favicon from my website, to the top-right corner.
  • Also, I used different fonts for different image formats (facebook vs twitter).

I hope this gives a general idea of how this can be used and for what. One can even use this to craft amazing NFT collections (imagine overlaying various traits) 😉

Note - this was a quick and dirty implementation, if you spot anything that can be improved, please let me know (or, make a fork and a pull-request).