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 it

The tool is a loop

01pixelcoords

Freeze, mark, save a session.

{ "schema": 1,
  "selections": [{ "shape": "rect", "label": "submit",
    "px": { "x": 812, "y": 440, "w": 96, "h": 40 } }] }
02pixelcoords assert --point 812,440 --expect submit

Score 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 0
03pixelcoords resolve --label submit --units auto

Where 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 } }
04pixelcoords emit --format pyautogui

Click code in the target tool’s own coordinate convention.

pyautogui.click(812, 460)  # submit
05pixelcoords wait --label dialog --for match

Block 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 }] }
06pixelcoords 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 }
07pixelcoords find

The UI drifted? Every region re-located by its saved crop.

{ "found": true, "score": 0.998,
  "delta": { "dx": 0, "dy": -120 } }
08pixelcoords resume

Reopen 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"] }
  }
}
toolsubcommandcaptures the screen
pixelcoords_sessionsno
pixelcoords_resolveresolveonly with relocate
pixelcoords_assertassertno
pixelcoords_waitwaitevery poll, and blocks
pixelcoords_findfindyes
pixelcoords_diffdiffunless 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 selection

Crops 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

FeaturepixelcoordsPowerToys Screen RulerPixelSnap 2SikuliX
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 measuringnot offered
Yes — measures the live screen
Yes — live edge-snapped measuring
Watches the live screen continuously
OCRnot offerednot offerednot 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 offerednot offered
In-script image matching
Re-location after UI drift
find — template re-location with deltas
not offerednot offered
Continuous visual search
License
MIT, open source
MIT (PowerToys), open source
Proprietary
MIT, open source
verified against PowerToys Screen Ruler v0.100, 2026-07-28verified against PixelSnap 2 v2.6, 2026-07-28verified against SikuliX v2.0.5 (archived March 2026), 2026-07-28

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

macOSSupported — primary development platform
WindowsSupported — 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 pixelcoords

Or 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-config

macOS 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.