Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

magic-image

Fast Go service. Fetch image from URL, crop pixels off the requested sides, pad the opposite side with white so the canvas keeps its original size. Redis-cached.

Behavior

  • Output dimensions == input dimensions (always).
  • Cropping a side pushes content that way and whitens the opposite strip:
    • left=N cuts N px off the left, adds N px white on the right.
    • right=N cuts N px off the right, adds N px white on the left.
    • top=N cuts N px off the top, adds N px white on the bottom.
    • bottom=N cuts N px off the bottom, adds N px white on the top.
  • Opposite sides combine: left=10&right=10 cuts 20 px of content width.
  • left+right >= width (or top+bottom >= height) → 400 (nothing left).
  • Source format preserved: JPEG→JPEG, PNG→PNG, GIF→GIF. WebP decoded (pure Go) and returned as PNG — stdlib has no WebP encoder.
  • Animated GIFs: first frame only.

API

GET /crop?url=<image-url>[&top=<int>&right=<int>&bottom=<int>&left=<int>][&q=<1-100>]
GET /healthz
  • url (required) — http/https image URL.
  • top, right, bottom, left (default 0) — pixels cropped per side, must be >= 0, clamped to MAX_CROP.
  • q (optional) — JPEG quality override.

Response: the image bytes. X-Cache: HIT|MISS. Cache-Control: public, max-age=86400.

Example:

curl "http://localhost:8080/crop?url=https://example.com/a.jpg&left=40&top=20" -o out.jpg

Example output

Same 1000×1000 source, one crop per side. Content shifts toward the cut; the opposite strip is filled white; the canvas stays 1000×1000.

left=300
left=300 — white on the right
right=300
right=300 — white on the left
top=200
top=200 — white on the bottom
bottom=200
bottom=200 — white on the top

Run

docker compose up --build

Or locally (needs a Redis on localhost:6379):

go run .

Config via env — see .env.example.

Performance / latency

  • Redis cache keyed by sha256(url|top|right|bottom|left|q); hits skip fetch+decode entirely.
  • singleflight coalesces concurrent identical misses into one unit of work (no thundering herd on cold cache).
  • Pooled HTTP transport, body size cap, per-request timeouts.
  • Static CGO_ENABLED=0 binary on distroless — small image, fast cold start.

Benchmarks

Apple M4 Pro (12 cores), 1000×1000 JPEG (~48 KB), left=300, no network (in-process imageproc.Process, the per-MISS CPU cost).

Single call — decode + crop + encode:

Stage Time Share
Process ~13.9ms 100%
JPEG decode ~6.7ms 48%
crop + white ~1.3ms 9%
JPEG encode ~5.9ms 43%

1000 calls fired concurrently from one start barrier (GOMAXPROCS=12):

Metric Value
wall (all 1000) ~1.68s
throughput ~596 img/s
latency avg / p50 ~861ms / ~852ms
latency p95 / p99 ~1.60s / ~1.66s
heap peak ~30 MB

Under a 1k burst the box is CPU-bound: jobs queue behind 12 cores, so per-request latency climbs (~0.85s avg) while sustained throughput holds ~600 img/s. Cache HITs bypass decode/encode entirely (Redis roundtrip only), so steady-state real traffic is far faster — bottleneck is JPEG decode+encode (~91%), not the crop step.

Security

  • SSRF guard: every dial (including redirect hops) is checked; private, loopback, link-local, CGNAT and multicast IPs are blocked. Disable with ALLOW_PRIVATE=true (local testing only).
  • Optional ALLOWED_HOSTS allowlist.
  • Fetch size hard-capped by MAX_IMAGE_BYTES.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages