heads up, this is experimental, not a finished or fully working project. it's a fun little thing built on top of a research paper (deepseek-ocr): render bulky context as images and see how far that actually gets you on a normal model. it's lossy, rough in places, and only really works in narrow cases, a toy to poke at, not something to depend on.
There's a claim in the DeepSeek-OCR paper that's hard to let go of: take a page of text, render it to an image, hand that image to a vision model, and read the text back for roughly a tenth of the tokens — with about 97% of it intact. Text, laundered through pixels, coming out cheaper than the text you started with.
Spend enough time in Claude Code and a question suggests itself: could that trick dent the token bill? opticompress is that question chased into code. The idea holds up — just in a much narrower band than it first appears, and charting where it helps versus where it quietly costs more turned out to be most of the work.
In one line: a small local proxy sits between Claude Code and Anthropic's API. When a request is hauling something bulky — a giant file read, a wall of test output, a pasted log — it renders that chunk to a dense image and sends the picture instead of the text. When the math doesn't work out (most of the time), it leaves the request alone and gets out of the way.
The write-up with the actual renders lives at https://stampixel.github.io/opticompress/.
The paper (DeepSeek-OCR, arXiv:2510.18234) trains a model to reconstruct text from a compact grid of vision tokens. The headline that does the hooking: ~97% reconstruction under 10× compression, tailing off to ~60% around 20×. The napkin logic is tempting — the context window in Claude Code isn't full of your typing, it's full of what the tools drag in, so render that as an image and pay less for it. Tempting, and wrong twice before it turned into something that works.
Wrong turn one: a Claude Code hook can't do this. The obvious move is the
UserPromptSubmit hook — grab the outgoing text, swap in an image. Except a hook can only
append text or block the prompt. It can't rewrite it, and it certainly can't inject an
image. (It's a popular ask; it's requested and closed in
claude-code#46761 and
#53330.) The one place the outgoing
request is actually yours to rewrite is the network boundary. Claude Code honors
ANTHROPIC_BASE_URL, so a small proxy sitting there owns the request body on its way out.
That's why opticompress is a proxy, not a plugin.
Wrong turn two — the one that nearly sinks the whole thing — the naive version loses.
Claude doesn't price an image as "one image = N tokens." It prices by area:
ceil(width/28) × ceil(height/28) visual tokens, capped at 1568 on the standard tier, or
4784 (at a 2576px edge) on the high-res readers like Fable 5, Opus 4.8, and Sonnet 5. Run the
arithmetic on a comfortably legible page of ~500 words: that's ~670 text tokens, versus
~1,500–4,800 image tokens once it's rendered large enough to read. You'd pay two to seven
times more to mail your text as a photograph of itself.
The paper's compression only shows up when the pixels get crushed — tiny fonts, thousands of characters per image — and that's exactly where fidelity starts to go. So the honest version of the idea isn't "image your context." It's: image the specific context that's bulky enough and forgiving enough to survive being crushed, and re-check the token math every single time, because most of the time the answer is no.
That one sentence is the whole design. Everything below is just discipline about it.
Claude Code ──▶ optc proxy (127.0.0.1:47821) ──▶ api.anthropic.com
│ on POST /v1/messages:
│ 1. find the bulky blocks (big tool_result / pasted context)
│ 2. render one, measure it: do the image tokens beat the text?
│ 3. if yes → swap in a dense PNG + a text "factsheet" of identifiers
│ 4. if no, or anything looks off → forward the original, untouched
▼
~/.opticompress/events.jsonl (what each turn actually cost)
A few things it does on purpose:
- It only ever touches the bulky, stale stuff. Your live prompt, the model's own output, short blocks, and (by default) the system prompt are never imaged. Those are either precious or too small to win.
- It keeps the exact strings as text. Rendering is lossy, and the failure mode is nasty: the model doesn't error, it quietly misreads a hash or a path and confabulates. So before imaging a block, opticompress pulls out the things you can't afford to get wrong — URLs, SHAs, UUIDs, file paths, flags, version numbers — and ships them as a short text factsheet alongside the image.
- It can't break your session. The entire transform runs inside a try/except. Parse error, render bug, upstream hiccup — anything at all, and it forwards your original bytes as if opticompress weren't there. Worst case is a plain passthrough, never a failed request.
The full details are in docs/ARCHITECTURE.md,
docs/SIZING.md, and docs/HONESTY.md.
Not on PyPI (it's a toy) — install it from source, straight off GitHub:
git clone https://github.com/stampixel/opticompress
cd opticompress
python -m venv .venv && source .venv/bin/activate # or: uv venv && source .venv/bin/activate
pip install -e . # or: uv pip install -e .
optc --versionPython 3.10+. It bundles the Spleen bitmap font (BSD-2-Clause); no other assets, no native build step.
Quickest thing to try (no API key needed) — render some code and see if you can read it:
optc render src/opticompress/render.py --out ./out
open ./out/page-000.png # macOS; otherwise open the PNG however you like
optc bench # density sweep on the tool's own sourceThen point Claude Code at the proxy:
optc proxy
# opticompress proxy on http://127.0.0.1:47821 [models=claude-fable-5 cell=5x8]
#
# ANTHROPIC_BASE_URL=http://127.0.0.1:47821 claudeUse Claude Code exactly as usual. Behind the scenes the proxy images the big tool_result
bodies and pasted context when it pays off, and writes what each turn cost to
~/.opticompress/events.jsonl — so it's easy to check whether it's actually helping rather
than taking the README's word for it.
That depends entirely on how dense you go. Here's a sweep over opticompress's own source
(~68k characters of dense Python) on Fable 5, with both sides measured through Anthropic's
count_tokens endpoint (the plain-text baseline is 27,274 tokens):
cell pages img_tok chars/tok ratio vs text
-------------------------------------------------
5x8 1 3588 19.0 7.6x
8x16 3 11408 6.0 2.4x
12x24 6 25484 2.7 1.1x <- barely beats text; bigger cells lose
At 5×8, one image carries ~19 characters per visual token — about 7.6× cheaper than the same text on dense code. Push the font up to a size you'd read verbatim and the ratio collapses (12×24 is already only 1.1×): that crossover is the DeepSeek-OCR compression/fidelity tradeoff, measured against Claude's own tokenizer. Note this is dense code, the favorable case — sparse prose (~3.5 chars/text-token) compresses far less and often loses, which is exactly what the profitability gate is for.
Token savings only count if the model can still use what it read. Spot-checked live against the API — a benign corpus with five planted facts, sent as the image alone (no factsheet), asking the model to read each value back:
| model | 5×8 | 8×16 | 12×24 |
|---|---|---|---|
| Fable 5 (default) | 5/5 | 5/5 | 5/5 |
| Opus 4.8 (opt-in) | 4/5 | 5/5 | 5/5 |
So on the default Fable 5 + 5×8, the dense render was both ~7.6× cheaper and read back
correctly here; Opus misreads the densest cell, which is why it's opt-in. Two honest caveats:
this is a small spot check (n=5 facts, one corpus, run-to-run variance), and it measures
gist, not verbatim — zero-redundancy strings (hashes, long IDs) still get silently
misread, which is why exact identifiers ride alongside as a text factsheet. Reproduce it with
optc bench --count-tokens and optc render.
(Fable also has a content-safety classifier that occasionally returns a false-positive refusal
on benign infra/security-flavored text — on the plain text too, not just the image. The
proxy's OPTC_FALLBACK re-serves those turns with a fallback model so they don't just fail.)
All of this is pokeable without touching the proxy:
optc render myfile.py --out ./out # dense PNG page(s) + factsheet.txt + a token report
optc render myfile.py --count-tokens # measure the real text baseline via the API
optc bench # the density sweep above, on your own inputReach for it when your sessions run heavy on bulky, low-signal context — long file reads, command dumps, pasted logs, the kind of thing that's mostly there for reference and doesn't need to come back character-perfect. That's where dense rendering wins big and the lossiness doesn't hurt.
Skip it when your context is short, already compact, or full of things you'll need quoted back
exactly. opticompress works that out on its own and passes through, but if the whole workload
looks like that, it's just a proxy doing nothing. There's no shame in --models off.
Every knob is an OPTC_* env var and a matching proxy flag (the flag wins):
| Env | Flag | Default | What it does |
|---|---|---|---|
OPTC_HOST |
--host |
127.0.0.1 |
bind host |
OPTC_PORT |
--port |
47821 |
bind port |
OPTC_UPSTREAM |
--upstream |
https://api.anthropic.com |
Anthropic base URL |
OPTC_MODELS |
--models |
claude-fable-5 |
allowlist (CSV); off disables imaging entirely |
OPTC_CELL |
--cell |
5x8 |
density rung (5x8, 8x16, 12x24, 16x32) |
OPTC_MIN_CHARS |
--min-chars |
6000 |
smallest block worth imaging |
OPTC_IMAGE_SYSTEM |
--image-system |
false |
also image the static system prompt (opt-in) |
OPTC_ENABLED |
--disabled |
true |
master switch |
OPTC_LOG |
--log |
~/.opticompress/events.jsonl |
where telemetry goes |
OPTC_FALLBACK |
--fallback / --no-fallback |
claude-opus-4-8 |
model that transparently re-serves a request if the allowlisted model returns a safety refusal (a false-positive guard on benign content); off to disable |
The allowlist defaults to claude-fable-5 on purpose: dense 5×8 renders lean on the reader
being good at OCR-ish vision, and promising savings on a model that would misread the page is
a bad trade. Add your own with --models a,b once you've checked fidelity.
- It's lossy, and it fails silently. A misread from an image isn't an error, it's a confident wrong answer. That's the whole reason identifiers ride along as text and why byte-exact content should never be imaged. Treat imaged context as "the gist," not "the record."
- It's model-dependent. 5×8 is legible to Fable-class readers; a weaker model needs a
bigger
--cell(and saves less), or should just stay off the allowlist. - The font is Latin-1. CJK, smart quotes, emoji — anything outside ASCII + Latin-1 renders
as a placeholder box and counts as
dropped. Keep that content as text. - A pre-existing image opts the whole request out. If a request already carries an image anywhere (a screenshot, a pasted picture), opticompress leaves the entire request untouched — no compression that turn.
- The interesting constraint in Claude Code isn't the prompt, it's everything the tools pull in around it. That's the token budget worth attacking.
- "An image is expensive" and "an image is cheap" are both true — it's entirely a function of
pixel density against a
ceil(w/28)×ceil(h/28)tokenizer, and the intuition flips somewhere most people never look. - The safest transform is one that's allowed to decline. A gate that leaves savings on the table beats one that occasionally makes things worse, because "occasionally worse" is how a tool that runs on every request loses your trust.
- It isn't compressing anything. Check the model is on the allowlist (default is
claude-fable-5only), that your blocks clear--min-chars(6000), and that they're actually profitable.~/.opticompress/events.jsonllogs apassthrough_reasonsfield that says exactly why each block was skipped. Address already in use. Another proxy already has the port — pass--port.- Claude Code can't connect.
ANTHROPIC_BASE_URLneeds the full URL including the scheme, e.g.http://127.0.0.1:47821. - Text renders as boxes. That's the Latin-1 font limit above. Keep non-Latin content as text.
token_cutis negative in the log. That's the tool being honest — the gate avoids emitting a net-loss image, but telemetry reports what actually happened rather than flattering itself. Try a denser--cell, or accept the workload isn't a fit.- Turn it off without killing the proxy.
--models offdrops it to a plain passthrough.
uv venv && uv pip install -e '.[dev]'
pytest -q # cost math, deterministic rendering, factsheet, transform,
# cache-safety, proxy/SSE, savings, CLIThe idea is entirely DeepSeek-OCR's; opticompress just applies it to Claude's tokenizer.
- Wei, Sun & Li (DeepSeek-AI), DeepSeek-OCR: Contexts Optical Compression, 2025. arXiv:2510.18234
- The encoder lineage the paper builds on: SAM (Kirillov et al., 2023), CLIP (Radford et al., 2021), Vary (Wei et al., 2024); plus the OCR baselines GOT-OCR2.0 and Nougat.
- Anthropic's own docs for the cost model — image tokenization (
ceil(w/28)×ceil(h/28)), the resolution tiers, and thecount_tokensendpoint.
@misc{wei2025deepseekocr,
title = {DeepSeek-OCR: Contexts Optical Compression},
author = {Wei, Haoran and Sun, Yaofeng and Li, Yukun},
year = {2025},
eprint = {2510.18234},
archivePrefix = {arXiv},
primaryClass = {cs.CV},
doi = {10.48550/arXiv.2510.18234},
url = {https://arxiv.org/abs/2510.18234}
}MIT (see LICENSE). Bundles the Spleen font (BSD-2-Clause,
src/opticompress/assets/fonts/LICENSE-spleen).