Why our document scanner never binarizes
5 October 2026 · How Nescanner turns a camera frame into a scan, and what we measured along the way.
Most phone scanner apps make small print worse. Photograph a page with fine text, run it through the “document” filter, zoom in, and you will usually see the same thing: letters with ragged, stair-stepped edges, thin strokes broken into dots, a white halo around every character, and grey paper turned into pure white by force. The page looks crisp from arm’s length and reads worse up close.
That comes from two habits: thresholding (every pixel becomes black or white) and “enhancing” (a contrast boost plus sharpening). We built Nescanner around the opposite rule: the pipeline never binarizes, never sharpens with an unsharp mask, and resamples the camera pixels exactly once. This post explains why, and shows the numbers from our test bench, including the cases where we lose.
What a threshold destroys
A printed stroke photographed by a phone is not a black shape on a white background. Its edge is a ramp a few pixels wide: optics blur it, the sensor samples it, and demosaicing smears it a little more. That ramp carries the real position of the edge to a fraction of a pixel. It is also what makes text look smooth on screen, because it is the anti-aliasing.
A threshold cuts that ramp at one level. Everything above it becomes paper and everything below becomes ink, so the edge snaps to whole pixels. Where a stroke is thinner than the blur, which happens with small print, low toner or a phone held a bit too far away, its darkest point never gets under the threshold. The stroke disappears or breaks into dots. OCR reads those remnants worse than it reads the original grey ramp.
What we do instead
Every stage works on 32-bit floats in linear light. That is where light actually adds and multiplies; sRGB values do not.
- RAW in. We capture Apple ProRAW where the phone has it and Bayer DNG everywhere else. In dim light (ISO 200 and up), phones without ProRAW take three frames and average them, which removes grain without smearing strokes.
- Rectify once. The page corners, the output size, the text-orientation turn and a small inset from the page edge are all folded into one matrix, and every output pixel samples the source frame once with Lanczos-3. The usual order of straighten, then scale, then crop resamples three times, and after three resamples fine strokes do not come back. Book-page flattening follows the same rule: a mesh interpolates only the source coordinates, and the pixels are still read once.
- Divide by the illumination. In linear light, uneven lighting is a multiplier. We estimate a smooth illumination map per channel from the paper (with a mask so photos on the page are not treated as paper) and divide it out. White balance falls out for free, because the paper becomes (1, 1, 1). On the phone, the paper goes from a ~20 % gradient in the raw frame to 250–254 (out of 255) across the whole page. We do not “whiten” anything by raising brightness.
- Denoise with a guided filter on luminance (radius 4, ε 0.002), picked on the bench. It halves paper noise without rounding stroke edges, at 19–28 ms per 12 MP page.
- Deconvolve, carefully. TV-regularised Richardson–Lucy, 3 iterations, with a PSF estimated per tile from the widths of the strokes on that tile. It is gated: tiles where the stroke is too thin relative to the blur are skipped, because there deconvolution invents detail and OCR gets worse.
- A smooth tone curve, not a threshold: deep ink, white paper, and the grey ramp on every edge kept intact.
- PDF with a text layer, recognised on the device.
Parameters live in JSON profiles, not in code, and shaders are written in a restricted subset of Metal Shading Language so they port to WGSL or GLSL later. Nothing leaves the phone: there is no account and no server of ours.
How we measure
Opinions about “sharper” are cheap, so every change went through a bench: about a hundred real DNG frames of printed pages, books and colour pages, shot handheld and on a stand, close and far, flat and tilted. Some pages were printed on a laser printer low on toner on purpose. The text pages have hand-typed ground truth. The bench runs the same pipeline as the app and records:
- CER: character error rate of Apple’s Vision OCR against the truth.
- edge: median 10→90 % width of paper↔ink transitions, divided by the text-line height. Lower means sharper.
- ring: 90th percentile of overshoot above the paper level, as a fraction of contrast. That is the halo sharpening leaves.
- paper noise: σ of luminance in text-free cells, median over a grid.
The same printed pages were also scanned on a 1200 dpi flatbed scanner, as a ceiling. Measured on 28 September 2026:
| Page | Output | CER | edge | ring | paper noise |
|---|---|---|---|---|---|
| Mixed sizes, low toner | Nescanner | 0.09 % | 0.041 | 0 | 0.0040 |
| Flatbed, 1200 dpi | 0.00 % | 0.022 | 0 | 0.0039 | |
| Dense small print, low toner | Nescanner | 1.1 % | 0.049 | 0 | 0.0040 |
| Flatbed, 1200 dpi | 0.5 % | 0.019 | 0 | 0.0026 | |
| Colour textbook page | Nescanner | — | 0.029 | 0 | — |
In short: on a flat page, paper noise matches the flatbed and there is no halo on any page. On faint, dense small print OCR misses about 1 character in 100, twice as many as from the scanner glass.
Where we lose
- A page tilted 25°. The phone focuses on the near edge, and the far edge of the page lies outside the depth of field. CER rises to 9.4 %. Sharpening that soft region hard would read better and add visible halos; we chose not to fake it. Instead the camera tells you to level the phone, and it blocks the shutter only when both the page geometry and the accelerometer agree that the phone is tilted (a curved book page alone can look like 12° of tilt).
- The flatbed is still about twice as sharp by the edge metric (0.019–0.022 against 0.041–0.049). A phone lens at 30 cm is not scanner glass, and we say so.
- Deconvolution from far away hurts. At about 1 m the page comes out at 78 dpi. An aggressive early deconvolution setting raised CER there from 3.1 % to 5.2 %, which is why the stage is gated by stroke width.
- Light grey print gets lighter. The tone curve that brings paper to white also lifts very light grey text. It is still readable, but paler than in the photo.
- The bench’s own OCR is noisy. Vision sometimes drops a whole line on a given image, and CER jumps by 8–12 % for reasons unrelated to pixels. We read CER together with the edge and noise metrics, never alone.
Try it
Nescanner is on the App Store for iPhone and iPad, in 33 languages. Every feature is free for up to 30 exported pages a day. Pro removes the limit for US$1.99 a month and changes nothing else. No ads, no account, no analytics.