@starwit/react-image-annotate
v7.0.2
Published
- Simple input/output format - Polygon and Line Annotation - Zooming, Scaling, Panning (can be locked via `movementLocked`) - Cursor Crosshair
Readme
React Image Annotate
Features
- Simple input/output format
- Polygon and Line Annotation
- Zooming, Scaling, Panning (can be locked via
movementLocked) - Cursor Crosshair

Usage
npm install @starwit/react-image-annotate
import React, { useRef } from "react";
import ReactImageAnnotate from "@starwit/react-image-annotate";
const App = () => {
const annotatorRef = useRef(null);
return (
<>
<button onClick={() => console.log(annotatorRef.current?.getRegions())}>
Log state
</button>
<ReactImageAnnotate
ref={annotatorRef}
classifications={[
{cls: "alpha", displayName: "Alpha", color: "#00da86", tool: "create-line"},
{cls: "beta", displayName: "Beta", color: "#1e87e9", tool: "create-polygon"},
{cls: "charlie", displayName: "Charlie"},
{cls: "delta", displayName: "Delta"},
]}
image={{
src: "https://placekitten.com/408/287",
name: "Image 1",
regions: []
}}
/>
</>
);
};
export default App;
Retrieving the annotation state
The annotator does not render a header or expose a save/exit callback. Instead,
pass a ref and call getRegions() to read the current annotations whenever you
need them, e.g. from your own toolbar button. It returns the list of drawn
regions:
const regions = annotatorRef.current.getRegions();
// regions: Array<Region> — the polygons/lines drawn on the imageOverlaying custom content on the image
Use renderImageOverlay to draw your own content (a heatmap, a mask, a
deck.gl instance) on top of the image. The annotator renders it in a
box that exactly covers the image and stays aligned through pan, zoom, and resize. The
box is pointerEvents: "none" (so annotation input still works) and sits below the
region annotations; fill it with width: 100%; height: 100%.
Pass a React node for simple CSS-laid-out overlays:
<Annotator image={image} renderImageOverlay={<MyOverlay />} />Or a function for overlays that need the image geometry — e.g. deck.gl, which draws in
its own coordinate space and needs the image's intrinsic pixel size. It receives
{ naturalWidth, naturalHeight, width, height, imagePosition, mat }, where
naturalWidth/naturalHeight are the image's constant pixel dimensions and
width/height are its current on-screen size:
<Annotator
image={image}
renderImageOverlay={({ naturalWidth, naturalHeight, width }) => (
<DeckGL
style={{ width: "100%", height: "100%" }}
views={new OrthographicView()}
controller={false} // the annotator owns pan/zoom
viewState={{
target: [naturalWidth / 2, naturalHeight / 2, 0], // center, in image pixels
zoom: Math.log2(width / naturalWidth), // fit image pixels into the box
}}
layers={[/* layers in image-pixel coordinates */]}
/>
)}
/>Expressing data in image-pixel coordinates (top-left origin, naturalWidth × naturalHeight)
keeps it glued to the image at any zoom.
To get the proper fonts, make sure to import the Inter UI or Roboto font, the following line added to a css file should suffice.
@import url("https://rsms.me/inter/inter.css");Props
All of the following properties can be defined on the Annotator...
| Prop | Type (* = required) | Description | Default |
| -------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------ |
| image | Image * | The image to annotate. | |
| selectedTool | string | Initially selected tool. e.g. "select", "pan", "zoom", "create-polygon", "create-line". | "select" |
| classifications | Array<Classification> | Allowed classifications (mutually exclusive) for regions. Each is { cls, displayName?, color?, tool? }, where cls is the technical identifier (source of truth, e.g. for DB references), displayName is the human-readable label shown in the UI (falls back to cls), color is optional (default palette is used otherwise), and tool sets the tool (e.g. "create-line", "create-polygon") to activate when the classification is selected. | |
| preselectCls | string | cls that should be preselected when creating a new region. | |
| ref | Ref | Ref exposing getRegions(), which returns the current array of regions. See "Retrieving the annotation state" above. | |
| enabledRegionProps | Array<string> | Which properties to show in the region edit popup ("name", "line-direction"). | ["class", "name"] |
| movementLocked | boolean | Reset zoom/pan to the default view and lock canvas movement (panning/zooming). | false |
| userReducer | (state, action) => state | Optional reducer to hook into event handling. It runs after the built-in reducers and receives every event triggered within the annotator (e.g. SELECT_CLASSIFICATION), so it can override or extend the default behavior. | |
| renderImageOverlay | ReactNode \| (args) => ReactNode | Custom content rendered on top of the image, kept aligned to it through pan/zoom. See "Overlaying custom content on the image" above. | |
Developers
Development
To begin developing run the following commands in the cloned repo.
npm installnpm start
Then navigate to http://localhost:5173/ and start testing.
See more details in the contributing guidelines.
Icons
Consult these icon repositories:
Testdrive in project
To test this package in your project follow this quickstart:
- Run
npm linkin the root directory of this project (where thepackage.jsonis located) - With the same Terminal window, go to your target project folder where the
package.jsonis located - Run
npm link "@starwit/react-image-annotate"to install the package. It might be necessary to remove a previously installed@starwit/react-image-annotatepackage. Please use the same node version when using npm link and executing the application. - Changes to this repository will apply live to the running dev session in your target project :)
Notes
Currently, there is an issue with vite-plugin-node-polyfills (0.15.0 at the time of writing), which shows many warnings while building (related to "use client"). That is expected and will probably be fixed in the future. See here: https://github.com/davidmyersdev/vite-plugin-node-polyfills/issues/49
