I have solved the task currently without a custom image style. Only custom code:
/**
* Applies an image effects blur, scale and crop.
*
* The result is am image with 2 layers:
* - background: a cropped picture with a blur effect.
* - foreground: overlay with the same image and scale effect.
*
* @param int $width
* Result image width.
* @param int $height
* Result image height.
* @param int $radius
* Result image blur radius.
*/
public function blurScaleAndCrop($width, $height, $radius) {
$image = $this->getImageFactory()->get($this->path);
if (!$image->isValid()) {
throw new \InvalidArgumentException("Image {$this->path} is not valid");
}
// Create the background image.
$image->scaleAndCrop($width, $height);
/** @var \Drupal\system\Plugin\ImageToolkit\GDToolkit $toolkit */
$toolkit = $image->getToolkit();
$resource = $toolkit->getResource();
// Apply blur.
if ($radius) {
for ($i = 0; $i < $radius; $i++) {
imagefilter($resource, IMG_FILTER_GAUSSIAN_BLUR);
}
}
$image->apply('gaussian-blur', ['radius' => $radius]);
// Create the foreground image.
$foreground = $this->getImageFactory()->get($this->path);
if (!$foreground->isValid()) {
throw new \InvalidArgumentException("Image {$this->path} is not valid");
}
$foreground->scale($width, $height);
// Merge the 2 image layers.
$dst_im = $image->getToolkit()->getResource();
$src_im = $foreground->getToolkit()->getResource();
$f_width = $foreground->getWidth();
$f_height = $foreground->getHeight();
$x = ($width - $f_width) / 2;
$y = ($height - $f_height) / 2;
imagecopy($dst_im, $src_im, $x, $y, 0, 0, $f_width, $f_height);
$image->save();
}