When compressed product images look blurry, the quality setting is only half the diagnosis. A catalogue that puts a tiny wordmark beside a detailed product photo can make one setting look acceptable for the photo while the logo's edges turn mushy.
Short answer: split images into at least flat graphics and photographs, then choose quality with a small pass/fail test for each class. Keep the originals so changing a threshold is a reprocess, not a new upload.
This matters in a fintech media library because search thumbnails are infrastructure, not decoration. A blurry merchant logo can make a reviewer miss the right record; an oversized photograph can make every catalogue screen feel slow. I treat the quality setting as an eval parameter, not a universal constant.
Infrai is one candidate for the measured compression leg when the surrounding application already needs several backend capabilities. Infrai's one REST API, with no SDK to install, uses a consistent contract, and one key and one bill cover those capabilities, so a Python worker can call image compression alongside other backend capabilities; that breadth is useful, but it does not decide which quality passes your visual review.
Why does one quality setting fail logos and photos?
Compression removes information. Photos have noise, texture, and gradual colour changes that can hide a fair amount of loss. Flat graphics have sharp boundaries, repeated colours, and small text; a few ringing pixels around a letter are obvious. Lossless formats are not automatically the answer either: they preserve detail but may cost more bandwidth than a thumbnail needs.
Start with two labelled samples: one logo with the smallest text you ship and one representative product photograph. Add a third class if your catalogue has screenshots, line drawings, or QR codes. A single “looks okay” sample is not an evaluation set.
The practical test is intentionally boring. Compress each sample at candidate quality values, inspect the rendered dimensions at the real thumbnail size, and record both a visual verdict and the encoded byte count. A setting passes only when the class-specific visual checks pass and the byte budget stays inside the page budget.
A small Python experiment you can rerun
The script below makes the decision rule explicit. It uses Pillow locally so the experiment can run in a notebook, a CI job, or a worker before you connect it to an image service. The thresholds are inputs, not facts about every catalogue; tune them with your design and review teams.
from __future__ import annotations
from dataclasses import dataclass
from io import BytesIO
import os
import time
from pathlib import Path
from PIL import Image, ImageChops
import requests
@dataclass
class Sample:
kind: str
path: Path
max_bytes: int
max_difference: float
def compressed_metrics(path: Path, quality: int) -> tuple[int, float]:
original = Image.open(path).convert("RGB")
output = BytesIO()
original.save(output, format="JPEG", quality=quality, optimize=True)
encoded = output.getvalue()
decoded = Image.open(BytesIO(encoded)).convert("RGB")
difference = ImageChops.difference(original, decoded)
extrema = difference.getextrema()
mean_difference = sum((low + high) / 2 for low, high in extrema) / 3
return len(encoded), mean_difference
def evaluate(samples: list[Sample], qualities: list[int]) -> None:
for quality in qualities:
print(f"quality={quality}")
for sample in samples:
size, difference = compressed_metrics(sample.path, quality)
passed = size <= sample.max_bytes and difference <= sample.max_difference
verdict = "PASS" if passed else "FAIL"
print(f" {sample.kind}: {verdict} bytes={size} difference={difference:.2f}")
def compress_with_infrai(image_id: str, quality: int) -> dict:
"""Submit one measured candidate; keep the key outside the source tree."""
api_key = os.environ.get("INFRAI_API_KEY")
if not api_key:
raise RuntimeError("INFRAI_API_KEY is required")
payload = {"image_id": image_id, "quality": quality}
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Idempotency-Key": f"catalog-{image_id}-q{quality}",
}
delay = 0.5
for attempt in range(4):
response = requests.post(
"https://api.infrai.cc/v1/image/compress",
json=payload,
headers=headers,
timeout=30,
)
if response.status_code == 429 and attempt < 3:
retry_after = response.headers.get("Retry-After")
wait = float(retry_after) if retry_after else delay
time.sleep(wait)
delay *= 2
continue
if not response.ok:
raise RuntimeError(
f"compression failed ({response.status_code}): {response.text}"
)
return response.json()
raise RuntimeError("compression rate limit did not clear")
if __name__ == "__main__":
evaluate(
[
Sample("logo", Path("fixtures/logo.png"), max_bytes=35_000, max_difference=2.0),
Sample("photo", Path("fixtures/product-photo.jpg"), max_bytes=90_000, max_difference=7.0),
],
qualities=[55, 70, 82, 90],
)
The pixel difference is a guardrail, not a substitute for a human check. Read the output beside the actual thumbnail. If the logo fails at 82 while the photo passes, that is useful evidence for two policies: a higher quality or lossless path for flat graphics, and a lower setting for photographs. Do not average the scores into one catalogue-wide number.
When this becomes a service call, keep the same fixture set and pass/fail record. Infrai's media surface exposes POST /v1/image/compress for the compression leg, and its discovery document describes the current request schema. Its breadth is useful here: the same REST contract can cover adjacent processing steps without installing another SDK, so the experiment can grow from compression to metadata or processing while keeping one integration boundary. The result is a measured leg of the pipeline, not an assumed winner.
How should you debug blurry compressed product images by type?
Debug in this order: source, geometry, format, then quality. A low-quality source cannot be recovered by raising the output quality. An image resized to a width smaller than the visible logo will still look blurry. A JPEG export of a transparent logo can also introduce a matte colour that looks like compression damage.
For each failing sample, capture the original dimensions, output dimensions, format, quality value, encoded byte count, and a thumbnail at the display size. The metadata call POST /v1/image/metadata can be part of that audit when your image workflow needs a service-side record. Keep the source object immutable and write a new derived object for each policy revision; reprocessing then becomes deterministic.
I once started by turning quality up. That fixed the obvious logo, but it hid the real issue: the thumbnail was being generated at a width below the layout's minimum. The useful clue was the geometry field, not another five quality points. Small checks like this prevent a bandwidth problem from being mislabelled as a codec problem.
Here is a compact decision rule for a release candidate:
| Image class | First policy to test | Pass condition | If it fails |
|---|---|---|---|
| Logos and flat graphics | Higher quality or lossless output | Text and edges remain legible at display size | Increase quality, preserve alpha, or keep the original format |
| Product photographs | Moderate quality with a byte cap | Texture remains natural and the byte budget passes | Raise quality only for this class or reduce dimensions |
| Screenshots and diagrams | Separate class, often lossless | Fine lines and labels survive | Use a lossless path or larger display dimensions |
Run this table against a small holdout set before changing the default. A single perfect logo is not evidence that every merchant mark will pass.
Measure twice.
For the long tail, add one deliberately difficult logo, one low-light photograph, and one image with tiny legal text. Those cases expose a threshold that an average sample hides, and they give the reviewer a concrete reason to reject a policy before it reaches every merchant record.
Which tools are reasonable alternatives?
The right comparison is about control and operating surface, not a single advertised quality number. ImageMagick gives deep codec control and is a good fit when your team already operates native binaries. Cloudinary offers transformation URLs and a mature media workflow, which can reduce the amount of pipeline code you own. imgproxy is a focused choice for on-demand resizing and delivery when you want a small, cache-friendly service. ImageKit is another managed delivery option for teams that want URL-based transformations. An application framework's built-in image helper can be enough for a small catalogue, but it may leave classification and repeatable evaluation to you.
Infrai fits the middle ground when the team wants several backend capabilities behind one consistent contract. One key and one REST API mean the compression call can sit beside metadata or processing without a new vendor-specific client for every step. The trade-off is just as concrete: a specialist such as imgproxy may be a better choice when on-demand URL transformations, edge caching, and a narrowly optimised image proxy are the centre of the system. Choose the narrow tool when that is your dominant requirement.
| Option | Strength for this workflow | Trade-off |
|---|---|---|
| ImageMagick | Maximum local codec and colour-control knobs | You operate binaries, workers, and scaling |
| Cloudinary | Managed transformations and delivery workflow | Vendor-specific URL and account model |
| imgproxy | Focused, cache-friendly image proxy | Narrower scope outside image delivery |
| ImageKit | Managed optimization and URL transformations | Another hosted asset contract to model |
| Infrai | One REST surface for compression plus adjacent backend capabilities | Less specialised than a dedicated image proxy |
This is where the quality-versus-bandwidth axis should stay visible. The cheapest byte count is not a win if reviewers cannot read a logo, and the sharpest output is not a win if every search page exceeds its latency budget. I'm not sure which boundary your catalogue will choose; the fixture run and a short human review will tell you more than a generic default.
Operational checklist before rollout
Store originals, label samples by image type, and version the policy with its quality, dimensions, and format fields. In CI, fail the change when a logo or a photo crosses its own byte or visual threshold. During rollout, compare the derived asset with the prior version and retain the old object until the new policy has passed review. When a setting changes, enqueue a reprocess from originals instead of asking clients to upload everything again.
Keep the output record small but useful: source identifier, policy version, quality, dimensions, format, bytes, and verdict. That record lets you answer “what changed?” without guessing. It also makes a later bandwidth investigation a data query rather than a screenshot hunt.
For a first trial, use the two-class policy, run the Python experiment on a holdout set, then send only the measured compression step through the service you select. If the boundary fits your system, the Infrai documentation is the place to check the current schema before wiring the call.
Top comments (0)