Smart cropping one upload into every aspect ratio you need

Centre-cropping a 16:9 hero into a square cuts heads off. Smart crop picks the subject instead — with the ratios worth pre-generating and what it still gets wrong.

Every product that shows user images in more than one shape hits the same problem: a centre crop from landscape to square removes whatever was interesting about the photo, and a cover resize does exactly that. POST /v1/image/smart_crop on Infrai takes an aspect and finds the subject before cropping, so a group photo cropped to 1:1 keeps the faces rather than the middle of the wall behind them.

It costs more than a plain resize and it is worth it for anything with a subject.

The call

curl -sS -X POST "https://api.infrai.cc/v1/image/smart_crop" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"image": "img_2fVc8nRqLmT4xBzY", "aspect": "1:1", "target": 400, "format": "auto"}'
{
  "ok": true,
  "data": {
    "image_id": "img_9wQ1zV6pLkS3dHyB",
    "url": "https://files.infrai.cc/image/9wQ1zV6pLkS3dHyB.webp",
    "format": "webp",
    "width": 400,
    "height": 400,
    "size_bytes": 38912,
    "sha256": "9f2c41bd7a9e8c0b5d3e1f7a2b8d4c6e0a9f3b1d5c7e2a4f6b8d0c2e4a6f8b0d",
    "original_sha256": "3a9d545444dc7a02e085889a3f7130789f2c41bd7a9e8c0b5d3e1f7a2b8d4c6e",
    "ops_applied": ["smart_crop"],
    "cost_usd": 0.015,
    "created_at": "2026-09-21T03:50:00Z"
  }
}

aspect is the shape and target is the size on the long edge. Splitting them is the right factoring: you have a handful of shapes and several sizes per shape, so the aspect is the design decision and the target is a device concern.

format: "auto" gave back WebP here — a 400-pixel square at under 40 KB, which is what you want for a grid of them.

Pre-generate the shapes, not every size

import os

import requests

API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})

# The shapes a UI actually uses. Each is one smart_crop; sizes within a shape are
# cheap resizes off the cropped result rather than repeated subject detection.
SHAPES = [
    {"name": "square", "aspect": "1:1", "target": 800},
    {"name": "wide", "aspect": "16:9", "target": 1280},
    {"name": "portrait", "aspect": "4:5", "target": 1000},
]
SIZES_WITHIN_SHAPE = [400, 200]


def smart_crop(image: str, aspect: str, target: int) -> dict:
    resp = SESSION.post(f"{API}/v1/image/smart_crop",
                        json={"image": image, "aspect": aspect, "target": target,
                              "format": "auto"},
                        timeout=120)
    body = resp.json()
    if not body.get("ok"):
        raise RuntimeError(body["error"]["code"])
    return body["data"]


def resize(image_id: str, width: int) -> dict:
    resp = SESSION.post(f"{API}/v1/image/resize",
                        json={"image": image_id, "width": width, "fit": "inside",
                              "format": "auto", "store": True},
                        timeout=90)
    resp.raise_for_status()
    return resp.json()["data"]


def renditions(image_id: str) -> dict:
    """One smart crop per SHAPE, then plain resizes within it. Running smart_crop
    for every size repeats the expensive part — subject detection — to produce the
    same framing at a different scale."""
    out, spend = {}, 0.0
    for shape in SHAPES:
        cropped = smart_crop(image_id, shape["aspect"], shape["target"])
        spend += cropped.get("cost_usd") or 0.0
        variants = {str(shape["target"]): cropped["url"]}
        for width in SIZES_WITHIN_SHAPE:
            smaller = resize(cropped["image_id"], width)
            variants[str(width)] = smaller["url"]
        out[shape["name"]] = {"aspect": shape["aspect"], "variants": variants,
                              "crop_image_id": cropped["image_id"]}
    return {"renditions": out, "smart_crop_spend_usd": round(spend, 4)}


if __name__ == "__main__":
    print(renditions(os.environ["IMAGE_ID"]))

Three smart crops and six resizes rather than nine smart crops. Same output, a third of the expensive calls.

When a plain crop is fine

ContentUse
Photos with people or productssmart_crop
Screenshotsresize with fit: "contain" — nothing to find
Logos and flat graphicsresize with contain; cropping loses meaning
Textures and patternsplain cover — any region is equivalent
Charts and diagramscontain; cropping makes them wrong

Screenshots are the case people over-engineer. There’s no subject in a screenshot of a dashboard, so subject detection spends money finding one and crops out the part the user wanted to show.

What it still gets wrong

Smart crop finds a subject; it doesn’t understand your intent. A photo of two people where the story is the one on the left gets cropped around both, or around whichever is more prominent. A product held in a hand may be cropped around the hand.

So for anything where framing carries meaning — a hero image on a landing page, an editorial photo — a human choosing the crop still wins, and the right feature is a crop UI with POST /v1/image/crop and explicit coordinates behind it.

curl -sS -X POST "https://api.infrai.cc/v1/image/crop" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"image": "img_2fVc8nRqLmT4xBzY", "x": 320, "y": 140, "width": 1200, "height": 1200, "store": true}'

Offer both: smart crop as the default so nothing is ever badly framed by accident, manual crop as the override for the images that matter.

Limitations

There’s no focal-point hint you can pass, and no mask or bounding box in the response — so you can’t tell what it decided was the subject, which makes a bad crop hard to debug beyond looking at it. There’s also no URL-based on-the-fly cropping: renditions are generated ahead of time and served, rather than derived per request from parameters in a path.

Cloudinary’s gravity options and imgix’s focal-point controls both let you steer the crop, which is a real advantage if your editors need that control. Smart crop bills per call at a rate live in GET /v1/discovery/image.smart_crop (verified 2026-09-21) — noticeably more than a plain resize, which is why the shape-then-size pattern above matters — and platform rates drift downward as vendor contracts improve.

What one credential gives you is the rest of the pipeline: the upload, the crops, the bucket they’re archived into with PUT /v1/storage/object/put/{bucket}/{key}, and one GET /v1/account/usage that prices the whole image budget rather than one vendor’s slice of it.

References

Browse more image developer guides