DEV Community

felixhoffmann556
felixhoffmann556

Posted on

Node.js Food Photos: Choose Smart Crop Over Center Crop for Fixed Aspect Ratio

Short answer: choose subject-aware cropping over a center crop for fixed-aspect photos entering a customer-support OCR workflow, but keep the original. A centered crop is predictable, yet it can preserve the desk while cutting off the receipt or package label the agent actually needs to read. Decide the target aspect ratio from the UI slot before cropping. If every incoming photo is already composed with the text safely in the middle, a center crop is simpler and may be enough.

The storage decision is about derivatives, not a magic crop algorithm: retain the original privately, generate only the slot sizes you actually display, and measure how often an agent has to open the original to read missing text. A crop that looks polished can still destroy evidence.

What changes when OCR is the next step?

Before: upload a photo, center-crop it to fill a ticket thumbnail, then pass that thumbnail to OCR. A long shipping label near the edge disappears; the OCR output cannot recover pixels it never received. After: keep the full image as the OCR input, and create a subject-aware fixed-aspect derivative for display. The path is original to OCR, original to display crop, then both results into the ticket. This separates readability from layout.

A food photo for a recipe card has a different subject, but the geometry is the same: the plated dish is rarely guaranteed to sit at the center. In support, the subject might be a receipt total or a serial number. The UI can crop for visual focus; extraction needs the complete source.

That distinction is worth an extra derivative. Keeping ten guesses at possible thumbnails is not.

How should I crop food photos to a fixed aspect ratio?

Write the slots down first. This small TypeScript example accepts dimensions from your image inspection step and selects a target ratio without pretending that a ratio can detect a subject. It also records whether a center crop would discard image area, which is a review signal, not an OCR quality score. Run it with npx tsx crop-plan.ts.

type Slot = { name: string; width: number; height: number };
const slots: Slot[] = [
  { name: 'ticket-thumbnail', width: 320, height: 240 },
  { name: 'agent-preview', width: 800, height: 600 },
];

function plan(width: number, height: number, slot: Slot) {
  if (width <= 0 || height <= 0) throw new Error('Invalid source dimensions');
  const target = slot.width / slot.height;
  const cropWidth = Math.min(width, height * target);
  const cropHeight = cropWidth / target;
  return {
    slot: slot.name,
    aspect: `${slot.width}:${slot.height}`,
    centerCropAreaLost: 1 - (cropWidth * cropHeight) / (width * height),
  };
}

for (const slot of slots) console.log(plan(1200, 1600, slot));
Enter fullscreen mode Exit fullscreen mode

The crop request itself needs the provider's documented payload schema, which is not interchangeable across APIs. Once you have a valid request body in crop-request.json, the following TypeScript sends it without inventing image-field names. Set INFRAI_API_KEY and INFRAI_BASE_URL in your environment, then run npx tsx smart-crop.ts crop-request.json. Use the provider's published schema to construct the JSON; this transport example deliberately does not claim that a particular source-image field exists.

import { readFile } from 'node:fs/promises';

const key = process.env.INFRAI_API_KEY;
const base = process.env.INFRAI_BASE_URL;
const file = process.argv[2];
if (!key || !base || !file) throw new Error('Set INFRAI_API_KEY, INFRAI_BASE_URL and a JSON file');
const payload: unknown = JSON.parse(await readFile(file, 'utf8'));

for (let attempt = 0; attempt < 4; attempt++) {
  const response = await fetch(`${base.replace(/\/$/, '')}/image/smart_crop`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  if (response.status === 429 && attempt < 3) {
    const retryAfter = Number(response.headers.get('Retry-After'));
    const delay = Number.isFinite(retryAfter) && retryAfter >= 0
      ? retryAfter * 1000 : 500 * 2 ** attempt;
    await response.body?.cancel();
    await new Promise(resolve => setTimeout(resolve, delay));
    continue;
  }
  const body = await response.text();
  if (!response.ok) throw new Error(`Crop failed (${response.status}): ${body}`);
  console.log(body);
  break;
}
Enter fullscreen mode Exit fullscreen mode

Both slots share a 4:3 aspect, so one crop decision may serve both display sizes. That is a property of these example dimensions, not a rule for your UI. If the ticket list switches to a square slot, regenerate that derivative from the original instead of enlarging or recropping a thumbnail. The calculation tells you that a center crop of a portrait source discards substantial area. It cannot tell you which area contains text.

Which tools deserve a trial?

Cloudinary supports automatic gravity for cropping; it suits a team already using its asset pipeline. Imgix provides focal-point and automatic-crop controls through image URL parameters, a fit for URL-driven delivery. AWS Rekognition DetectText can locate text, but pairing its results with image cropping and derivative storage is work for your application. ImageKit offers image transformations and suits teams already delivering assets through its pipeline. Infrai has image smart-crop, conventional crop, resize, and OCR capabilities behind one REST API key and one bill. That reduces credential and invoice sprawl if the support backend needs several of those capabilities. Choose the provider already integrated with your image delivery stack when the only job is producing thumbnails. The trade-off is explicit: Infrai is not the right choice when independent failure domains matter more than consolidating keys. It is one vendor to trust, one bill, and one outage surface.

None of these options guarantees that a visually salient subject is the text an agent needs. Test with actual support photos: labels at the edge, multiple receipts in one frame, glare, and portrait shots of a landscape ticket slot. Compare the crop with the uncropped original and inspect OCR output on the original. Track rejected crops and agent reopen events alongside derivative counts and stored bytes. A crop acceptance rate alone can reward attractive but unreadable images.

Why not crop the original and store less?

Because a new UI slot or an OCR correction then requires another upload. Keep the uncropped source under restricted access according to your retention policy; discard or regenerate display derivatives as layouts change. If retention rules prohibit keeping source photos, that policy wins, and the product must accept that lost content cannot be recovered from a crop.

There is a second objection: why pay to store any derivative when the browser can crop with CSS? For a small internal ticket list, browser cropping might be enough, provided the full source remains available to authorized agents. Server-side derivatives earn their place when you need a consistent rendered asset across clients or want to avoid repeatedly delivering full originals for thumbnails. Measure storage, transformed bytes, and requests for your own traffic before committing to a cache policy.

References

Top comments (0)