DEV Community

Andrey Altrouter
Andrey Altrouter

Posted on

The same phone photo costs 1,120 tokens on Gemini and 14,364 on GPT-5.6

A while ago I wrote about what it costs to generate an image or a video. This post is about the opposite direction: the picture you send into a model so it can read a screenshot, an invoice, a photo of a whiteboard. That side has no price list at all. It's billed as ordinary input tokens, and each vendor turns pixels into tokens with its own formula.

Two words first. A token is the unit models bill by, priced per million. Vision input means an image inside your prompt: the model reads it, it doesn't draw it. The image never shows up as text, yet it lands in usage.prompt_tokens like a paragraph would.

Three vendors, three rulers

All formulas below are from the vendors' own docs as of October 2026. They change, and they already differ by model within one vendor.

OpenAI (GPT-5.4 and newer) cuts the image into 32×32-pixel squares called patches and charges 1.2 tokens per patch on GPT-5.6. The knob is detail: low shrinks the image to fit 512×512 first, high fits it into 2048px and caps it at 2,500 patches, and auto, the default when you leave the field out, behaves like original: no downscale until 65,535px per side. Older models such as GPT-4o still use the previous rule: 512px tiles at 170 tokens each plus 85 base.

Anthropic counts 28×28 patches, one token each. Older docs phrased this as "width × height / 750", and 28² = 784 is the same idea. On Claude 4.7 and later the image is downscaled to fit 2,576px on the long edge and 4,784 tokens, so a single image has a ceiling.

Google Gemini 3 ignores pixel count. Each image costs a flat amount set by media_resolution: 280 tokens at low, 560 at medium, 1,120 at high, which is also the default. Gemini 2.5 used yet another rule: 258 tokens per 768px tile.

Same price, 13x apart

To isolate the formula, I picked three models with the same official input price, $2.00 per million tokens: GPT-5.6, Claude Sonnet 5.5 and Gemini 3.1 Pro. Then I ran four typical images through each vendor's documented arithmetic, every setting at its default:

Image GPT-5.6 (auto) Claude Sonnet 5.5 Gemini 3.1 Pro
Icon, 512×512 308 361 1,120
Screenshot, 1920×1080 2,448 2,691 1,120
Phone photo, 4032×3024 14,364 4,740 1,120
Same photo resized to 1024×768 922 1,036 1,120

At $2 per million, the raw phone photo costs $28.73 per thousand images on GPT-5.6, $9.48 on Claude and $2.24 on Gemini. The icon flips the order: Gemini is the most expensive there, at 3.6x OpenAI's count. There's no vendor that is "cheap for images". There's a formula that is cheap for your image sizes.

You don't need my table. The arithmetic fits on one screen:

import math

def gpt56_auto(w, h):          # 32px patches x 1.2, no downscale by default
    return math.ceil(math.ceil(w / 32) * math.ceil(h / 32) * 1.2)

def claude_hires(w, h):        # 28px patches, 1 token each (before any downscale)
    return math.ceil(w / 28) * math.ceil(h / 28)

GEMINI3_DEFAULT = 1120         # flat, regardless of size

for w, h in [(512, 512), (1920, 1080), (4032, 3024), (1024, 768)]:
    print(w, h, gpt56_auto(w, h), claude_hires(w, h), GEMINI3_DEFAULT)
Enter fullscreen mode Exit fullscreen mode

One caveat on the code: claude_hires skips the downscale step, so for the raw photo it prints 15,552 instead of the 4,740 Claude actually bills. Anthropic's vision docs include a reference function for the exact resized size.

The fix: decide the pixels yourself

Look at the last row of the table. Once the photo is 1024×768, all three vendors land between 922 and 1,120 tokens. The spread that mattered came from sending 12 megapixels a model didn't need. A receipt, a chart or a UI screenshot is readable at around 1,000px on the long side. Resize on your side, before the request:

from PIL import Image
import base64, io

img = Image.open("photo.jpg")
img.thumbnail((1024, 1024))               # keeps aspect ratio, never upscales
buf = io.BytesIO(); img.save(buf, "JPEG", quality=85)
b64 = base64.b64encode(buf.getvalue()).decode()

content = [
    {"type": "text", "text": "What is the total on this receipt?"},
    {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}", "detail": "low"}},
]
Enter fullscreen mode Exit fullscreen mode

The detail: "low" field is the second lever, and it only exists on OpenAI. It caps GPT-5.6 at 512px: the phone photo drops to 231 tokens, the screenshot to 173. It's the right choice for "is there a cat in this picture" and the wrong one for reading 10px text in a screenshot. Gemini's equivalent is media_resolution: low (280 tokens). Anthropic has no flag; the resize is the flag.

What this doesn't solve

I didn't check these numbers against live usage.prompt_tokens for this post. They're the docs' arithmetic, and OpenAI itself warns that billing can be off by a token from the estimate. A resize trades cost for legibility: small print that survives at 2048px may not survive at 1024. And images resent on every turn of a chat get billed on every turn. Resizing shrinks each copy, it doesn't remove the copies.

The prices above are official list prices. We resell these three models below them (GPT-5.6 and Sonnet 5.5 at $1.70, Gemini 3.1 Pro at $1.46 per million input), which cuts every row by the same percentage and leaves the 13x spread untouched. A discount is a percentage; a resize is a multiplier. Do the resize first.

Open your logs and find the largest image you sent last week. Divide its prompt_tokens by 1,000. If the result is in double digits, a thumbnail() call is the cheapest optimization you'll ship this month.

Top comments (0)