●SONNET5 — Claude Sonnet 5 is now the default model in Claude Code, with a native 1M-token context and promo pricing through Aug 31●MCPTUNNEL — MCP tunnels arrives as a Research Preview, letting you reach MCP servers inside private networks●IDP — Admins can now provision MCP connectors via their IdP (starting with Okta); Asana, Figma, Linear and more support managed auth●SANDBOX — Claude Managed Agents can now run tool execution in your own self-hosted sandbox instead of Anthropic's infrastructure●FAST — Fast mode for Opus 4.7 is deprecated and will be removed on July 24; migrate to fast mode for Opus 4.8●FIXES — Recent fixes cover public gateway endpoints, a confirm prompt for external git worktrees, and MCP request timeouts●SONNET5 — Claude Sonnet 5 is now the default model in Claude Code, with a native 1M-token context and promo pricing through Aug 31●MCPTUNNEL — MCP tunnels arrives as a Research Preview, letting you reach MCP servers inside private networks●IDP — Admins can now provision MCP connectors via their IdP (starting with Okta); Asana, Figma, Linear and more support managed auth●SANDBOX — Claude Managed Agents can now run tool execution in your own self-hosted sandbox instead of Anthropic's infrastructure●FAST — Fast mode for Opus 4.7 is deprecated and will be removed on July 24; migrate to fast mode for Opus 4.8●FIXES — Recent fixes cover public gateway endpoints, a confirm prompt for external git worktrees, and MCP request timeouts
Full-Size or Downscaled? A Per-Image Resolution Rule for Opus 4.7's High-Resolution Vision
Opus 4.7 finally read the fine texture in my wallpapers, so I sent everything at full size. My weekly image tokens jumped 2.4x. Here is the preflight that decides resolution and model per image, with the measured savings.
When I moved the auto-tagging pipeline for my Dolice wallpaper apps over to Opus 4.7 — released in July 2026 with its stronger high-resolution vision — I could suddenly feel it picking up the fine stuff: the gradients in traditional Japanese patterns, the thin lines in ukiyo-e prints.
Delighted, I started sending every image through at full size. The next week I looked at the input-token bill and stopped. Accuracy really had improved, but the image-input tokens alone had swollen to roughly 2.4x the prior week.
"Just downscale everything" was not the answer either. Splitting landscape from portrait is fine at 512px, but the moment I needed to tell two near-duplicate wallpapers apart, or read small text inside a sample UI image, downscaling broke the result. What I needed was neither "shrink everything" nor "send everything full-size," but a rule that decides, per image, how much resolution that particular image actually demands.
Here is how I built that decision rule, and how much it cut my weekly cost.
Image tokens are set by area — downscaling tunes information, not just cost
The first thing worth internalizing: the tokens Claude bills for an image are driven almost entirely by pixel area. The documented estimate is:
input tokens ≈ (width px × height px) ÷ 750
Images whose long edge exceeds 1568px, or that exceed roughly 1.15M pixels, are downscaled by the API before processing. So past a certain size, sending full-size buys you no extra tokens billed — it just wastes upload bandwidth while the tokens flatten out.
Below are the approximate input tokens per resolution tier that I measured with count_tokens in my wallpaper pipeline. The formula is only an estimate, so in production I strongly recommend measuring with the count_tokens endpoint — it drifts by tens of tokens.
Long edge sent
Approx. pixels
Input tokens (measured)
Input cost per 10k images (Sonnet 5 intro $2/M)
384px
~150k
~200
~$4.0
512px
~260k
~350
~$7.0
768px
~590k
~790
~$15.8
1024px
~1.05M
~1,400
~$28.0
Near the cap (~1.15M px)
~1.15M
~1,540
~$30.8
Building the table is what made it click for me: 384px versus 1024px is a 7x cost difference per 10k images. Sending everything full-size meant I was paying the most expensive row even to classify a landscape. Downscaling stopped feeling like a quality compromise and started feeling like what it is — dropping resolution down to the information the decision actually needs.
One trap: if you accidentally stuff an image into a text field as a base64 string, this area-based billing does not apply and you burn an order of magnitude more tokens. Everything here assumes you pass images as image blocks. I dug into that failure mode in saving tokens with tool-result image blocks.
Where downscaling helps, and where it breaks
Whether you can drop resolution depends on which grain of the image the task is reading. When I sorted my 30 wallpaper categories into "survives downscaling" and "doesn't," they split cleanly into two layers.
The labels that tolerate 512px were the ones looking at overall composition or theme. Landscape, portrait, abstract, seasonal — Opus 4.7 nails those even at 384–512px. In some cases downscaling actually stabilized the result by removing noise.
The labels that need full size were the ones where a fine difference decides the answer. Three cases in particular:
Kind of decision
What downscaling causes
Long edge needed
Fine texture (patterns, gradients, grain effects)
Texture collapses and gets mislabeled as "solid color"
The difference disappears; distinct images called "same"
1024px+
Small text inside the image (sample UI, logos)
Text becomes unreadable; content misread
Near the cap
Opus 4.7's high-resolution improvement pays off precisely in this lower layer. With the previous model, fine texture was shaky even at full size; with 4.7, hand it full resolution and it reads it reliably. Which is the whole point: pay for full size only on the images that benefit from high-resolution vision, and downscale the rest. That line is the backbone of the decision rule.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Image tokens scale with pixel area and flatten near a 1568px long edge and ~1.15M pixels — why you should measure with count_tokens instead of trusting the formula
✦Coarse labels survive downscaling; fine texture and small text need full size. Where downscaling quietly destroys accuracy
✦A two-stage ladder that classifies downscaled first and escalates to full-size plus Opus 4.7 only when needed, with working Python and a week of real numbers
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The core of the design is two-staged: classify cheaply at a downscaled tier first, look at the result, and promote to full size only when needed. Not sending full-size up front is the cost lever — I escalate only when confidence is low or the target is a detail-dependent class.
The preflight that handles downscaling and sending looks like this in Python. Pillow fits the long edge to a chosen tier and passes it to Claude as an image block.
import base64, io, jsonfrom PIL import Imagefrom anthropic import Anthropicclient = Anthropic()# Classes that do NOT survive downscaling, stated up frontFINE_DETAIL_CLASSES = {"japanese_pattern", "gradient", "ukiyoe", "grain"}def encode_at(path: str, long_edge: int) -> dict: """Downscale to the given long edge and return an image-block dict.""" img = Image.open(path).convert("RGB") w, h = img.size scale = long_edge / max(w, h) if scale < 1.0: # never upscale; downscale only img = img.resize((round(w * scale), round(h * scale)), Image.LANCZOS) buf = io.BytesIO() img.save(buf, format="JPEG", quality=90) b64 = base64.standard_b64encode(buf.getvalue()).decode() return { "type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": b64}, }def classify(path: str, long_edge: int, model: str) -> dict: """Classify one image at a given resolution and model; return label + confidence.""" msg = client.messages.create( model=model, max_tokens=200, messages=[{ "role": "user", "content": [ encode_at(path, long_edge), {"type": "text", "text": "Classify this wallpaper into one of 30 categories. " 'Return only JSON: {"label": ..., "confidence": 0.0-1.0}.'}, ], }], ) return json.loads(msg.content[0].text)
The scale < 1.0 guard matters. Upscaling past the original adds no information and only adds tokens. Encode the asymmetry directly: downscaling reduces information, upscaling is pure wasted cost.
A two-stage ladder with abstention
On top of the preflight sits a ladder that promotes the decision step by step. Three rungs: hit it cheaply with downscaled + Sonnet 5, promote doubtful cases to full-size + Opus 4.7, and abstain to a human queue if it still can't decide.
DOWNSCALE, FULLSIZE = 512, 1568SONNET, OPUS = "claude-sonnet-5", "claude-opus-4-7"def decide(path: str) -> dict: # Rung 1: downscaled + Sonnet 5, cheap r = classify(path, DOWNSCALE, SONNET) # Promote only if it touched a detail class or looked unsure need_detail = r["label"] in FINE_DETAIL_CLASSES or r["confidence"] < 0.75 if not need_detail: return {"label": r["label"], "route": "downscale/sonnet", "conf": r["confidence"]} # Rung 2: full-size + Opus 4.7 high-resolution vision r2 = classify(path, FULLSIZE, OPUS) if r2["confidence"] >= 0.85: return {"label": r2["label"], "route": "fullsize/opus", "conf": r2["confidence"]} # Rung 3: still undecided -> abstain to a human queue return {"label": None, "route": "abstain", "conf": r2["confidence"]}
If it finishes at rung 1, that is ~350 tokens for the image. Even when rung 2 exists, most images stop at rung 1, so you only pay the full-size cost on images that truly need the detail. The two thresholds, 0.75 and 0.85, are best derived per class from a precision target. A single global threshold quietly wrecks your smallest classes — a trap I worked through in don't trust the confidence score — so pair this with that calibration procedure.
Don't treat abstention (rung 3) as a throwaway. I route abstained images into a prioritized human queue, and the labels I assign there feed the next week's threshold retune. An abstention isn't a failure; it's an input that grows the rule.
Before and after — from full-size everything to a resolution rule
I compared the same one week of volume (~9,400 images) before and after the switch.
Metric
Before (full-size + Opus for all)
After (resolution rule)
Images sent full-size
9,400 (100%)
1,860 (~20%)
Total image input tokens
~14.47M
~5.53M
Weekly image input cost
~$60
~$22
Precision on detail-dependent classes
0.94
0.94
Abstentions to human queue
~40/week
~52/week
Image input cost dropped from ~$60/week to ~$22/week, about 63%. As intended, precision on the detail-dependent classes that actually drive quality held at 0.94. That is the result of paying for full size on only the 20% of images that need it and downscaling the other 80%.
Abstentions ticking up from ~40 to ~52 per week is images honestly raising a hand when downscaling left them unsure — I read that as healthy, not worse. I didn't trade accuracy for a lower bill; I re-chose where to spend. That is the one thing I most want to pass on from this design. If you want the ground floor of the wallpaper classifier itself, I wrote it up in running batch classification across 30 wallpaper categories.
Your next step
Take your own classification task and measure how much the same 50 images shift between 512px and full size. If they barely move, your task can move to downscaling today. Put only the classes that do shift into FINE_DETAIL_CLASSES, and the skeleton of this decision rule carries over as-is.
Because wallpapers live and die by detail, I had defaulted to "everything full-size" — but once I measured, full size was only genuinely required on 20% of them. Measurement is what gives you the nerve to drop resolution one notch.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.