agentsclimarketplace

Image annotation usage

Skill Frank17008/skills/image-annotation-usage

Use when integrating @frank17008/image-annotation React component into web applications, including basic setup, controlled/uncontrolled mode, custom image upload, styling, or debugging annotation rendering issues, canvas blank problems, or annotations not updating.From its SKILL.md

Install
npx -y skills add Frank17008/skills --skill image-annotation-usage

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

11.5 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it

Image Annotation Component Usage

Overview

@frank17008/image-annotation is a React + TypeScript image annotation component with Canvas 2D. Supports rectangle, circle, arrow, text, and freehand tools with undo/redo, drag, export, keyboard shortcuts, zoom/pan, and DPR-aware rendering.

Package location: packages/image-annotation/src/ Published path: @frank17008/image-annotation

When to Use

Trigger phrases:

  • "How to integrate image annotation component"
  • "Add annotation to my React app"
  • "Canvas blank even though image loads"
  • "Annotations not saving"
  • "Custom image upload with annotation"
  • "Controlled mode not working"
  • "HiDPI/Retina display blurry"

Installation

pnpm add @frank17008/image-annotation
# or
npm install @frank17008/image-annotation

Basic Integration

Minimal Setup

import { useState } from 'react';
import { ImageAnnotation, type Annotation } from '@frank17008/image-annotation';
import '@frank17008/image-annotation/dist/index.css';

export default function App() {
  const [annotations, setAnnotations] = useState<Annotation[]>([]);

  return (
    <div style={{ height: 600 }}>
      <ImageAnnotation
        src="https://example.com/image.jpg"
        onChange={setAnnotations}
      />
    </div>
  );
}

Critical: Parent container MUST have explicit height. Without it, canvas has 0×0 dimensions → blank canvas.

Type Import

Always use explicit type import for TypeScript:

import type { Annotation, ToolType } from '@frank17008/image-annotation';
// or
import { type Annotation, type ToolType } from '@frank17008/image-annotation';

Controlled vs Uncontrolled Mode

Uncontrolled (Default)

<ImageAnnotation
  src="image.jpg"
  onChange={(annotations) => console.log(annotations)}
/>

The component manages its own state internally.

Controlled (External State)

const [myAnnotations, setMyAnnotations] = useState<Annotation[]>([]);

<ImageAnnotation
  src="image.jpg"
  value={myAnnotations}
  onChange={setMyAnnotations}
/>

Rules:

  1. value → external annotations (read-only for component)
  2. onChange → called when annotations change
  3. If both value and onChange provided → controlled mode
  4. If neither → uncontrolled mode
  5. If only value without onChange → React warning

Sync Controlled to Uncontrolled

<ImageAnnotation
  src={src}
  value={annotations}
  onChange={(newAnnotations) => {
    setAnnotations(newAnnotations);
    // Save to backend, localStorage, etc.
    saveToBackend(newAnnotations);
  }}
/>

Custom Image Upload

Pattern 1: External Button

import { useState, useRef } from 'react';
import { ImageAnnotation } from '@frank17008/image-annotation';

export function ImageUploader() {
  const [imageSrc, setImageSrc] = useState<string | null>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    const reader = new FileReader();
    reader.onload = (event) => {
      const result = event.target?.result;
      if (typeof result === 'string') {
        setImageSrc(result); // base64 string
      }
    };
    reader.readAsDataURL(file);
    e.target.value = ''; // reset for same file re-select
  };

  return (
    <div style={{ height: 600 }}>
      <input
        ref={fileInputRef}
        type="file"
        accept="image/*"
        onChange={handleFileChange}
        style={{ display: 'none' }}
      />
      <button onClick={() => fileInputRef.current?.click()}>
        Upload Image
      </button>
      {imageSrc && (
        <ImageAnnotation
          src={imageSrc}
          onChange={() => {}}
        />
      )}
    </div>
  );
}

Pattern 2: Component's Upload Button

const handleUpload = () => fileInputRef.current?.click();

<ImageAnnotation
  src={imageSrc}
  onChange={setAnnotations}
  onUpload={handleUpload}
/>

The component displays an upload button in toolbar when onUpload is provided.

Annotation Types Reference

// Rectangle annotation
{ type: 'rectangle', x: number, y: number, width: number, height: number, color: string, lineWidth?: number }

// Circle annotation
{ type: 'circle', x: number, y: number, radius: number, color: string, lineWidth?: number }

// Arrow annotation
{ type: 'arrow', x: number, y: number, toX: number, toY: number, color: string, lineWidth?: number }

// Text annotation
{ type: 'text', x: number, y: number, text: string, color: string, lineWidth?: number }

// Freehand annotation
{ type: 'freehand', points: Array<{ x: number, y: number }>, color: string, lineWidth?: number }

Type Export

import type {
  Annotation,
  RectangleAnnotation,
  CircleAnnotation,
  ArrowAnnotation,
  TextAnnotation,
  FreehandAnnotation,
  ToolType,
  Point,
} from '@frank17008/image-annotation';

Props API

PropTypeRequiredDescription
srcstringImage URL or base64
valueAnnotation[]Controlled annotations
onChange(annotations: Annotation[]) => voidAnnotation change callback
classNamestringContainer className
onUpload() => voidUpload button callback

Zoom and Pan

Programmatic Zoom

const handleZoomIn = () => {
  // Component doesn't expose zoom API directly
  // Use keyboard shortcuts: Ctrl++
};

// Or via keyboard shortcuts (user must press):
// - Ctrl++ : Zoom in
// - Ctrl+- : Zoom out
// - Ctrl+0 : Reset zoom

Space + Drag Pan

Hold Space + drag to pan the canvas.

Zoom Ratio Display

The toolbar shows current zoom percentage (e.g., "100%", "150%").

Exporting Annotations

On Change Callback

<ImageAnnotation
  src={src}
  onChange={(annotations) => {
    // Save to database
    await saveAnnotations(imageId, annotations);

    // Or export as JSON
    const json = JSON.stringify(annotations, null, 2);
    download(json, 'annotations.json', 'application/json');
  }}
/>

Export Image with Annotations

Use the toolbar's export button, or programmatically:

import { download } from '@frank17008/image-annotation';
// Not currently exported - use toolbar button

Keyboard Shortcuts

ShortcutAction
DeleteDelete selected annotation
EscapeCancel text input
Ctrl+ZUndo
Ctrl+Y / Ctrl+Shift+ZRedo
Ctrl++ / Ctrl+=Zoom in
Ctrl+-Zoom out
Ctrl+0Reset zoom
Space + DragPan canvas

Styling Customization

CSS Variables

:root {
  --annotation-primary: #FF0000;
  --annotation-toolbar-bg: #FFFFFF;
}

Container Styling

<ImageAnnotation
  src={src}
  className="my-annotation-container"
/>
.my-annotation-container {
  border: 1px solid #ddd;
  border-radius: 8px;
}

Debugging Common Issues

Issue 1: Canvas Completely Blank

Symptoms:

  • Image loads successfully (network request completes)
  • Canvas area is empty/white

Root Causes and Solutions:

CauseDiagnosisFix
Parent has no heightContainer has height: auto or no height setSet explicit height: style={{ height: 600 }}
DPR mismatchRetina/HiDPI display, canvas looks tinyComponent handles DPR automatically
Image load errorCheck browser console for CORS errorsAdd crossOrigin="anonymous" to server
Zero canvas sizeCheck canvas element dimensionsEnsure parent has explicit dimensions

Issue 2: Annotations Not Saving

Symptoms:

  • Draw annotations but array remains empty
  • onChange callback never fires

Root Causes and Solutions:

CauseDiagnosisFix
Stale stateUsing prev => prev incorrectlyPass new array directly: setAnnotations(newValue)
Missing onChangeNo callback providedAdd onChange={setAnnotations}
Controlled conflictBoth value and internal stateProvide onChange with value

Correct pattern:

// WRONG - stale closure
onChange={(annotations) => {
  setAnnotations((prev) => [...prev, ...annotations]);
}}

// CORRECT - direct assignment
onChange={setAnnotations}

Issue 3: Blurry on HiDPI/Retina Displays

Diagnosis:

  • Canvas appears blurry or pixelated
  • Only happens on Retina displays

Root Cause: Missing devicePixelRatio scaling

Fix: The component handles DPR automatically since v1.x. Ensure:

  1. CSS import is included
  2. Parent container has proper dimensions

Issue 4: High-DPI Canvas Blank

Symptoms:

  • Works on standard displays
  • Blank on Retina/HiDPI

Root Cause: Canvas dimensions not scaled by devicePixelRatio

Diagnosis:

// In browser console
const canvas = document.querySelector('canvas');
console.log('Canvas size:', canvas.width, canvas.height);
console.log('Display size:', canvas.style.width, canvas.style.height);
console.log('DPR:', window.devicePixelRatio);

If canvas.width === canvas.style.width (not multiplied by DPR), this is the bug.

Issue 5: Text Annotation Not Updating

Symptoms:

  • Text tool selected
  • Click to add text
  • Text doesn't appear or doesn't save

Solution: Press Enter or click outside to commit text input.

Issue 6: Undo/Redo Not Working

Symptoms:

  • Press Ctrl+Z but nothing happens

Root Cause: No history to undo (first annotation can't be undone)

Issue 7: SSR/Next.js Issues

Symptoms:

  • "window is not defined"
  • Canvas only renders client-side

Solution:

'use client';
import dynamic from 'next/dynamic';

const ImageAnnotation = dynamic(
  () => import('@frank17008/image-annotation').then((mod) => mod.default),
  { ssr: false }
);

Build Commands

pnpm build:lib      # Build package to dist/
pnpm dev:lib       # Watch mode for library
pnpm start         # Run demo app
pnpm build:all    # Build everything

Dependencies

  • React 18+
  • TypeScript 4.5+
  • Canvas 2D API

Common Mistakes

❌ Forgetting CSS Import

// MISSING - component won't render correctly
import { ImageAnnotation } from '@frank17008/image-annotation';

// REQUIRED
import '@frank17008/image-annotation/dist/index.css';

❌ Parent Without Height

// WRONG - canvas has 0x0
<div><ImageAnnotation src="..." /></div>

// CORRECT - explicit height
<div style={{ height: 600 }}><ImageAnnotation src="..." /></div>

❌ Wrong Type Import

// WRONG
import { Annotation } from '@frank17008/image-annotation';

// CORRECT
import type { Annotation } from '@frank17008/image-annotation';

❌ Not Linking onChange with value

// WRONG - controlled without onChange (React warning)
<ImageAnnotation src="..." value={annotations} />

// CORRECT - controlled mode
<ImageAnnotation
  src="..."
  value={annotations}
  onChange={setAnnotations}
/>

Performance Tips

Large Images

For images > 5MB:

  1. Resize before passing to component
  2. Use appropriate container dimensions
  3. Consider lazy loading

Many Annotations (>100)

The component redraws on each change. For performance:

  1. Reduce lineWidth
  2. Simplify freehand paths (reduce point count)

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,401. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.