Freeze your screen, mark regions, get pixel-exact coordinates and crops
Rectangles, ellipses, triangles, N-gons, freehand, rulers — rotate, label, verify, regenerate
Every screen tool that measures pixels ends at a human's eyeball: a ruler shows you a number, a screenshot app draws an arrow, a mouse tracker prints a position you copy by hand. pixelcoords starts from a different premise — the real consumer of a coordinate is a machine.
It freezes your screen so nothing moves while you measure, then lets you mark regions with real shapes — rotated, labeled, and placed to the exact pixel with a loupe and arrow-key nudging, plus rulers that report distance and angle between any two points. Marks snap to the UI edges already in the frozen image, so a rect around a button lands on the button. What you mark becomes data, not a picture: versioned JSON in physical pixels with per-monitor DPI scale, the color under each click point, labeled crops and frame-sized cutouts, and ready-to-paste click code.
Those answers are also served over MCP, so a model can ask them directly instead of being handed a screenshot: pixelcoords mcp, six read-only tools, no subprocess to wire up. Asking a marked session where to click reads a file and answers in microseconds and sends no image at all — the alternative is a screenshot per step, and a long computer-use task runs to hundreds of steps. A human still marks the regions; the model does the rest.
What happens in this demo
A private browser window on the Google homepage is frozen by pixelcoords with an amber outline marking the targeted window. Four shapes are drawn over the page — a rectangle, an ellipse, a triangle, and a hexagon — in committed green, then a freehand region in preview blue with the live coordinate chip showing its position and size. The control panel lists every key. The session is saved, and the demo ends on the resulting session.json open in a browser: schema, monitors, target window, and one record per marked selection.
Sixty seconds
pixelcoords
# screen freezes; drag shapes, A labels, S saves
# → Downloads/pixelcoords-captures/<timestamp>/
# session.json screenshot-0.png cutout-primary-0.png cutout-inverse-0.png crop-0-submit.png
pixelcoords assert --session <dir> --point 812,440 --expect submit
# exit 0: that point is inside the region you labeled "submit"
# --stdin scores a whole trajectory in one process, one point per line
pixelcoords resolve --session <dir> --label submit --units auto
# where to click, right now, in the units your input API actually wants
pixelcoords emit --session <dir> --format pyautogui
# ready-to-paste click code, coordinate conventions already handled
pixelcoords wait --session <dir> --label dialog --for match
# block until that region is back on screen; exit 1 if it never arrives
pixelcoords diff --session <dir> --against baseline/
# did those regions still look right? visual regression, per region
pixelcoords find --session <dir>
# the UI moved? every region re-located by its saved crop, deltas included
pixelcoords mcp
# serve all of the above to an LLM over stdio
# read-only: it asks about sessions you marked, it cannot make one
pixelcoords resume
# pick any saved session, keep editing itThe tool is a loop
pixelcoordsFreeze, mark, save a session.
{ "schema": 1,
"selections": [{ "shape": "rect", "label": "submit",
"px": { "x": 812, "y": 440, "w": 96, "h": 40 } }] }pixelcoords assert --point 812,440 --expect submitScore points against it. The exit code is the API: 0 hit, 1 miss, 2 malformed.
{ "command": "assert", "ok": true,
"results": [{ "hit": true, "space": "global" }] } # exit 0pixelcoords resolve --label submit --units autoWhere to click now, in the units your input API wants — per monitor scale.
{ "label": "submit", "scale": 2.0, "units": "logical",
"point": { "x": 406, "y": 230 } }pixelcoords emit --format pyautoguiClick code in the target tool’s own coordinate convention.
pyautogui.click(812, 460) # submit
pixelcoords wait --label dialog --for matchBlock until the region is back on screen; exit 1 if it never arrives.
{ "ok": true, "polls": 7, "elapsed_ms": 3204,
"results": [{ "score": 0.997, "matching": true }] }pixelcoords diff --against baseline/Did those regions still look right? Visual regression, per region.
{ "masked_px": 400, "changed_px": 3,
"changed_pct": 0.75, "mean_delta": 172.67 }pixelcoords findThe UI drifted? Every region re-located by its saved crop.
{ "found": true, "score": 0.998,
"delta": { "dx": 0, "dy": -120 } }pixelcoords resumeReopen any session and keep editing; saves update it in place.
session.json screenshot-0.png crop-0-submit.png → editable again
Drive it with your LLM
The loop above answers the questions a machine asks. Reaching it still took a human — wire a subprocess call, remember the flag spellings, parse stdout. pixelcoords mcp serves the same commands over the Model Context Protocol on stdio, so a model calls them directly. Point a client at the binary:
{
"mcpServers": {
"pixelcoords": { "command": "pixelcoords", "args": ["mcp"] }
}
}| tool | subcommand | captures the screen |
|---|---|---|
| pixelcoords_sessions | — | no |
| pixelcoords_resolve | resolve | only with relocate |
| pixelcoords_assert | assert | no |
| pixelcoords_wait | wait | every poll, and blocks |
| pixelcoords_find | find | yes |
| pixelcoords_diff | diff | unless against is stored |
A miss is an answer, not a failure. The exit codes survive the trip: 0 and 1 both come back as ordinary results, so a miss, a timeout, or an over-tolerance diff arrives with ok: false and no error flag. Only a malformed question is an error. Backwards, every negative answer looks like a broken tool and the model retries instead of reacting.
It is read-only. No tool opens the overlay, renames a session, or edits one — marking regions is something a human does. The shape is mark once, run many. Asking a marked session where to click reads a file and needs no screen-recording permission; the tools that do capture say so in their own descriptions. Full reference.
What a save writes
pixelcoords-captures/20260728-182121-117/
├── session.json # versioned schema: three coordinate spaces, DPI scale
├── screenshot-0.png # one full frozen capture per monitor
├── cutout-primary-0.png # the frame with only the selections visible
├── cutout-inverse-0.png # the exact complement: selections punched out
└── crop-0-submit.png # one labeled crop per selectionCrops isolate each region; the cutout pair keeps every region in place on transparency — together they reassemble the screenshot. Files pixelcoords didn't write are never touched.
Where it stands
| Feature | pixelcoords | PowerToys Screen Ruler | PixelSnap 2 | SikuliX |
|---|---|---|---|---|
| Price | Free | Free | $39 one-time | Free |
| Platforms | macOS · Windows · Linux | Windows only | macOS only | macOS · Windows · Linux (JVM)requires Java; development archived, continued by the OculiX fork |
| What you get out | session.json + labeled crops + cutouts + click codephysical-pixel coordinates in three spaces, per-monitor DPI scale | On-screen measurement; copies a number | On-screen measurements; screenshots with dimensionsits edge-snapping while measuring is genuinely excellent | Automation scripts that act on the screen |
| Live on-screen measuring | not offered | Yes — measures the live screen | Yes — live edge-snapped measuring | Watches the live screen continuously |
| OCR | not offered | not offered | not offered | Built in (Tesseract) |
| Point verification | assert, diff, wait — exit codes for CI and agentsa point, a whole trajectory from stdin, per-region pixel diffing, or blocking until the screen settles | not offered | not offered | In-script image matching |
| Re-location after UI drift | find — template re-location with deltas | not offered | not offered | Continuous visual search |
| License | MIT, open source | MIT (PowerToys), open source | Proprietary | MIT, open source |
Non-goals
Knowing what a tool is means knowing what it isn't. These are settled:
- OCR— text extraction is a different product
- Live (unfrozen) measurement— the freeze is the thesis
- Annotation— arrows and blur are a screenshot editor's job
- Recording / GIF capture— the product is the frozen instant, not the timeline
- Cloud, accounts, sharing— offline by design, permanently
Platform status
| macOS | Supported — primary development platform |
|---|---|
| Windows | Supported — verified by hand on Windows 11 |
| Linux (X11) | Supported — verified by hand on GNOME 46; every feature works |
| Linux (Wayland) | Screen coordinates + --pick window marking — verified by hand on GNOME 46; no windows / --target (the protocol withholds window geometry) |
Hand-verification is per platform and per release, so the rows above are not a blanket claim. On macOS the overlay was driven by hand through 0.5.0 — multi-monitor and mixed-DPI included, two displays at different scales with every coordinate recorded where it was drawn — and 0.5.1's capture path was checked against a real capture. Windows and Linux were verified by hand through 0.4.0. Nothing since 0.5.1 has had a human overlay run on any platform, which covers the mid-gesture Shift fix and the comfort settings — both change how the overlay behaves, and both are backed by automated tests only. The MCP server is not on that list: it adds no overlay code, and it was driven end to end against a real session. Multi-monitor off macOS, and fractional scaling anywhere, remain test-only. This table is kept honest — claims match runs, and where a run has not happened it says so.
What is checked automatically, on macOS, Windows and Linux, every push: the binary driven against a real capture of the runner's screen — shoot, find, resolve, assert, diff, wait, emit, rename, every shape kind, all six MCP tools — plus display-free tests pinning every exit code across every command. That is a great deal more than it was, and it is still not the overlay. Drawing, dragging, undo, the loupe, snapping, the permission prompts and window targeting need a person at a machine.
Two ways in
cargo install pixelcoordsOr skip the toolchain: prebuilt binaries for macOS, Windows, and Linux — download, unpack, run.
Rust 1.88+ for the cargo route. On Linux, build dependencies first:
sudo apt-get install -y libxcb1-dev libxcb-randr0-dev libpipewire-0.3-dev \
libclang-dev libegl1-mesa-dev libgbm-dev pkg-configmacOS asks for Screen Recording permission on first run.
No account, no network, no toolkit — one small native binary for macOS, Windows, and Linux. MIT-licensed, because the aim was to build the best tool in this category and give it away.
The loop has an executor half: pixelactions consumes these sessions — click, type, verify, by label. Early, and macOS only.