Image annotator

Annotate an image on a constrained canvas and export the result as a PNG.

import { useState } from 'react'
import 'tldraw/tldraw.css'
import { ImageAnnotationEditor } from './ImageAnnotationEditor'
import { ImageExport } from './ImageExport'
import { ImagePicker } from './ImagePicker'
import './image-annotator.css'
import { AnnotatorImage } from './types'

type State =
	| {
			phase: 'pick'
	  }
	| {
			phase: 'annotate'
			id: string
			image: AnnotatorImage
	  }
	| {
			phase: 'export'
			result: Blob
	  }

export default function ImageAnnotatorWrapper() {
	const [state, setState] = useState<State>({ phase: 'pick' })

	switch (state.phase) {
		case 'pick':
			return (
				<div className="ImageAnnotator">
					<ImagePicker
						onChooseImage={(image) =>
							setState({ phase: 'annotate', image, id: Math.random().toString(36) })
						}
					/>
				</div>
			)
		case 'annotate':
			return (
				<div className="ImageAnnotator">
					<ImageAnnotationEditor
						// remount tldraw when a new image is chosen
						key={state.id}
						image={state.image}
						onDone={(result) => setState({ phase: 'export', result })}
					/>
				</div>
			)
		case 'export':
			return (
				<div className="ImageAnnotator">
					<ImageExport result={state.result} onStartAgain={() => setState({ phase: 'pick' })} />
				</div>
			)
	}
}

The chosen image is placed as a locked image shape. Store side effects keep it locked and at the bottom of the page, and camera constraints (editor.setCameraOptions with contain behavior and a fit-min-100 base zoom) stop you from panning or zooming away from it. An InFrontOfTheCanvas overlay dims everything outside the image so it's clear what will be exported.

Press "Done" to export with editor.toImage, cropped to the image bounds, then copy or download the result.

Try picking a very long, thin image to see how the camera constraints behave.

Is this page helpful?
Prev
Education canvas
Next
PDF editor