# canvas_readback

> The canvas known-answer probe: a 16x16 canvas in two known colours, read back twice. exact means untouched readback; altered means something adds noise. Not in either id.

Source: https://whiskerjs.com/signals/canvas-readback/ (last updated 2026-10-26)

**canvas_readback at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `canvas_readback` |
| In the visitorId | no (reported, kept out of both ids) |
| structuredId group | none |
| Hashing | plain value; a digest only when longer than 128 characters or not printable ASCII |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 3](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Some browsers and privacy extensions change a few pixels whenever a page reads an image back from a canvas, to make canvas fingerprints useless. WhiskerJS paints a tiny canvas in two exact colours and reads it back twice. If the colours come back unchanged it reports `exact`; if not, `altered`. It is a diagnostic about the browser’s settings, so it is kept out of the identifiers.

## What it reads

The RGBA bytes of a 16x16 2D canvas after painting it, through `getImageData(0, 0, 16, 16)`, read twice. This is the canvas known-answer probe, shared with [`canvas`](https://whiskerjs.com/signals/canvas/), [`noise_profile`](https://whiskerjs.com/signals/noise-profile/) and the browser class (one run per collection). Until signalsVersion 5 it was a separate 1x1 probe; since signalsVersion 6 it reports the 16x16 probe.

## How the technique works

1. A canvas element is created and sized 16x16; a 2D context is taken with `willReadFrequently: true` (so Chromium does not warn about repeated readback).
2. The left half is filled with `rgb(17, 34, 51)` and the right half with `rgb(204, 153, 102)`, and every pixel is read back. These opaque colours survive premultiplied alpha exactly in every engine; two colours rather than one catch a noise routine that skips single-colour canvases.
3. The fill and read are done a second time.
4. Both reads exact gives `exact`. Any other answer (the same wrong bytes twice, or two different answers) gives `altered`.
5. When the probe cannot run at all (no document, no 2D context, a missing method, or the browser throws), the component is `unavailable` / `no_context`.

## Why it helps

It turns canvas tampering into a signal of its own. Your server can see that a browser alters readback even when the noise is the same on every read. The same probe result picks the `firefox_fpp` and `safari_afp` [browser classes](https://whiskerjs.com/signals/browser-class/) and makes `canvas` report `noisy` / `seeded_noise`, so the graphics group stays stable instead of changing every session.

## Stability

High for a given browser and its settings, but it follows those settings: turning an extension or a protection mode on or off flips it. That is why it is outside both ids.

## Browser support and what blocks it

- Works wherever a 2D canvas exists: [Chrome](https://whiskerjs.com/browsers/chrome-desktop/), [Firefox](https://whiskerjs.com/browsers/firefox/), [Safari](https://whiskerjs.com/browsers/safari-macos/), [iOS browsers](https://whiskerjs.com/browsers/safari-ios/), [Edge](https://whiskerjs.com/browsers/edge/), [Brave](https://whiskerjs.com/browsers/brave/).
- Browsers or modes that randomise readback (Brave, Firefox Fingerprinting Protection, Safari Advanced Fingerprinting Protection, canvas-noise extensions) report `altered`. Unlike `canvas`, this probe is never excluded by a browser class, so there it tells you what the browser does.
- Where the canvas cannot be read at all, it reports `unavailable` / `no_context`.

## Privacy

It reveals only whether canvas reads are altered, which suggests a privacy setting or extension. WhiskerJS does not try to work around the alteration.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` |   | `exact` or `altered` |
| `unavailable` | `no_context` | the probe could not run: no document, no 2D context, no `getImageData` or `fillRect`, or a read threw |
| `excluded` | `caller_excluded` | you excluded it (the probe still runs for the browser class) |
| `timed_out` | `timeout`, `aborted` | budget or abort |

Never hashed.

## Example value

A stock browser reports `exact`:

```json
"canvas_readback": { "status": "ok", "value": "exact" }
```

## Questions and answers

### What makes canvas_readback report altered?

A browser setting or an extension that changes pixel data when a page reads a canvas, which is how many anti-fingerprinting tools work. A clean browser returns the exact colours.

### How is this different from canvas being noisy?

canvas renders a full scene twice and compares the pixels, so on its own it catches noise that changes between reads. canvas_readback compares its reads with the colours it painted, so it also catches a change that is the same on every read. Since signalsVersion 6 canvas uses this same probe: when the renders agree but the probe is altered, canvas reports noisy with ruleId seeded_noise.

### How is it different from noise_profile?

Both report the same canvas probe. canvas_readback says only exact or altered; noise_profile tells seeded noise (the same wrong answer on both reads) from per-read noise, and adds the WebGL and audio probes.

## Related pages

- [canvas](https://whiskerjs.com/signals/canvas/): The canvas scene, with the double render.
- [Canvas noise](https://whiskerjs.com/glossary/#canvas-noise): Why browsers and extensions alter readback.
- [noise_profile](https://whiskerjs.com/signals/noise-profile/): The canvas, WebGL and audio probes, with seeded and per-read told apart.
- [Noise handling](https://whiskerjs.com/docs/how-it-works/#noise-handling-the-double-render-and-the-known-answers): The double render and the known answers.
- [native_tampering flag](https://whiskerjs.com/docs/integrity/#native_tampering): Checks whether getImageData and toDataURL look native.
