Markup

Show a model where and what to edit: paint over an area, box things in, or comment on a spot, as with comments on an image. Numbered for the prompt, exported at full resolution.

a tiled washroom

Paint over what to change. Tab reaches the marks: arrow keys move one, Enter edits its note, Delete removes it.

In use

The Image Edit Studio's canvas. Marks and a prompt go to FLUX 3 with the marks drawn in, and the result is laid back only inside what's painted, so nothing else moves. Paint with no prompt erases instead.

Lumen
CreateEditVideo
240 credits
the image

Paint over what to change. Tab reaches the marks: arrow keys move one, Enter edits its note, Delete removes it.

Installation

pnpm dlx shadcn@latest add https://mgp.paukraft.com/r/markup.json

Sending marks

Marks are shapes, not pixels, so the same marks become whatever the endpoint takes. For an instruction to FLUX 3 or Kontext, send the image with the marks drawn in and the notes in the prompt: flux3Image({ prompt: [prompt, ...markupNotes(marks)].join("\n"), images: [await drawMarkup(marks, { src })] }). To erase or fill, send the mask: erase({ image, mask: markup.toMask() }).

PropTypeDefault
drawMarkup(marks, { src?, size?, color? }) => Promise<string>

The marks drawn into the image at full resolution, numbered, as a JPEG data URL: the reference to send with an instruction. Reading the image needs CORS on cross-origin URLs.

—
markupNotes(marks) => string[]

The notes as "1: make it blue", for the prompt.

—
markupMask(marks, size) => string

The mask of any marks, at any size.

—

Markup

Click a box or comment to edit its note or delete it; a click elsewhere closes it, and a comment left empty goes. B, E, R and C pick the brush, eraser, box and comment; Enter adds a box or comment in the middle; [ and ] size the brush. Tab reaches the marks: arrow keys move one, Alt + arrows resize a box, Enter edits its note, Delete removes it. Changes are announced to screen readers. Zoom with Ctrl/⌘ + scroll, a pinch, or + and −, and 0 to fit; pan by scrolling, or dragging with Space held or two fingers.

PropTypeDefault
markupMarkupState

From useMarkup, which holds the marks for submitting.

—
srcstring

The image to mark up; marks are in its pixels. A new one starts over. Without it, a blank sheet at useMarkup's size, for sketching a layout.

—
altstring

What the image shows, e.g. "the living room".

—
toolbarboolean

Floating tools, brush size, undo, redo, clear and zoom. Hide it to place MarkupToolbar yourself.

true

useMarkup

PropTypeDefault
tools("brush" | "eraser" | "box" | "comment")[]

The tools offered, e.g. ["brush", "eraser"] for a mask only, or ["comment"] for comments only.

all
defaultMarksMark[]

Marks to start with, e.g. a saved draft.

—
brushSizenumber

In screen pixels, so it feels the same at any zoom.

40
size{ width, height }

The blank sheet's size, without an image.

—
PropTypeDefault
marks / setMarksMark[]

Strokes, boxes and comments, in image pixels. setMarks is one undo step.

—
callouts(BoxMark | CommentMark)[]

Boxes and comments, numbered from 1 in this order.

—
paintedboolean

Some area is painted or boxed, after erasing: there's a mask to send.

—
toMask() => string | undefined

Painted areas and boxes as a PNG data URL at the image's size: white to change, black to keep.

—
tool / setTool, brushSize / setBrushSize

The current tool and brush.

—
selected / selectstring | null

The box or comment whose note is open.

—
undo, redo, clear() => void

Each undoable; also Ctrl/⌘ + Z and Shift + Ctrl/⌘ + Z.

—