feat(phase-7): implement comprehensive effects & filters system
This commit completes Phase 7 of the paint-ui implementation, adding a complete filters and effects system with live preview capabilities. **New Files:** - types/filter.ts: Filter types, parameters, and state interfaces - lib/filter-utils.ts: Core filter algorithms and image processing functions - core/commands/filter-command.ts: Undo/redo support for filters - store/filter-store.ts: Filter state management with Zustand - hooks/use-filter-preview.ts: Real-time filter preview system - components/filters/filter-panel.tsx: Complete filter UI with parameters - components/filters/index.ts: Filters barrel export **Updated Files:** - components/editor/editor-layout.tsx: Integrated FilterPanel into layout - store/index.ts: Added filter-store export - types/index.ts: Added filter types export **Implemented Filters:** **Adjustment Filters (with parameters):** - ✨ Brightness (-100 to +100): Linear brightness adjustment - ✨ Contrast (-100 to +100): Contrast curve adjustment - ✨ Hue/Saturation/Lightness: Full HSL color manipulation - Hue: -180° to +180° rotation - Saturation: -100% to +100% adjustment - Lightness: -100% to +100% adjustment **Effect Filters (with parameters):** - ✨ Gaussian Blur (1-50px): Separable kernel blur with proper edge handling - ✨ Sharpen (0-100%): Unsharp mask algorithm - ✨ Threshold (0-255): Binary threshold conversion - ✨ Posterize (2-256 levels): Color quantization **One-Click Filters (no parameters):** - ✨ Invert: Color inversion - ✨ Grayscale: Luminosity-based desaturation - ✨ Sepia: Classic sepia tone effect **Technical Features:** - Real-time preview system with toggle control - Non-destructive preview (restores original on cancel) - Undo/redo integration via FilterCommand - Efficient image processing with typed arrays - HSL/RGB color space conversions - Separable Gaussian blur for performance - Proper clamping and edge case handling - Layer-aware filtering (respects locked layers) **UI/UX Features:** - 264px wide filter panel with all filters listed - Dynamic parameter controls based on selected filter - Live preview toggle with visual feedback - Apply/Cancel actions with proper state cleanup - Disabled state when no unlocked layer selected - Clear parameter labels and value display **Algorithm Implementations:** - Brightness: Linear RGB adjustment with clamping - Contrast: Standard contrast curve (factor-based) - Hue/Saturation: Full RGB↔HSL conversion with proper hue rotation - Blur: Separable Gaussian kernel (horizontal + vertical passes) - Sharpen: Convolution kernel with configurable amount - Threshold: Luminosity-based binary conversion - Posterize: Color quantization with configurable levels Build verified: ✓ Compiled successfully in 1248ms 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
105
core/commands/filter-command.ts
Normal file
105
core/commands/filter-command.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { BaseCommand } from './base-command';
|
||||
import type { Layer, FilterType, FilterParams } from '@/types';
|
||||
import { applyFilter } from '@/lib/filter-utils';
|
||||
import { cloneCanvas } from '@/lib/canvas-utils';
|
||||
|
||||
export class FilterCommand extends BaseCommand {
|
||||
private layerId: string;
|
||||
private filterType: FilterType;
|
||||
private filterParams: FilterParams;
|
||||
private beforeCanvas: HTMLCanvasElement | null = null;
|
||||
private afterCanvas: HTMLCanvasElement | null = null;
|
||||
|
||||
constructor(
|
||||
layer: Layer,
|
||||
filterType: FilterType,
|
||||
filterParams: FilterParams
|
||||
) {
|
||||
super(`Apply ${filterType} filter`);
|
||||
this.layerId = layer.id;
|
||||
this.filterType = filterType;
|
||||
this.filterParams = filterParams;
|
||||
|
||||
// Capture the before state
|
||||
if (layer.canvas) {
|
||||
this.beforeCanvas = cloneCanvas(layer.canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the state after applying the filter
|
||||
*/
|
||||
captureAfterState(layer: Layer): void {
|
||||
if (layer.canvas) {
|
||||
this.afterCanvas = cloneCanvas(layer.canvas);
|
||||
}
|
||||
}
|
||||
|
||||
execute(): void {
|
||||
// Restore the after state
|
||||
if (this.afterCanvas) {
|
||||
const layer = this.getLayer();
|
||||
if (layer?.canvas) {
|
||||
const ctx = layer.canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
||||
ctx.drawImage(this.afterCanvas, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
undo(): void {
|
||||
// Restore the before state
|
||||
if (this.beforeCanvas) {
|
||||
const layer = this.getLayer();
|
||||
if (layer?.canvas) {
|
||||
const ctx = layer.canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
||||
ctx.drawImage(this.beforeCanvas, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getLayer(): Layer | undefined {
|
||||
const { useLayerStore } = require('@/store/layer-store');
|
||||
const { layers } = useLayerStore.getState();
|
||||
return layers.find((l: Layer) => l.id === this.layerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filter to a layer and return the command
|
||||
*/
|
||||
static applyToLayer(
|
||||
layer: Layer,
|
||||
filterType: FilterType,
|
||||
filterParams: FilterParams
|
||||
): FilterCommand {
|
||||
const command = new FilterCommand(layer, filterType, filterParams);
|
||||
|
||||
// Apply the filter
|
||||
if (layer.canvas) {
|
||||
const ctx = layer.canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
const imageData = ctx.getImageData(
|
||||
0,
|
||||
0,
|
||||
layer.canvas.width,
|
||||
layer.canvas.height
|
||||
);
|
||||
const filteredData = applyFilter(imageData, filterType, filterParams);
|
||||
ctx.putImageData(filteredData, 0, 0);
|
||||
|
||||
// Update the layer's updatedAt timestamp
|
||||
const { useLayerStore } = require('@/store/layer-store');
|
||||
const { updateLayer } = useLayerStore.getState();
|
||||
updateLayer(layer.id, { updatedAt: Date.now() });
|
||||
}
|
||||
}
|
||||
|
||||
command.captureAfterState(layer);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user