@shopg41/wimp
v0.0.2
Published
wimp - Web-based Image Manipulation Program
Readme
wimp - Web-based Image Manipulation Program
I created this project because I couldn't find an open-source and free-to-use image editor library that had solid touch support and was actively maintained. My goal is to provide a powerful and easy-to-use image editor for developers to integrate into their projects.
This project is a professional image editing application built with React, HTML5 Canvas, and Zustand for state management.
Features
- Transform Tools: Rotate, flip, and resize your image with ease.
- Crop Tools: Crop your images with various aspect ratio guides or freeform.
- Drawing Tools: Draw on your images with a pencil, eraser, and text tool.
- Adjustments: Brightness and contrast controls.
- Filters: Apply grayscale, sepia, and vintage filters.
- Secure Blur: Redact sensitive information with blur tools.
- File Operations: Open, save, and export images in multiple formats.
- Undo/Redo: Don't worry about making mistakes, you can always undo and redo your actions.
- Touch Support: Works on mobile devices with touch support.
- Zoom & Pan: Navigate large images with intuitive controls.
Architecture
This project uses:
- React - UI framework
- Zustand - State management with a centralized store
- TypeBox - Runtime type validation
- HTML5 Canvas - Image manipulation
Store Structure
The Zustand store (src/store/canvasStore.ts) manages all application state:
- Tool State: Current tool selection
- Drawing Settings: Color, brush size, font family
- Text Objects: Text overlays on canvas
- Adjustments: Brightness, contrast, filters
- Blur Settings: Radius and mode
- Crop State: Crop mode, selection, aspect ratios
- Zoom/Pan: Viewport transformations
- History: Undo/redo stack (limited to 20 states)
Components access state directly from the store using useCanvasStore(), eliminating prop drilling.
Getting Started
- Clone the repository:
git clone https://github.com/shopg41/wimp.git - Install dependencies:
bun install - Start the development server:
bun run dev
Installation
npm install @shopg41/wimp
# or
bun add @shopg41/wimp
# or
yarn add @shopg41/wimpUsing it as a Library
To use this image editor in your own project, import the Wimp component and the necessary styles.
import { Wimp } from '@shopg41/wimp';
import '@shopg41/wimp/style.css';
function App() {
return (
<div style={{ height: '100vh' }}>
<Wimp />
</div>
);
}Accessing the Store
You can also access the Zustand store directly:
import { useCanvasStore } from '@shopg41/wimp';
function MyComponent() {
const currentTool = useCanvasStore((state) => state.currentTool);
const setCurrentTool = useCanvasStore((state) => state.setCurrentTool);
const zoom = useCanvasStore((state) => state.zoom);
// Use selectors for better performance
const historyIndex = useCanvasStore((s) => s.historyIndex);
return (
<div>
<p>Current tool: {currentTool}</p>
<p>Zoom: {Math.round(zoom * 100)}%</p>
<p>History: {historyIndex}</p>
</div>
);
}Performance Benchmarks
Performance benchmarks are run using Bun. Results below are from local development environment (M2 MacBook).
Canvas Scaling Performance
Time to scale images using HTML5 Canvas drawImage with high-quality smoothing.
| Image Size | Scale 0.5x | Scale 1.0x | Scale 1.5x | Scale 2.0x | |------------|------------|------------|------------|------------| | Small (640x480) | 1.38ms | 3.81ms | 8.37ms | 14.52ms | | Medium (1280x720) | 2.70ms | 10.71ms | 24.99ms | 43.86ms | | Large (1920x1080) | 6.19ms | 24.14ms | 55.05ms | 99.00ms | | 4K (3840x2160) | 24.55ms | 98.24ms | 227.44ms | 415.45ms |
Scaling Performance Notes:
- Operations scale quadratically with image dimensions
- 4K image scaling to 2x can take 400+ ms (notable UI lag)
- Recommendation: Show loading indicator for images >2K resolution
Blur Processing Performance
Time to apply secure blur with different radii (optimized algorithm).
| Image Size | Radius 10px | Radius 25px | Radius 50px | |------------|-------------|-------------|--------------| | Medium (400x300) | 21ms | 88ms | 373ms | | Large (800x600) | 59ms | 349ms | 1493ms | | Full HD (1920x1080) | 192ms | 1138ms | 4886ms |
Blur Performance Notes:
- Selective blur is 40% faster at 25px radius vs original implementation
- For full-image blur (no mask), uses stack blur algorithm - O(n) complexity
- Stack blur stays ~50ms for 400x300 regardless of radius
- Large images with high radius still block - consider Web Worker for production
Full Blur Performance (Stack Blur)
When blurring entire image (no selective mask):
| Image Size | Radius 10px | Radius 25px | Radius 50px | |------------|-------------|-------------|--------------| | Medium (400x300) | 8ms | 18ms | 52ms | | Large (800x600) | 28ms | 65ms | 204ms | | Full HD (1920x1080) | 704ms | 863ms | 875ms |
Full Blur Notes:
- Stack blur algorithm is O(n) - scales linearly with image size
- Radius has minimal impact on performance (constant time per pixel)
- Recommended for real-time preview when blurring full image
Canvas Cropping Performance
Time to crop images at various percentages.
| Image Size | Crop 25% | Crop 50% | Crop 75% | |------------|----------|----------|----------| | Small (640x480) | 3.63ms | 6.30ms | 11.31ms | | Medium (1280x720) | 10.41ms | 20.41ms | 33.40ms | | Large (1920x1080) | 21.64ms | 41.22ms | 76.98ms | | 4K (3840x2160) | 86.82ms | 167.40ms | 324.76ms |
Cropping Performance Notes:
- Cropping involves getImageData, temp canvas creation, and putImageData
- Large crops take longer due to memory operations
- 4K crops can take 80-325ms depending on crop size
Canvas History Performance
Time to save/restore canvas state for undo/redo functionality.
| Image Size | Save State | Undo Operation | |------------|------------|----------------| | Small (640x480) | 16.75ms | 2.73ms | | Medium (1280x720) | 49.97ms | 7.50ms | | Large (1920x1080) | 112.69ms | 18.21ms | | 4K (3840x2160) | 430.22ms | 74.84ms |
History Performance Notes:
- Save state is memory-intensive (stores full ImageData)
- 4K images use ~32MB per history state
- History is limited to 20 states to prevent memory issues
- Undo is faster than save (no memory allocation)
Interaction Performance
| Operation | Average Time | |-----------|--------------| | Resize Handle Detection | 0.019ms | | Coordinate Bounding | 0.002ms |
Interaction Notes:
- UI interactions are extremely fast (<0.02ms)
- No performance concerns for real-time interactions
Throughput Test
Continuous operations per second for 1 second:
| Resolution | Operations/sec | Throughput | |------------|----------------|------------| | 720p (1280x720) | 76.5 | 70.5 MP/s | | 1080p (1920x1080) | 37.8 | 78.5 MP/s | | 4K (3840x2160) | 9.9 | 81.9 MP/s |
Throughput Notes:
- Sustained throughput of ~80 MP/s
- Performance is consistent across resolutions
File Decoding Performance
Time to decode images from file (FileReader + Image element).
| Image Size | JPEG Read | PNG Read | JPEG Decode | PNG Decode | |------------|-----------|----------|-------------|------------| | Small (640x480) | 0.034ms | 0.210ms | <0.001ms | <0.001ms | | Medium (1280x720) | 0.302ms | 0.841ms | <0.001ms | <0.001ms | | Large (1920x1080) | 0.047ms | 1.172ms | <0.001ms | <0.001ms | | 4K (3840x2160) | 0.397ms | 7.044ms | <0.001ms | <0.001ms |
Decoding Performance Notes:
- PNG files take longer to read due to larger file size
- JPEG decoding is extremely fast (browser optimized)
- Full load pipeline (File → Decoded): <1ms for all sizes in benchmarks
- Actual browser performance may vary based on image complexity
Running Benchmarks
# Run all performance tests
bun run benchmarks/performance-test.ts
# Run decode performance tests
bun run benchmarks/decode-performance.ts
# Run unit tests
bun testAPI Reference
Store Exports
The following types and functions are exported from the store:
// Types
import {
Tool, // "pencil" | "eraser" | "blur" | "text" | "crop"
CropState, // "inactive" | "creating" | "set" | "moving" | "resizing"
Filter, // "none" | "grayscale" | "sepia" | "vintage"
BlurMode, // "marked" | "unmarked"
TextObject,
Point,
CropRect,
} from '@shopg41/wimp';
// Store
import { useCanvasStore } from '@shopg41/wimp';Store Actions
const store = useCanvasStore.getState();
// Tool
store.setCurrentTool('pencil');
// Drawing
store.setDrawingColor('#ff0000');
store.setBrushSize(10);
store.setFontFamily('Arial');
// Text
store.addTextObject({ id: '1', text: 'Hello', x: 100, y: 100, fontSize: 24, fontFamily: 'Arial', color: '#000' });
store.updateTextObject('1', { text: 'Updated' });
store.removeTextObject('1');
// Adjustments
store.setBrightness(120);
store.setContrast(110);
store.setCurrentFilter('sepia');
// Blur
store.setBlurRadius(25);
store.setBlurMode('marked');
// Crop
store.setCropMode(true);
store.setCropGuide('16:9');
store.resetCrop();
// Zoom/Pan
store.setZoom(2);
store.setPan({ x: 100, y: 50 });
store.zoomIn();
store.zoomOut();
store.autoFit();
// History
store.saveCanvasState();
store.undo();
store.redo();
store.clearHistory();
// File operations
store.loadImageUrl('https://example.com/image.jpg');
store.exportImage('png', 0.92, 1);Subscribing to Store Changes
// Subscribe to specific state changes
useEffect(() => {
return useCanvasStore.subscribe(
(state) => state.currentTool,
(tool) => console.log('Tool changed:', tool)
);
}, []);
// Get current state
const state = useCanvasStore.getState();License
This project is licensed under the MIT License. See the LICENSE file for details.
