Skip to content
Published on
·7 min read

Vision-Encoded Text Compression: Tiny Text, Promising Results, Unmeasured Savings

Authors
  • Avatar of Hani Al-Shater
    Name
    Hani Al-Shater
    Twitter

How small can you make a screenshot before a vision model stops reading it?

That was the experiment. The surprising part was how far I could shrink the text and still get a useful transcription. The mistake was treating a rough image-token estimate as a measured API bill.

Correction to the original post: the advertised 8× token savings and comparison with DeepSeek-OCR were not established by these tests. The observations below concern readability on a small set of examples. The cost claim needs a separate measurement.

Try the image with the prompt “read this text and write it down”:

A densely rendered English passage used in the experiment

The source passage contains 1,287 characters. Getting recognizable text out of something this small is interesting even before we say anything about tokens.

The accidental discovery

I was testing LLMs on a summarization task and wanted to include both text and images. I started sending screenshots. They worked surprisingly well.

Then I made them smaller. And smaller. Gemini kept recovering text I would not enjoy reading myself. That suggested a useful question: could a compact visual representation carry a long passage more cheaply than its text representation?

I proposed fine-tuning on low-resolution text. The obvious objection was accuracy: at some point the model would stop reading and start guessing. The idea went on the shelf.

Then DeepSeek-OCR appeared, investigating optical compression of context with a purpose-built model. That was exciting: the research direction was real. But their results and my screenshots answer different experimental questions. A few successful transcriptions do not reproduce a document benchmark.

What the small tests showed

The original experiments used Gemini 2.5 Flash through OpenRouter in October 2025. These are the transcription scores recorded in that post:

Font and vertical scaleOriginally reported accuracy
8px, Y = 1.091.1%
8px, Y = 0.987.8%
8px, Y = 0.899.4%
8px, Y = 0.755.0%
8px, Y = 0.699.5%

Here Y = 0.6 means keeping 60% of the rendered height while leaving the width unchanged. The promising setting was 8px Verdana with that vertical squeeze.

These are exploratory observations, not a validated accuracy benchmark. The original post did not specify an exact scoring function, publish complete prediction/reference pairs, or report repeated trials. For the review passage, it listed only the change 500K500k. If that were the only character error in 1,287 characters, character accuracy would be about 99.92%, not 99.5%. The quoted percentage and the described error need reconciling against the raw output.

The non-monotonic result is also a question, not an explanation. Why did 0.7 fail and 0.6 recover? Resampling, image preprocessing, and model variability are possible contributors. These tests do not tell us which. Calling 0.6 a universal “sweet spot” would turn one surprising observation into a rule we have not earned.

Readability is not token accounting

The original calculation used:

estimated tokens = width × height / 750

It treated that as a shared Claude/Gemini rule. It is not a valid basis for the Gemini claim. Even the arithmetic was wrong: 800 × 30 / 750 = 32, not 40. Neither number establishes what the API actually charged.

Google's token-counting documentation describes model image processing and provides a token-counting API and response usage information. Image size alone does not justify borrowing another model's estimate. For a routed request, keep the provider, exact model identifier, settings, and returned usage together.

A fair cost comparison sends the same task through two paths: ordinary text and the rendered image. Count the complete request, the output, any extra transcription step, and retries needed to meet the same quality threshold. Input-token reduction is not automatically an equal reduction in the total bill.

That experiment could find a saving. It could also find that the image costs more. The results above do not settle it.

The rendering pipeline

The mechanism is simple: render, capture the text element, then resize it vertically. Capture the element rather than the whole browser viewport, or blank space becomes part of the image. Escape the input so a code snippet is rendered as text rather than interpreted as HTML.

from html import escape
from io import BytesIO
from pathlib import Path

from PIL import Image
from playwright.async_api import async_playwright


async def render_compressed_text(text: str, output_path: str, y_scale=0.6):
    if not 0 < y_scale <= 1:
        raise ValueError("y_scale must be in (0, 1]")

    html = f"""
    <html><body style="margin:0;background:white">
      <div id="text" style="width:800px;color:black;font-family:Verdana,sans-serif;
           font-size:8px;line-height:8px;white-space:pre-wrap;
           overflow-wrap:anywhere">{escape(text)}</div>
    </body></html>
    """

    async with async_playwright() as p:
        browser = await p.chromium.launch()
        try:
            page = await browser.new_page(
                viewport={"width": 800, "height": 600}, device_scale_factor=1
            )
            await page.set_content(html)
            await page.evaluate("document.fonts.ready")
            png = await page.locator("#text").screenshot()
        finally:
            await browser.close()

    with Image.open(BytesIO(png)) as source:
        height = max(1, round(source.height * y_scale))
        compressed = source.resize((source.width, height), Image.Resampling.LANCZOS)
        compressed.save(Path(output_path))
        return compressed.size

This is rendering code, not a reproduction of the model results. Verdana must be installed if you want that font; otherwise the browser uses its fallback. Record the actual font and returned image dimensions with every run.

What failed, and what that means

Binary grids. Encoding characters as bits in a pixel grid did not produce useful decoding in these attempts. A compact encoding is no help if the reader does not know the code.

Morse code. The original note listed estimates of 192 versus 321 tokens and then called Morse “more tokens.” That comparison was internally inconsistent, and both the accounting and decoding quality would need a clean retest.

RGB channel splitting. Putting different text into the color channels did not work in the reported attempts. There is no measured saving to claim from it.

Arabic. The tested rendering failed to recover Arabic reliably. Connected letterforms, dots, and diacritics make tiny rendering a different problem. That is a reason to test Arabic separately, not evidence that the language cannot benefit from visual compression.

The original comparison also reported that a Claude 4.5 setup needed a larger font than Gemini 2.5. Without exact model identifiers, common test passages, repeated runs, and measured usage, “Gemini is 4× better” is not an interpretable result. Minimum font size is not a general measure of vision quality.

The experiment worth running next

Use a held-out collection of prose, numbers, code, tables, and Arabic. Fix the rendering configurations before testing the collection. Save every source passage, image, output, model identifier, prompt, and usage record.

For transcription, report character error rate:

CER=S+D+IN,\mathrm{CER}=\frac{S+D+I}{N},

where substitutions, deletions, and insertions are measured against the NN reference characters. State how whitespace and case are handled. Also check critical content separately: a missing minus sign can matter more than a paragraph of punctuation errors.

For summarization or question answering, test those tasks directly. Good transcription on one passage is not a guarantee of good downstream reasoning on a compressed document. Repeat the calls so that one lucky response does not become the headline.

Then plot task quality against measured total cost. That is the comparison the original headline was trying to make.

Why I still like the idea

Text tokens are one representation, not the only possible one. A visual encoder can potentially make a different trade-off between detail and context. DeepSeek-OCR makes that a concrete research question; an off-the-shelf API makes it easy to explore a small version of it.

One direction I would like to investigate is VET-RAG: Vision-Encoded Text Retrieval-Augmented Generation. Retrieve relevant document pages, then use a visual representation where it helps preserve layout or reduce processing cost. Retrieval still has to work; shrinking pages does not solve relevance, and compressing away the crucial sentence is not an optimization.

The attractive question remains: how much useful information can the model recover per unit of computation? The tiny screenshot gives us a reason to investigate. The bill and the error report have to give us the answer.

Additional test material

This second image contains a passage written in the style of quantum-computing news. It is test material, not a sourced news report:

A second densely rendered English test passage

The original note recorded 1,442 source characters and a 99.6% transcription score, mentioning punctuation changes and “for” versus “to.” As with the review example, the full reference/output pair is needed to verify that score. Its old token-savings estimate is withdrawn for the same accounting reason.