For those unfamiliar with Cloudinary, it is a cloud service that is focused on image serving, with unique features like transformations, responsive generation, and a speedy CDN for delivery. I have no affiliation with Cloudinary, other than being a fan of their service.
Introduction
One of the big changes in web development over the years has been a shift from "fixed" designs to "responsive". For example, where we used to define layouts or the display of assets with fixed units, we now try to use percentages or other ratio based approaches to adapt to any display size. This is something to be celebrated, and Cloudinary has an entire feature set built around this, but today I want to talk about when the opposite approach is needed.
We often have "cover images", "hero images", and/or images that we need for social media sharing sites, and these frequently have specific desired pixel sizes or aspect ratios. For example:
- Dev.to cover image: 1000 x 420 (see Editor Guide)
- Twitter: (depends) 16:9 (1.77:1), 2:1, 1200x628 (1.91:1)
- Facebook feed: 1.91:1
Finding images that perfectly fit these aspect ratios is often difficult and time-consuming, and manually cropping and resizing existing images to fit, equally so.
What if there was a better way to generate the perfectly sized hero image, with any image as the input?...
Our Goal
So, here is our example goal. We want to produce a 1000 x 420 pixel (2.38:1) cover image for Dev.to, but do so automatically, with a method that accepts any image as the input. If the input aspect ratio does not match the desired output, then the input will be centered, and a blurred background based on the same input shown behind it.
Sample Input:
Sample goal output:
The cover image for this very post was created with the method outlined below! But, you'll see that later.
Building It With Cloudinary
I'm going to walk through how to build this with Cloudinary image transformations, using URL parameters alone. This makes it very easy to use anywhere, with any backend or frontend, since all we need is a URL.
Base Image: https://res.cloudinary.com/demo/image/upload/sample
When you see Bold and Italic text in the URL, that indicates what was added in the step.
Steps:
- Start with the "Base Image":
https://res.cloudinary.com/demo/image/upload/
sample
- Added:
sample
- Added:
- Use image to fill exact desired size
-
https://res.cloudinary.com/demo/image/upload/
c_fill,w_1000,h_420/
sample
- Added:
/c_fill,w_1000,h_420/
-
- Since this is actually going to be the background, apply a blur effect and reduce opacity
-
https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420
,e_blur:1500,o_75
/sample
- Added:
e_blur:1500,o_75
- Blur is on a scale from 1 to 2000
- Opacity is on a scale from 0 to 100 (100 being completely opaque)
-
- Overlay the same image, but limit its size to the "canvas"
-
https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75
/l_sample,c_limit,w_1000,h_420/
sample
- Added:
/l_sample,c_limit,w_1000,h_420/
- Notice how
l_{uploadedId}
is used to generate an image overlay layer.
-
- Add a nice blurred border to our overlay
-
https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420
,e_outline:outer:2:100
/sample
- Added:
e_outline:outer:2:100
- You might notice that the border now pushes the overlay outside the canvas...
-
- Prevent "overflow" caused by new border
- Adding a border to the overlay actually made it larger than the boundaries of the background (base image). We have two options to remedy this:
-
Option A: Re-crop: Since adding a border to the overlay made it larger than the boundaries of the background, we can simply re-crop the output
-
https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100
/c_crop,w_1000,h_420/
sample
- Added:
/c_crop,w_1000,h_420/
- If we wanted to re-crop just the overlay, we could use
fl_layer_apply
to prevent crop action from applying to the entire chain
-
-
Option B: Use the
no_overflow_flag
- This is a super helpful flag to remember! From the docs:
- > Prevents Cloudinary from extending the image canvas beyond the original dimensions when overlaying text and other images.
-
https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100
,fl_no_overflow
/sample
- Added:
fl_no_overflow
- This is a super helpful flag to remember! From the docs:
Final output, using option B:
Extras:
There is still more we can do with this!
- Add compression
- To save some bytes, we can tell Cloudinary to serve as a compressed JPG with a quality of 80%
-
https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100/c_crop,w_1000,h_420
,q_80
/sample
- For our sample image, this gives us a saving of approximately 50%!!!
- Add overlay text
- We can add overlay text, such as the title of the post that our cover image is for
-
https://res.cloudinary.com/demo/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_sample,c_limit,w_1000,h_420,e_outline:outer:2:100,fl_no_overflow
/l_text:Roboto_100_normal_stroke_:Hero%20Image,co_rgb:FFFFFF,bo_8px_solid_black,g_south,y_30/
sample
-
stroke, and border (
bo_
) are used to provide an outline around the text
Wrap-Up
This might have seemed like a bunch of work, but don't forget; now that we have the transformation chain built, we can reuse it across an unlimited number of images, simply replacing {uploadedId}
with our desired input:
const myCloudId = 'acb123';
// pretend `myImages` is an array of hundreds of image IDs
myImages.forEach(uploadedId => {
const heroImage = `https://res.cloudinary.com/${myCloudId}/image/upload/c_fill,w_1000,h_420,e_blur:1500,o_75/l_${uploadedId},c_limit,w_1000,h_420,e_outline:outer:2:100,fl_no_overflow/${uploadedId}`;
saveImage(heroImage);
});
In fact, here it is in action, working its magic across any input I give it (large GIF, give a second to load...):
Nice, right!?
We could also...
- Save this as a Named Transformation
- Once we do this, reusing it becomes as easy as
/image/upload/{transformationName}/{uploadedId}
- Once we do this, reusing it becomes as easy as
- Implement this via Cloudinary API
- Integrate into any backend (WordPress, Gatsby, etc.) for dynamic
meta
image tags to serve to Twitter, FB / OpenGraph, etc. - ... and many more things!
If this was interesting to you, I have a few other Cloudinary related projects that might be worth checking out:
- Cloudinary Cheatsheet - WIP quick cheatsheet
- Desktop Cloud Transform - Transform local images with Cloudinary, with easy drag-and-drop GUI
- Cloudinary WYSIWYG - Visually build Cloudinary transformations with interactive canvas
More About Me:
- πjoshuatz.com
- π¨βπ»dev.to/joshuatz
- π¬@1joshuatz
- πΎgithub.com/joshuatz
Top comments (4)
A few things...
@joshuatz and @joshpuetz , they look very similar at a glance.
Also, great writeup. We use Cloudinary at DEV and it's really simplified things... Though I foresee we may move off due to price scaling and wanting to remove proprietary services as we expand what we're doing with open sourcing of our platform.
Thanks!
That is a fair point about your usage; I feel like I've seen that as a common pattern with services like Cloudinary - bootstrap with it until you reach a certain scale, and then when it makes sense to invest the time to move off it, replicate the results with something you can have more control over / in-house.
Personally, I also have this as a goal; I'd like to learn more about using more "bare-metal" image manipulation tools, like ImageMagick and libvips.
Always nice to meet another Josh!