DEV Community

HarrisonFord3572
HarrisonFord3572

Posted on

5 Ways to Resize Mobile Image Payloads Before Compression for Predictable Delivery

Short answer: resize classroom promo images to the display size before compressing them, then cache by the transformation parameters. That ordering makes payload size, visual quality, and cache behavior predictable for a Python service generating short edtech promo videos.

The data flow is uncomplicated: an upload keeps its original, a worker creates a bounded derivative for the phone layout, and a delivery layer serves that derivative with an immutable cache key. Compression is the last step in the image transform, not the first guess at a bandwidth budget.

1. Why does resize-before-compression matter for mobile image payloads?

Compression cannot undo excessive pixels. A 4,000 x 3,000 source sent into a 360-pixel card still carries detail the learner cannot see, and its encoder spends CPU and bytes preserving it. Resize first, inspect the resulting dimensions, and then choose a format and quality level. The resulting payload is easier to reason about across a slow 4G connection and a fast classroom Wi-Fi network.

It matters.

I've started with a quality slider because it was the visible control. That produced tiny files for some posters and surprisingly large files for others. The fix was less clever: cap the long edge, record the target box, and only then encode. Three numbers became useful telemetry: source pixels, derivative pixels, and encoded bytes. The long version of this mistake is painful in an edtech catalog: a teacher previews a lesson on a phone, the browser downloads a full poster, the same poster appears in six video scenes, and a cache miss repeats the work for every query variant. By the time someone notices, the logs show only aggregate bandwidth, not which transformation caused it. Recording the dimensions and policy beside the object key turns that mystery into a diff you can test.

2. Five rules for a cacheable transformation pipeline

  1. Keep the original immutable. It is the recovery source when a designer changes a crop or a video storyboard.
  2. Make dimensions explicit. A card-360x203 derivative should never quietly become a 640-pixel image.
  3. Include every visual parameter in the key: width, height, crop mode, format, and encoder quality.
  4. Generate once and reuse. A cache miss may do work, but a hit should only read bytes and metadata.
  5. Measure the tail. Track p95 encoded size and decode time by device class, not only the average.

Here is a small, runnable policy object. It deliberately uses a generic encoder boundary so the same tests can run with Pillow, an image proxy, or a self-hosted worker.

from dataclasses import dataclass


@dataclass(frozen=True)
class ImageVariant:
    width: int
    height: int
    format: str
    quality: int


def variant_key(asset_id: str, variant: ImageVariant) -> str:
    return (
        f"{asset_id}/w{variant.width}-h{variant.height}"
        f"-{variant.format.lower()}-q{variant.quality}"
    )


def choose_variant(box_width: int, box_height: int, pixel_ratio: float) -> ImageVariant:
    width = max(1, round(box_width * min(pixel_ratio, 2.0)))
    height = max(1, round(box_height * min(pixel_ratio, 2.0)))
    return ImageVariant(width, height, "webp", quality=78)
Enter fullscreen mode Exit fullscreen mode

That tiny contract gives an eval harness something concrete to check: requested dimensions, bounded pixel ratio, stable key, and an encoder call whose output format is recorded. It also catches accidental cache fragmentation when one path writes WebP and another writes webp.

3. What should an edtech video pipeline cache?

Cache the image derivative, not a temporary frame directory. A short promo-video job can reference the same course logo, instructor portrait, and lesson card many times. Store those assets under content-addressed or versioned identifiers, then let the video worker read them without regenerating pixels for every scene. Keep a separate job record for the assembled video so image retention and video retention can differ.

The cache key is an API contract. Include the asset version and transformation policy version; otherwise a changed crop can serve yesterday's bytes indefinitely. Send Cache-Control: public, max-age=31536000, immutable only for keys that truly cannot be overwritten. For mutable previews, use a short lifetime and an explicit revalidation path.

4. Failure modes, limits, and the release checklist

Test the ugly inputs: a transparent PNG with a huge canvas, a panoramic banner, an animated source, and a portrait whose focal point is near an edge. Also test a 1-pixel dimension, a missing orientation tag, and a repeated request arriving while the first transform is running. The expected result is a bounded, valid derivative or a clear validation response, never an unbounded original download.

My eval fixtures include a 2G-like bandwidth budget and a 360 CSS-pixel card. Your mileage may vary because decode speed differs sharply between low-end Android devices and recent iPhones. I am not sure one quality value can serve every lesson artwork; collect field data, then tune per format and viewport class.

The catch is that resize-before-compression is not suitable when users must download the original for printing, when a downstream editor needs every source pixel, or when a server-side image service already owns a tested responsive transformation contract. In those cases, keep the original path explicit and use a specialist image pipeline or direct object delivery. The extra derivative cache also costs storage and invalidation work, so a tiny catalog with one fixed viewport may be better served by a single prebuilt size. Stick with a single prebuilt size when that narrower contract is all the product needs.

The operational checklist is short prose: log source and derivative dimensions, encoded bytes, format, cache status, and policy version; alert on sudden p95 growth; pin a representative fixture set in CI; and make deletion remove both the original and every derived key. That is enough to keep a notebook experiment from quietly becoming a bandwidth bill.

References

Top comments (0)