Combining transformers for even more awesome

Imgix is great and all, but it's not exactly Imagick!

— Published on February 14, 2021

Most of the time, Imgix or AWS Serverless is all you need (and more). But there're a few scenarios where you need to whip out good ol' Imagick from your tool belt. This article is about how you can combine the two, and get the best of both worlds.

The most likely scenario you'll encounter is that there's an effect in Imagick/Imager that's not easily replicable in the other services. One of the benefits with Imager's effects API is that it's guaranteed to add the effects in the same order that you pass them in. When it comes to image manipulation, the order you do things in could produce wildly different results. Also, there're effects in Imagick/Imager that doesn't exist in the other transformers.

Another scenario could be that you need to do a transform in several steps (for instance, crop out the top half of the image, then do the actual transforms only on that part),, where the best solution would be to combine a local Imagick transform with one in Imgix.

For the purposes of this article, I'll use an example with an effect to keep things simple.

Switching transformers

But first, let's start with a reminder. Most of us use only one transformer for our projects most of the time. That is, we configure transformer in our imager-x.php config file, or don't, and use the default craft transformer. But if we're to use more than one, we need to know how. And, it's as easy as this:

{# Let's use the craft transformer #}
{% set transforms = craft.imagerx.transformImage(
    image, 
    [{ width: 1000 }, { width: 1400 }, { width: 1800 }], 
    { ratio: 16/9 }, 
    { transformer: 'craft' }) 
%}

{# Let's use the imgix transformer #}
{% set transforms = craft.imagerx.transformImage(
    image, 
    [{ width: 1000 }, { width: 1400 }, { width: 1800 }], 
    { ratio: 16/9 }, 
    { transformer: 'imgix' }) 
%}

Almost all of the config settings in Imager, including transformer, can be overridden through the fourth parameter of transformImage.

What we want to achieve

Let's say our client has a style guide that calls for using a very specific duotone style for images, and you have been asked if you can replicate this effect automatically on the website. You've played around with the effects playground app, and come up with a combination of grayscale, clut (color lookup table) and contrast that produces the desired output. The resulting images look like this:

Grayscale + CLUT + contrast!

The code for this using the craft transformer would be:

{% set transform = craft.imager.transformImage(image, 
    { 
        effects: { 
            grayscale: true, 
            contrast: 2, 
            clut: "gradient:rgba(25, 120, 41, 1)-rgba(255, 255, 255, 1)" 
        } 
    }) 
%}

Now you want to be able to apply this effect to images, but continue using Imgix to resize and deliver the images. Let's get down to business!

Configuring for success

The first thing we need is a new source in Imgix. I'll assume that you've already used Imgix, have a working site with imgix configured as the default transformer, and a default imgixProfile that works. If that's a tall order, either take a second to head over to the documentation and check out the relevant config settings, or just enjoy the ride.

The easiest way to achieve what we want is to create a new Web Proxy source in Imgix. A Web proxy source let's you pass in whatever publicly available image URL, and Imgix will transform it. Usually we opt to use a Web Folder or cloud source for transforming our normal images, since the URL's are shorter and nicer, and we could opt out of using secure URLs if the need was there (Web Proxy sources must have secure URLs, or anyone could use your source to transform images). And even though we could have used a Web Folder source for this, it's always nice to have a Web Proxy source available in your project for scenarios where you want to transform external images (for instance video thumbnails from Youtube).

Add your new source in imager-x.php alongside the Imgix source you already had. Example:

imager-x.php:

'transformer' => 'imgix',
'imgixProfile' => 'default',
'imgixConfig' => [
    'default' => [
        'domain' => 'my-s3-source.imgix.net',
        'useHttps' => true,
        'signKey' => 'xY2xyX3xY2xxY',
        'sourceIsWebProxy' => false,
        'useCloudSourcePath' => true,
        'getExternalImageDimensions' => true,
        'defaultParams' => [
            'auto' => 'compress,format',
        ],
    ],
    'proxy' => [
        'domain' => 'my-web-proxy.imgix.net',
        'useHttps' => true,
        'signKey' => '2xYx1xyXx2Yx',
        'sourceIsWebProxy' => true,
        'useCloudSourcePath' => false,
        'getExternalImageDimensions' => true,
        'defaultParams' => [
            'auto' => 'compress,format',
        ],
    ]
]

Notice that our new source (the second one) has sourceIsWebProxy set to true. Since there's no way of detecting what type an Imgix source is, you need to tell Imager. Also notice imgixProfile which is set to the default config, and that we're using imgix as our default transformer.

Make sure that you have imagerUrl configured to be a full URL. By default it's set to /imager/ which won't work in this case, so make sure to add your site URL, preferably through an environment variable.

Wrapping it up

The rest is simple, really. First, we need to add the necessary effects to our original image using the craft transformer, and the tell Imgix to transform that image using the new Web Proxy source we created.

{% set transform = craft.imager.transformImage(image, 
    { 
        effects: { 
            grayscale: true, 
            contrast: 2, 
            clut: "gradient:rgba(25, 120, 41, 1)-rgba(255, 255, 255, 1)" 
        } 
    }, 
    {}, 
    { transformer: 'craft', jpegQuality: 95 }) 
%}

{% set imgixTransforms = craft.imager.transformImage(transform, 
    [{ width: 800 }, { width: 2000 }], 
    { ratio: 16/9 }, 
    { imgixProfile: 'proxy', fillTransforms: true, fillInterval: 300 }) 
%}

<img sizes="100vw" srcset="{{ imgixTransforms | srcset }}">

There's a few extra things going on here, but the important parts is that in the first transform, transformer is set to craft, and in the second imgixProfile is set to proxy (transformer is omitted since imgix is our default one).

That's it, you can now combine all the features of Imagick and Imgix! 🎉