DEV Community

Cover image for Same platform, two image transforms, opposite rules for how to author them
frank chu
frank chu

Posted on

Same platform, two image transforms, opposite rules for how to author them

I generate every image on my blog, covers and inline charts, from the same HTML-to-screenshot pipeline. I had been treating them as one problem with one set of rules.

They are two problems. The platform runs each through a different transform, and once I read the parameters, the correct authoring advice for the two turns out to be opposite.

Reading the transform out of the URL

Both go through the same image proxy, and the proxy puts its entire configuration in the path. That means you can read exactly what will happen to your file without any documentation.

The cover, from the cover_image field of a live post:

media2.dev.to/dynamic/image/
  width=1000,height=420,fit=cover,gravity=auto,format=auto
  /https%3A%2F%2Fraw.githubusercontent.com%2F...%2Fcover.png
Enter fullscreen mode Exit fullscreen mode

An inline image, from the rendered body_html of another post:

media2.dev.to/dynamic/image/
  width=800,height=,fit=scale-down,gravity=auto,format=auto
  /https%3A%2F%2F...
Enter fullscreen mode Exit fullscreen mode

Both came straight out of the API, no guessing:

import re

a = get_article(ARTICLE_ID)

# the cover transform lives in the cover_image field
cover_params = a["cover_image"].split("/dynamic/image/")[1].split("/")[0]

# the inline transform only appears in the RENDERED html, not in body_markdown
inline_src   = re.findall(r'<img[^>]+src="([^"]+)"', a["body_html"])[0]
inline_params = inline_src.split("/dynamic/image/")[1].split("/")[0]

print("cover :", cover_params)
print("inline:", inline_params)
Enter fullscreen mode Exit fullscreen mode
cover : width=1000,height=420,fit=cover,gravity=auto,format=auto
inline: width=800,height=,fit=scale-down,gravity=auto,format=auto
Enter fullscreen mode Exit fullscreen mode

Note where each one hides. The cover transform is in a top-level field you can read
directly. The inline transform exists only in body_html; body_markdown still holds
your original raw URL, so if you only ever look at the markdown you will never know a
proxy is involved at all.

Three differences, and every one of them changes what you should hand it.

fit=cover with both dimensions

The cover transform pins width and height. fit=cover means fill that exact box and crop whatever does not fit, with gravity=auto choosing the crop.

Consequences:

  • Output is always 1000x420 regardless of input. A higher-resolution source is resampled down and the extra pixels are discarded, which is why rendering covers at 2x device scale buys nothing.
  • Aspect ratio is not preserved, it is enforced. Hand it a square and it crops to 1000x420. Anything important near an edge is at the mercy of an automatic gravity heuristic.

So the rule for covers: author at exactly 1000x420, and do not put anything that matters near the edges.

fit=scale-down with an empty height

The inline transform is a different animal. height= is empty, so only width is constrained, and scale-down means it will shrink an image that exceeds the limit and never enlarge one that does not.

Consequences:

  • A 400px-wide chart is served at 400px. It is not stretched to 800. If your source is narrower than the limit, you shipped a small image and the proxy will not save you.
  • Aspect ratio is preserved, because only one dimension is constrained. A tall chart stays tall.
  • A source wider than 800 is scaled down, so the extra width is discarded, but the height comes along proportionally.

So the rule for inline images: author at at least 800 wide, and let height be whatever the content needs.

The two rules side by side

cover inline
transform width=1000,height=420,fit=cover width=800,height=,fit=scale-down
output size always 1000x420 width capped at 800, height free
too small a source upscaled, blurry served small, no upscale
too large a source wasted bytes width wasted, height preserved
aspect ratio cropped to fit preserved
author it at exactly 1000x420 ≥ 800 wide, any height

The row that matters most is the last one, and I had a single rule where I needed two.

It also resolves something from my chart generator. That tool computes output height from the number of rows, so a twenty-row chart comes out 1660px tall. Under the cover transform that would be crushed into a 420px box and cropped to uselessness. Under the inline transform the height survives untouched, because height is unconstrained. My tall charts were only ever fine because they happen to go inline, not because I designed for it.

COVER_W, COVER_H = 1000, 420    # fit=cover pins both; author exactly
INLINE_MIN_W     = 800          # fit=scale-down never upscales; author >= this
Enter fullscreen mode Exit fullscreen mode

The habit worth stealing

A URL-based image proxy is self-documenting if you read it. The parameters are sitting in the path of every image on the page, and they tell you the resize mode, the target dimensions, the crop behavior, and the format policy, without asking anyone.

I had been looking at that string for weeks as an opaque prefix before my real URL. Ten seconds of parsing it would have told me that my two image types had two different contracts, and saved me from nearly shipping a change that optimized for a transform that does not apply.

If your platform proxies images, go read one of its URLs. What are the actual parameters, and does your generator author to them, or to what you assumed?

Top comments (0)