Guide
Screenshot OCR accuracy test: PNG, JPG, WebP, dark mode and photos
Real numbers from our own run: 22 screenshots saved 8 ways each, plus a text size sweep, all read by the same in-browser OCR this site uses.
Updated September 25, 2026
What we tested and why
Most OCR advice says PNG beats JPG, dark mode is harder, and photos of a screen are worse than screenshots. We wanted numbers for the reader on this site instead of rules of thumb. So we made test screenshots where we know every character, ran each one through the same in-browser OCR that the free screenshot to text tool uses, and counted the mistakes.
This page reports three runs on the same 226 images, all on September 25, 2026. Run 1 is the baseline. Run 2 came after one change: an update to the site's OCR pipeline (change #56 in the site's code), which changed the cleanup filters, how the tool picks between recognition passes, and how it handles price columns. Run 3 came after a second update (change #61), which keeps chat sender lines and greetings, drops stray digits left by chat bubble tails, keeps a dash between two words, and reads unsure words that contain digits a second time. The images, answer keys, scoring, engine, language data, and test machine were the same in all three runs. Figures are copied from each run to two decimals. Nothing is rounded or estimated.
How the test worked
We made 22 base screenshots of five kinds: 5 app and settings screens, 5 blocks of paragraph text, 4 code and terminal snippets, 4 receipts, invoices, and price lists, and 4 chat conversations. They were rendered in Chromium at real screen sizes: a normal desktop display, a 2x laptop display, and a 3x phone display.
Each base screenshot was saved 8 ways: lossless PNG, JPEG at quality 90, 70, 50, and 30, lossy WebP at quality 75, a dark mode version with light text on a dark background, and a simulated phone photo of the screen. That makes 176 images. A text size sweep, covered further down, added 50 more, for 226 images in total.
Because we generated the screenshots, we know the exact text in each one. No line wraps, so the expected text matches what is on screen character for character.
We scored each read two ways. Character error rate (CER) is the number of single-character fixes needed to turn the OCR output into the correct text, divided by the length of the correct text. A fix means adding, removing, or swapping one character. Word error rate (WER) is the same idea counted in whole words. Lower is better, and 0.00% is a perfect read. We also report word recall: the share of correct words that appear anywhere in the output, in any order. It separates words that were lost from words that were read but came back in a different order.
Before scoring, both texts were cleaned the same way. Curly quotes and dashes became plain ones, and every run of spaces and line breaks became a single space. Capital letters and punctuation still count.
We did not swap in a different engine. We built this site from its own code, started it on a test machine, and had headless Chromium, driven by Playwright, drop each image into the homepage tool and read what landed in the output box. That means the same image preparation, recognition passes, and cleanup filters a visitor gets. In runs 2 and 3, when the layout step splits prices away from their rows, the tool also reads the page once more as a single block. In run 3, it also reads each low-confidence word that contains a digit a second time from the image before contrast processing, and keeps the new reading only when it swaps look-alike characters, such as 8 for 0, and the engine is clearly more sure of it. The engine was tesseract.js 6.0.1 with tesseract.js-core v6.0.0 and the English eng 4.0.0_best_int language data, loaded from the same public CDN the live site uses.
All three runs used the same test machine: a Linux virtual machine with 8 Intel Xeon cores running Chromium 153. Timing starts when the tool begins reading an image and stops when the text is ready, with the engine already loaded. A phone or an older laptop will be slower.
Results by image format
Each row averages the same 22 screenshots. The median is the middle screenshot, which one bad read cannot drag around. Time is the average seconds per image on the test machine. This table is run 3, after the second OCR update. Run 2 and the run 1 baseline are in the next two tables.
| Version | Mean CER | Median CER | Mean WER | Word recall | Mean time |
|---|---|---|---|---|---|
| PNG (lossless) | 0.65% | 0.00% | 3.57% | 96.82% | 0.71 s |
| JPEG quality 90 | 2.07% | 0.00% | 4.81% | 96.58% | 0.72 s |
| JPEG quality 70 | 1.31% | 0.00% | 4.31% | 95.83% | 0.72 s |
| JPEG quality 50 | 1.48% | 0.00% | 4.71% | 95.50% | 0.74 s |
| JPEG quality 30 | 2.17% | 0.00% | 5.70% | 94.77% | 0.72 s |
| WebP quality 75 | 0.93% | 0.52% | 4.53% | 95.98% | 0.72 s |
| Dark mode PNG | 1.77% | 0.34% | 4.64% | 95.86% | 1.26 s |
| Simulated phone photo | 9.74% | 4.04% | 17.96% | 85.13% | 1.28 s |
Run 2, after the first OCR update
Run 2 used the same 22 screenshots per row, after the first OCR pipeline update and before the second.
| Version | Mean CER | Median CER | Mean WER | Word recall | Mean time |
|---|---|---|---|---|---|
| PNG (lossless) | 1.16% | 0.47% | 4.72% | 95.81% | 0.75 s |
| JPEG quality 90 | 2.56% | 0.90% | 5.84% | 95.68% | 0.73 s |
| JPEG quality 70 | 1.73% | 0.84% | 5.81% | 94.57% | 0.71 s |
| JPEG quality 50 | 1.84% | 0.72% | 5.82% | 94.53% | 0.73 s |
| JPEG quality 30 | 2.60% | 0.71% | 7.05% | 93.70% | 0.73 s |
| WebP quality 75 | 1.51% | 1.07% | 5.98% | 94.93% | 0.73 s |
| Dark mode PNG | 2.22% | 1.46% | 5.44% | 95.20% | 1.29 s |
| Simulated phone photo | 10.12% | 4.14% | 19.29% | 83.92% | 1.23 s |
Baseline run before the OCR update
Run 1 used the same 22 screenshots per row, before either OCR pipeline update.
| Version | Mean CER | Median CER | Mean WER | Word recall | Mean time |
|---|---|---|---|---|---|
| PNG (lossless) | 2.72% | 1.20% | 7.88% | 93.11% | 0.72 s |
| JPEG quality 90 | 3.56% | 1.78% | 8.27% | 93.42% | 0.72 s |
| JPEG quality 70 | 4.50% | 1.12% | 9.72% | 92.89% | 0.69 s |
| JPEG quality 50 | 4.15% | 1.07% | 9.12% | 92.53% | 0.72 s |
| JPEG quality 30 | 6.40% | 1.68% | 11.39% | 92.16% | 0.71 s |
| WebP quality 75 | 2.65% | 1.38% | 8.54% | 92.36% | 0.73 s |
| Dark mode PNG | 4.92% | 2.51% | 9.80% | 93.00% | 1.28 s |
| Simulated phone photo | 11.07% | 4.14% | 20.46% | 81.79% | 1.22 s |
What the results mean
The second OCR update lowered mean CER and mean WER and raised word recall again for every version. PNG went from 1.16% CER in run 2 to 0.65% in run 3, JPEG at quality 30 from 2.60% to 2.17%, dark mode from 2.22% to 1.77%, and simulated phone photos from 10.12% to 9.74%. Of the 226 images, 74 gave different text in run 3 than in run 2, all 74 had a lower CER, and none scored worse. The 4 chat screenshots in PNG went from 1.84% to 0.00%: run 3 kept a sender line ("You 2:04 PM") and a greeting ("Hi Sam,") that run 2 dropped, and read a capital I that run 2 lost.
The first OCR update lowered mean CER and mean WER and raised word recall for every version. PNG went from 2.72% to 1.16% CER, JPEG at quality 30 from 6.40% to 2.60%, dark mode from 4.92% to 2.22%, and simulated phone photos from 11.07% to 10.12%. Of the 226 images, 110 gave different text in run 2. Of those, 109 had a lower CER than in run 1, one scored the same, and none scored worse.
PNG and WebP came out on top. In run 3, lossless PNG had a mean CER of 0.65% and WebP at quality 75 had 0.93%, against 1.16% and 1.51% in run 2. Plain paragraphs were close to perfect: the 5 paragraph screenshots in PNG had a mean CER of 0.09% in run 3, against 0.43% in run 2 and 0.52% in run 1.
JPEG hurt less after the updates. In run 3, mean CER was 2.07% at quality 90 and 2.17% at quality 30, and word recall went from 96.82% for PNG to 94.77% for JPEG at quality 30. In run 2 those figures were 2.56%, 2.60%, 95.81%, and 93.70%. In run 1 the biggest JPEG losses came from the receipt: heavier compression made the layout step treat the price column as its own block, so the prices came back after the item list instead of beside each item, and the receipt reached 43.12% CER at quality 30. In run 2 the same receipt scored 3.67% at quality 30, with each price beside its item, and in run 3 it scored 1.38%.
The JPEG trend is still not smooth. One account screen scored 16.67% CER at quality 90 and 0.72% at quality 70 in all three runs, because a single row came back out of order. In runs 2 and 3 a form scored 12.22% at quality 70 and 50 because its button label went missing. With 22 screenshots per row, one bad read moves an average.
Dark mode was still a little less accurate and slower. In run 3, mean CER was 1.77%, against 0.65% for the same screenshots in light mode, and the average read took 1.26 seconds against 0.71 seconds. In run 2 it was 2.22% against 1.16%, and 1.29 seconds against 0.75 seconds. In run 1 it was 4.92% against 2.72%, and 1.28 seconds against 0.72 seconds. The tool flips light-on-dark areas before reading, and when it does, it runs all three recognition passes instead of stopping after a confident first pass. OCR for dark mode screenshots covers how to capture dark UI.
Simulated phone photos were still much worse. In run 3, mean CER was 9.74%, mean WER was 17.96%, and word recall was 85.13%, against 10.12%, 19.29%, and 83.92% in run 2. Large phone text suffered most: a phone account screen reached 49.28% CER in all three runs, and a phone chat reached 35.63% in run 3, against 38.13% in runs 1 and 2. Take a real screenshot whenever you can. If you have to photograph a screen, crop tight and hold the camera straight on. How to get better OCR from screenshots and PNG, JPG, and WebP for OCR go further.
If you still have the original PNG, use it. You can convert a PNG screenshot to text directly, or drop any supported image on the homepage tool.
Text size and resolution
Small text is a common worry, so we ran a sweep. We took 5 of the screenshots, one of each kind, and rendered them so the capital letters in body text were 8, 10, 12, 14, 16, 20, and 24 pixels tall, measured on the letter H. For 8, 10, and 12 pixels we also made a copy enlarged 2x before upload, the way you might in an image editor. That makes 50 images, all PNG.
| Version | Capital letter height | Mean CER | Mean WER | Word recall |
|---|---|---|---|---|
| 8 px | 8.00 px | 2.22% | 5.81% | 94.19% |
| 8 px enlarged 2x | 16.00 px | 3.06% | 7.61% | 93.36% |
| 10 px | 10.00 px | 2.42% | 3.75% | 96.25% |
| 10 px enlarged 2x | 20.00 px | 2.29% | 2.70% | 97.79% |
| 12 px | 12.00 px | 2.78% | 6.54% | 93.92% |
| 12 px enlarged 2x | 24.00 px | 1.99% | 3.45% | 96.55% |
| 14 px | 14.00 px | 1.05% | 5.54% | 95.06% |
| 16 px | 16.00 px | 0.98% | 4.03% | 96.58% |
| 20 px | 20.00 px | 3.42% | 3.90% | 96.10% |
| 24 px | 24.00 px | 1.98% | 3.90% | 96.10% |
Text size in runs 1 and 2
The same 50 images in the two earlier runs.
| Version | Capital letter height | Mean CER, run 1 | Mean CER, run 2 | Mean WER, run 1 | Mean WER, run 2 | Word recall, run 1 | Word recall, run 2 |
|---|---|---|---|---|---|---|---|
| 8 px | 8.00 px | 10.75% | 3.35% | 18.18% | 7.67% | 89.00% | 92.33% |
| 8 px enlarged 2x | 16.00 px | 9.49% | 4.28% | 17.23% | 9.98% | 89.36% | 91.49% |
| 10 px | 10.00 px | 8.67% | 2.83% | 15.72% | 5.21% | 88.32% | 95.25% |
| 10 px enlarged 2x | 20.00 px | 12.31% | 3.36% | 20.29% | 5.06% | 87.89% | 95.42% |
| 12 px | 12.00 px | 11.49% | 4.00% | 18.29% | 8.90% | 87.29% | 91.55% |
| 12 px enlarged 2x | 24.00 px | 12.62% | 3.12% | 20.94% | 5.30% | 87.26% | 94.70% |
| 14 px | 14.00 px | 11.69% | 2.18% | 23.04% | 7.40% | 85.77% | 93.21% |
| 16 px | 16.00 px | 11.53% | 2.20% | 21.43% | 6.40% | 87.38% | 94.21% |
| 20 px | 20.00 px | 14.24% | 4.55% | 21.40% | 5.76% | 86.81% | 94.24% |
| 24 px | 24.00 px | 12.61% | 3.11% | 21.40% | 5.76% | 86.81% | 94.24% |
Why text size mattered so little
In run 1, one sample dominated that table. The receipt's prices were split or reordered at almost every size, which pushed all the five-sample averages up. Without the receipt, run 1 mean CER stayed between 3.40% and 6.56% at every size. In run 2 the receipt scored 4.59% CER or lower at every size, and mean CER for all five samples stayed between 2.18% and 4.55%, with no steady trend from small to large. In run 3 the receipt scored 4.13% or lower at every size, and mean CER for all five samples stayed between 0.98% and 3.42%, again with no steady trend.
There is a reason the line is flat. Before reading, the tool measures the text and scales the image so lines land at about 30 pixels tall, which suits the recognition engine. Text at 8 pixels and text at 24 pixels end up at a similar size by the time the engine sees them, as long as the letters were legible to begin with. We did not test text smaller than 8 pixels.
Enlarging the image 2x before upload did not help reliably. In run 3, with all five samples, it raised the error at 8 pixels, from 2.22% to 3.06%, but lowered it at 10 pixels, from 2.42% to 2.29%, and at 12 pixels, from 2.78% to 1.99%. In run 2, it raised the error at 8 pixels, from 3.35% to 4.28%, and at 10 pixels, from 2.83% to 3.36%, but lowered it at 12 pixels, from 4.00% to 3.12%. Run 1 was just as mixed: 10.75% to 9.49% at 8 pixels, 8.67% to 12.31% at 10 pixels, and 11.49% to 12.62% at 12 pixels. Upload the original and let the tool do the scaling.
Known weak spots
The first four came up in run 1. The first OCR update targeted them, and run 2 shows what changed. The fifth came up in run 2, and run 3 shows what changed.
Prices split at the decimal point. In run 1, on the receipt saved as PNG, $4.29 came back as $4. with the cents missing, and in dark mode the cents came back as a separate list at the end. Fixed in run 2: the PNG line read "whole milk 1 gal $4.29", and the dark mode receipt scored 0.46% CER. Digits can still slip. In run 2 the PNG receipt read the order number 20483 as 26483. Run 3 read the order number correctly but still read the coffee price $11.50 as $11.56, so check money fields and numbers against the image.
Code braces went missing. In run 1, an opening { at the end of a line and a closing } on a line by itself were often dropped, so a JavaScript line came back as "async function fetchUser(id)". Fixed in run 2 for that sample: it read "async function fetchUser(id) {" and kept its closing braces. The JSON sample still lost its outer { and } in runs 2 and 3, so recheck brackets before you paste code anywhere.
Right-side chat bubbles sometimes lost their first word. In run 1, in one phone chat, the bubble "Sure. Apple pie or brownies?" came back as "Apple pie or browniesJ)". Fixed in run 2: the same screenshot read "Sure. Apple pie or brownies?".
A capital I was sometimes read as a vertical bar. In run 1 a text message came back as "Yes! | will be there". Fixed in run 2: "Yes! I will be there".
Chat sender lines went missing. In run 2, a support chat lost the line "You 2:04 PM" above a message, because the tool mistook the name and time for a phone status bar. Fixed in run 3: the line came back, and all 4 chat screenshots in PNG read with no errors.
Limits of this test
The phone photos were simulated from the screenshots with tilt, blur, noise, and uneven lighting. They are not real camera shots, and a real photo can be better or worse.
The screenshots used fonts that ship on Linux, such as Inter, IBM Plex, Lato, and DejaVu, not Apple or Windows system fonts. They had no phone status bars, keyboards, icons, or avatars.
We did not test HEIC files, PDFs, or languages other than English. Each format row has 22 screenshots and each size row has 5, which is enough to show patterns but not to settle small differences.
Times vary from run to run. In run 1, rerunning 40 of the images gave exactly the same text, but individual times moved by as much as 29.15% faster and 37.44% slower. Run 2 was done twice, on two builds with the same OCR code, and all 226 outputs matched. Run 3 was done once. After the update went live, 9 of its images read on screenshot2text.com gave the same text as in the run.
How to reproduce it
Build the site from its source, start it locally, and open the homepage in headless Chromium with Playwright.
Render screenshots with text you control, with no line wrapping, and save the exact text as the answer key. Save each one as PNG, as JPEG at your chosen qualities, and as WebP.
For each image, set it on the tool's file input, wait for the reading to finish, and copy the text from the output box.
Normalize both texts the same way, then compute character and word edit distance against the answer key. Divide by the length of the answer key to get CER and WER.
Our per-image results for all 226 images are in three CSV files: run 1, the baseline, run 2, after the first OCR update, and run 3, after the second OCR update. Each lists file name, sample, version, font, text size, error counts, rates, word recall, and time for each read.