mui-image-editor
v0.1.3
Published
A React image editor component built with Material-UI
Downloads
409
Maintainers
Readme
MUI Image Editor
A powerful, feature-rich React image editor component built with Material-UI. Edit images directly in your React applications with support for cropping, rotation, flipping, filters, and more.
Features
🎨 Comprehensive Editing Tools
- Crop with aspect ratio controls
- Rotate with fine angle adjustment
- Flip horizontally or vertically
- Custom filters with preview
🖼️ Advanced Image Processing
- High-quality canvas-based rendering
- Undo/redo support
- Before/after comparison view
- Zoom and pan controls
- Grid overlays (rule of thirds, golden ratio, pixel grid)
🎯 Developer-Friendly
- TypeScript support with full type definitions
- Imperative API via refs
- State management (save/restore editor state)
- Customizable UI and theming
- Internationalization (i18n) support
⚡ Performance
- Efficient rendering engine
- Preview mode for real-time feedback
- Configurable performance options
Installation
npm install mui-image-editorPeer Dependencies
This package requires the following peer dependencies:
npm install react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styledQuick Start
import React, { useRef } from 'react';
import { ImageEditor, EditorHandle } from 'mui-image-editor';
function App() {
const editorRef = useRef<EditorHandle>(null);
const handleSave = (result) => {
console.log('Saved image:', result);
// result contains: blob, dataUrl, format, width, height, operations
};
return (
<ImageEditor
src="https://example.com/image.jpg"
onSave={handleSave}
ref={editorRef}
/>
);
}API Reference
ImageEditor Component
The main component that renders the image editor interface.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| src | string | required | Image source URL or data URI |
| crossOrigin | "anonymous" \| "use-credentials" \| null | "anonymous" | CORS setting for image loading |
| imageLoader | (src: string) => Promise<Blob \| ArrayBuffer \| File> | - | Custom image loader function |
| initialState | EditorState | - | Initial editor state (operations, viewport) |
| onSave | (result: SaveResult) => void | required | Callback when save is triggered |
| onChange | (preview: HTMLCanvasElement \| ImageBitmap, operations: OperationManifest) => void | - | Callback on each edit operation |
| onError | (err: Error) => void | - | Error handler |
| output | OutputOptions | - | Output configuration |
| ui | UIOptions | - | UI customization options |
| filters | FilterDefinition[] | [traceLinesFilter] | Array of custom filters |
| tools | ToolDefinition[] | - | Custom tool definitions |
| engine | EngineOptions | - | Engine performance options |
| i18n | I18nOptions | - | Internationalization options |
Output Options
{
format?: "png" | "jpeg" | "webp"; // Default: "png"
quality?: number; // Default: 0.92 (for jpeg/webp)
preserveExif?: boolean; // Default: false
background?: string | null; // Background color for transparent areas
colorProfile?: "sRGB" | "display-p3" | "auto";
returnDataUrl?: boolean; // Include data URL in save result
maxSize?: {
width?: number;
height?: number;
fit?: "contain" | "cover" | "downscaleOnly";
};
}UI Options
{
themeMode?: "light" | "dark" | "system";
themeOverrides?: object; // MUI theme overrides
tokens?: Partial<DesignTokens>; // Design token overrides
toolbarPosition?: "top" | "left" | "right";
showGrid?: boolean;
gridType?: "ruleOfThirds" | "goldenRatio" | "pixel" | "none";
snapping?: boolean;
keyboardShortcuts?: boolean; // Default: true
mobileLayout?: "compact" | "auto";
}Engine Options
{
previewScale?: number; // Preview resolution scale
useWebGL?: boolean; // Use WebGL for rendering
workerCount?: number; // Number of web workers
keyframeEvery?: number; // Keyframe interval for history
maxHistoryBytes?: number; // Maximum history size
downscaleLargeImages?: {
maxMP?: number; // Max megapixels before downscaling
method?: "pica" | "canvas";
};
}I18n Options
{
t?: (key: string, params?: Record<string, any>) => string; // Custom translation function
strings?: Partial<Record<EditorStringKey, string>>; // Custom string overrides
locale?: string; // Locale code (default: "en")
rtl?: boolean; // Right-to-left layout
}EditorHandle (Ref API)
Access the editor instance via a ref to use imperative methods:
const editorRef = useRef<EditorHandle>(null);
// Methods available:
editorRef.current?.save(); // Save the edited image
editorRef.current?.reset(); // Reset to original image
editorRef.current?.undo(); // Undo last operation
editorRef.current?.redo(); // Redo last operation
editorRef.current?.zoomToFit(); // Zoom to fit image in viewport
editorRef.current?.zoom(scale); // Set zoom level (0.05 - 8)
editorRef.current?.getState(); // Get current editor state
editorRef.current?.setState(state); // Restore editor stateExamples
Basic Usage
import React, { useRef } from 'react';
import { ImageEditor, EditorHandle } from 'mui-image-editor';
function MyEditor() {
const editorRef = useRef<EditorHandle>(null);
return (
<ImageEditor
src="/path/to/image.jpg"
onSave={(result) => {
// Download the image
const url = URL.createObjectURL(result.blob);
const a = document.createElement('a');
a.href = url;
a.download = 'edited-image.png';
a.click();
}}
ref={editorRef}
/>
);
}With Built-in Filter
import React from 'react';
import { ImageEditor, traceLinesFilter } from 'mui-image-editor';
function App() {
return (
<ImageEditor
src="/image.jpg"
filters={[traceLinesFilter]}
onSave={(result) => console.log(result)}
/>
);
}With Custom Filters
import React from 'react';
import { ImageEditor, FilterDefinition } from 'mui-image-editor';
const customFilter: FilterDefinition = {
id: 'myFilter',
name: 'My Custom Filter',
ui: [
{
kind: 'slider',
key: 'intensity',
label: 'Intensity',
min: 0,
max: 100,
default: 50
}
],
apply: async (ctx, params) => {
const bitmap = await ctx.getImageBitmap();
// Apply your filter logic
return bitmap;
},
preview: async (ctx, params) => {
// Preview version (can be faster/lower quality)
return customFilter.apply(ctx, params);
}
};
function App() {
return (
<ImageEditor
src="/image.jpg"
filters={[customFilter]}
onSave={(result) => console.log(result)}
/>
);
}State Management
import React, { useRef, useState } from 'react';
import { ImageEditor, EditorHandle, EditorState } from 'mui-image-editor';
function App() {
const editorRef = useRef<EditorHandle>(null);
const [savedState, setSavedState] = useState<EditorState | null>(null);
const saveState = () => {
const state = editorRef.current?.getState();
if (state) {
setSavedState(state);
localStorage.setItem('editorState', JSON.stringify(state));
}
};
const restoreState = () => {
if (savedState) {
editorRef.current?.setState(savedState);
}
};
return (
<>
<button onClick={saveState}>Save State</button>
<button onClick={restoreState}>Restore State</button>
<ImageEditor
src="/image.jpg"
initialState={savedState || undefined}
onSave={(result) => console.log(result)}
ref={editorRef}
/>
</>
);
}Custom Image Loader
import React from 'react';
import { ImageEditor } from 'mui-image-editor';
function App() {
const imageLoader = async (src: string) => {
// Custom loading logic (e.g., from authenticated API)
const response = await fetch(src, {
headers: {
'Authorization': 'Bearer token'
}
});
return response.blob();
};
return (
<ImageEditor
src="https://api.example.com/protected-image.jpg"
imageLoader={imageLoader}
onSave={(result) => console.log(result)}
/>
);
}Internationalization
import React from 'react';
import { ImageEditor } from 'mui-image-editor';
function App() {
return (
<ImageEditor
src="/image.jpg"
i18n={{
locale: 'pl',
strings: {
'editor.toolbar.save': 'Zapisz',
'editor.toolbar.undo': 'Cofnij',
'editor.toolbar.redo': 'Ponów'
}
}}
onSave={(result) => console.log(result)}
/>
);
}Output Configuration
import React from 'react';
import { ImageEditor } from 'mui-image-editor';
function App() {
return (
<ImageEditor
src="/image.jpg"
output={{
format: 'jpeg',
quality: 0.9,
maxSize: {
width: 1920,
height: 1080,
fit: 'contain'
},
returnDataUrl: true
}}
onSave={(result) => {
console.log('Format:', result.format);
console.log('Size:', result.width, 'x', result.height);
if (result.dataUrl) {
console.log('Data URL:', result.dataUrl);
}
}}
/>
);
}Exports
The package exports the following:
ImageEditor- The main component (default export)EditorHandle- Type for the editor refImageEditorProps- Component props typeEditorState- Editor state typeOperation,OperationManifest- Operation typesFilterDefinition- Filter definition typetraceLinesFilter- Built-in edge detection filtertranslations- Built-in translations object
Types
Operation Types
The editor supports several operation types:
- CropOperation:
{ type: "crop", rect: {...}, aspect?: string | null } - RotateOperation:
{ type: "rotate", angle: number, expandCanvas: boolean, background?: string } - FlipOperation:
{ type: "flip", axis: "x" | "y" } - FilterOperation:
{ type: "filter", id: string, params: Record<string, any>, version?: string }
FilterDefinition
{
id: string;
name: string;
version?: string;
ui?: FilterUiParam[];
preview?: (ctx: FilterContext, params: Record<string, any>) => Promise<ImageBitmap | ImageData>;
apply: (ctx: FilterContext, params: Record<string, any>) => Promise<ImageBitmap | ImageData>;
glApply?: (gl: WebGLRenderingContext, texture: WebGLTexture, params: Record<string, any>) => WebGLTexture;
}Keyboard Shortcuts
Ctrl/Cmd + Z- UndoCtrl/Cmd + Shift + Z- RedoCtrl/Cmd + +- Zoom inCtrl/Cmd + -- Zoom outCtrl/Cmd + 0- Zoom to fitSpace + Drag- PanB(hold) - Before/after comparison
Browser Support
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
Requires modern browser features:
- Canvas API
- ImageBitmap API
- ES2021+ JavaScript
Development
Building the Library
npm run build:libThis will create a dist folder with:
mui-image-editor.es.js- ES module buildmui-image-editor.cjs.js- CommonJS buildindex.d.ts- TypeScript declarations
Publishing to npm
# Simple publish
npm run publish:npm
# Or use the interactive script
npm run publish:script
# or
node publish.jsDevelopment Mode
npm run devLicense
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues, questions, or feature requests, please open an issue on the GitHub repository.
