Skip to content

Latest commit

 

History

37 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

selection-tools

Framework-agnostic image selection tools — rectangles, ellipses, lasso (free select), color select, magic wand, and bitmap masks.

Demo

Features

  • Draw rect/ellipse selections by dragging
  • Lasso (free select) tool: click to place polygonal nodes, drag to draw freehand curves, mix both freely
  • Color select and magic wand tools for pixel-based selection
  • Resize and move committed selections via handles
  • Bitmap mask mode with Shift (add) / Ctrl (subtract) modifiers
  • Shared selection state across all tools (GIMP-like behavior)
  • Animated marching-ants canvas overlay
  • Automatic cursor management
  • Copy selection to clipboard (Ctrl+C / Cmd+C)
  • Works with any framework — React, Vue, Svelte, vanilla JS, etc.

Install

npm install selection-tools

Quick start — mount()

Wrap an <img> in a container div, then call mount():

<div id="container">
  <img src="photo.jpg" />
</div>
import { SelectionToolkit } from "selection-tools";

const toolkit = new SelectionToolkit({
  onChange(state) {
    console.log(state.activeTool, state.mask);
  },
});

const cleanup = toolkit.mount(document.getElementById("container"));

toolkit.setTool("magic-wand");
toolkit.setTolerance(20);
toolkit.clearSelection();

// Tear down when done
cleanup();

mount() handles everything: finds the <img>, creates and positions the overlay canvas, applies required styles, binds all events, and auto-updates the cursor.

SelectionToolkitOptions

Option Type Default Description
onChange (state: ToolkitState) => void required Called after every state mutation
tool ToolType "rect" Initial tool
tolerance number 10 Color tolerance for pixel tools (0–100)
zoom number 1 Display zoom factor
getBounds () => { w, h } | null auto Drawable area dimensions; defaults to image client size

ToolkitState

Field Type Description
activeTool ToolType Current tool
cursorStyle string CSS cursor value (auto-applied by mount())
mask SelectionMask | null The shared bitmap mask
maskOutlines string[] SVG path d strings for the mask boundary
maskVersion number Increments on every mask change
selection SelRect | null Active rect/ellipse in display coordinates
dragPhase DragPhase "idle" / "drawing" / "committed"
handleLayout HandleLayout | null Pre-computed resize handle rects
lassoNodes { x, y }[] Lasso polygon vertices
lassoPhase string "idle" / "placing" / "committed"

SelectionToolkit methods

Method Description
mount(container) All-in-one setup. Returns cleanup function.
setTool(tool) Switch to "rect" / "ellipse" / "freehand" / "color-select" / "magic-wand"
setTolerance(n) Set pixel tool tolerance (0–100)
clearSelection() Clear the entire selection and mask
getState() Get the current ToolkitState snapshot
getSelectedPixels() Extract selected pixels as cropped RGBA data
copySelection() Copy selection to clipboard as PNG
configure(opts) Update getBounds, zoom, or tolerance
setMask(mask) Programmatically set the selection mask
computeOutlines() Recompute mask outlines (returns true if changed)
transformSelection(fn) Transform the active rect/ellipse selection

Advanced setup

For cases where mount() doesn't fit (e.g. you manage the canvas yourself), use the individual methods:

const toolkit = new SelectionToolkit({
  onChange(state) {
    /* ... */
  },
});

const cleanups = [
  toolkit.attachImage(imgElement),
  toolkit.bindEvents(containerElement),
  toolkit.attachCanvas(canvasElement),
];

// Tear down
cleanups.forEach((fn) => fn());
toolkit.destroy();

Tools

Rectangle & Ellipse

Click and drag to draw a selection. After committing, resize via handles or move by dragging inside.

Lasso (free select)

Action Effect
Click Place a polygonal anchor node
Click + drag Draw a freehand curve (auto-simplified on release)
Click on start node Close the polygon and commit to mask
Double-click Close with a straight line back to start and commit
Enter Commit the current path to mask
Backspace Undo the last action (node or freehand stroke)
Escape Cancel and clear the path
Drag an anchor node Reposition the node; adjacent freehand curves deform

Color Select

Click on a pixel to select all pixels of that color across the entire image.

Magic Wand

Click on a pixel to flood-fill select contiguous pixels within the tolerance.

Modifiers

Hold Shift while clicking/drawing to add to the selection, or Ctrl to subtract. Works across all tools.

Coordinate systems

The toolkit uses two coordinate systems:

  • Display coordinates — scaled by the current zoom factor. UI events, SelRect, and handle positions all use display coordinates.
  • Native coordinates — 1:1 with image pixels. The bitmap mask and pixel tools (colorSelect, magicWand) operate in native coordinates.

Use toNativeRect(rect, zoom) to convert a display-coordinate rectangle to native coordinates before applying it to a mask.

Bitmap mask utilities

import {
  createMask,
  maskApplyRect,
  maskApplyEllipse,
  maskApplyPolygon,
  toNativeRect,
  extractMaskedPixels,
  extractOutlines,
} from "selection-tools";

const mask = createMask(imageWidth, imageHeight);

// Convert display rect to native coords before masking
const nativeRect = toNativeRect(displaySelection, zoom);
maskApplyRect(mask, nativeRect, "replace");

const pixels = extractMaskedPixels(mask, ctx.getImageData(0, 0, w, h).data);

const paths = extractOutlines(mask);
Mask mode Effect
"replace" Clears the mask, then sets the new shape
"add" Union — adds the shape to the existing mask
"subtract" Difference — removes the shape from the existing mask

Low-level controllers

For maximum control, use SelectionController and LassoController directly:

import { SelectionController, LassoController } from "selection-tools";

const ctrl = new SelectionController({
  getBounds: () => ({ w: canvas.width, h: canvas.height }),
  onChange: (state) => render(state),
  scheduleFrame: null,
});

canvas.addEventListener("mousedown", (e) => {
  ctrl.handlePointerDown({
    x: e.offsetX,
    y: e.offsetY,
    button: e.button,
    shiftKey: e.shiftKey,
    ctrlKey: e.ctrlKey,
  });
});
window.addEventListener("mousemove", (e) => {
  ctrl.handlePointerMove({
    x: e.offsetX,
    y: e.offsetY,
    button: e.button,
    shiftKey: e.shiftKey,
    ctrlKey: e.ctrlKey,
  });
});
window.addEventListener("mouseup", () => ctrl.handlePointerUp());

Examples

Working examples are in examples/. Each is a self-contained Vite project:

cd examples/<framework>
npm install
npm run dev
Framework Directory Description
React examples/react/ All five tools with SelectionToolkit
Vanilla JS examples/vanilla/ All five tools, no framework
Vue 3 examples/vue/ Composition API with <script setup>
Svelte 5 examples/svelte/ Runes ($state)

All examples use SelectionToolkit.mount() for setup.

License

MIT

About

Framework-agnostic image selection tools: rect, ellipse, lasso, color select, and magic wand for HTML5 Canvas.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages