Files
paint-ui/types/layer-effects.ts

328 lines
6.7 KiB
TypeScript
Raw Normal View History

feat(phase-11): implement comprehensive non-destructive layer effects system Adds Photoshop-style layer effects with full non-destructive editing support: **Core Architecture:** - Type system with 10 effect types and discriminated unions - Zustand store with Map-based storage and localStorage persistence - Canvas-based rendering engine with intelligent padding calculation - Effects applied at render time without modifying original layer data **Implemented Effects (6 core effects):** - Drop Shadow - Customizable shadow with angle, distance, size, and spread - Outer Glow - Soft glow around layer edges with spread control - Inner Shadow - Shadow effect inside layer boundaries - Inner Glow - Inward glow from edges with choke parameter - Stroke/Outline - Configurable stroke with position options - Color Overlay - Solid color overlay with blend modes **Rendering Engine Features:** - Smart padding calculation for effects extending beyond layer bounds - Effect stacking: Background → Layer → Modifying → Overlay - Canvas composition for complex effects (inner shadow/glow) - Global light system for consistent shadow angles - Blend mode support for all effects - Opacity control per effect **User Interface:** - Integrated effects panel in layers sidebar - Collapsible panel with effect count badge - Add effect dropdown with 6 effect types - Individual effect controls (visibility toggle, duplicate, delete) - Master enable/disable for all layer effects - Visual feedback with toast notifications **Store Features:** - Per-layer effects configuration - Effect reordering support - Copy/paste effects between layers - Duplicate effects within layer - Persistent storage across sessions - Global light angle/altitude management **Technical Implementation:** - Non-destructive: Original layer canvas never modified - Performance optimized with canvas padding only where needed - Type-safe with full TypeScript discriminated unions - Effects rendered in optimal order for visual quality - Map serialization for Zustand persistence **New Files:** - types/layer-effects.ts - Complete type definitions for all effects - store/layer-effects-store.ts - Zustand store with persistence - lib/layer-effects-renderer.ts - Canvas rendering engine - components/layers/layer-effects-panel.tsx - UI controls **Modified Files:** - components/canvas/canvas-with-tools.tsx - Integrated effects rendering - components/layers/layers-panel.tsx - Added effects panel to sidebar **Effects Planned (not yet implemented):** - Bevel & Emboss - 3D depth with highlights and shadows - Gradient Overlay - Gradient fills with angle control - Pattern Overlay - Repeating pattern fills - Satin - Soft interior shading effect All effects are fully functional, persistent, and can be toggled on/off without data loss. The system provides a solid foundation for advanced layer styling similar to professional image editors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 17:42:36 +01:00
/**
* Layer Effects Type Definitions
* Non-destructive visual effects for layers (inspired by Photoshop Layer Styles)
*/
export type EffectType =
| 'dropShadow'
| 'innerShadow'
| 'outerGlow'
| 'innerGlow'
| 'stroke'
| 'bevel'
| 'colorOverlay'
| 'gradientOverlay'
| 'patternOverlay'
| 'satin';
export type BlendMode =
| 'normal'
| 'multiply'
| 'screen'
| 'overlay'
| 'soft-light'
| 'hard-light'
| 'color-dodge'
| 'color-burn'
| 'darken'
| 'lighten';
export type GradientType = 'linear' | 'radial' | 'angle';
export type StrokePosition = 'outside' | 'inside' | 'center';
export type BevelStyle = 'outer-bevel' | 'inner-bevel' | 'emboss' | 'pillow-emboss';
export type BevelDirection = 'up' | 'down';
/**
* Base effect interface - all effects extend this
*/
export interface BaseEffect {
id: string;
type: EffectType;
enabled: boolean;
blendMode: BlendMode;
opacity: number; // 0-1
}
/**
* Drop Shadow Effect
* Casts a shadow behind the layer
*/
export interface DropShadowEffect extends BaseEffect {
type: 'dropShadow';
color: string;
angle: number; // degrees 0-360
distance: number; // pixels
spread: number; // 0-100%
size: number; // blur radius in pixels
useGlobalLight: boolean;
}
/**
* Inner Shadow Effect
* Shadow appears inside the layer
*/
export interface InnerShadowEffect extends BaseEffect {
type: 'innerShadow';
color: string;
angle: number;
distance: number;
choke: number; // 0-100%, similar to spread but inward
size: number;
useGlobalLight: boolean;
}
/**
* Outer Glow Effect
* Soft glow around the outside edges
*/
export interface OuterGlowEffect extends BaseEffect {
type: 'outerGlow';
color: string;
spread: number; // 0-100%
size: number; // blur radius
technique: 'softer' | 'precise';
range: number; // 0-100%
}
/**
* Inner Glow Effect
* Glow from the edges inward
*/
export interface InnerGlowEffect extends BaseEffect {
type: 'innerGlow';
color: string;
source: 'edge' | 'center';
choke: number; // 0-100%
size: number;
technique: 'softer' | 'precise';
range: number;
}
/**
* Stroke Effect
* Outline around the layer
*/
export interface StrokeEffect extends BaseEffect {
type: 'stroke';
size: number; // stroke width in pixels
position: StrokePosition;
fillType: 'color' | 'gradient' | 'pattern';
color: string; // for solid color
gradient?: {
type: GradientType;
colors: Array<{ color: string; position: number }>; // position 0-1
angle: number;
};
pattern?: {
id: string;
scale: number;
};
}
/**
* Bevel & Emboss Effect
* Creates 3D depth effect
*/
export interface BevelEffect extends BaseEffect {
type: 'bevel';
style: BevelStyle;
technique: 'smooth' | 'chisel-hard' | 'chisel-soft';
depth: number; // 0-1000%
direction: BevelDirection;
size: number; // pixels
soften: number; // pixels
angle: number; // light angle
altitude: number; // light altitude 0-90 degrees
useGlobalLight: boolean;
highlightMode: BlendMode;
highlightOpacity: number;
highlightColor: string;
shadowMode: BlendMode;
shadowOpacity: number;
shadowColor: string;
}
/**
* Color Overlay Effect
* Fills the layer with a solid color
*/
export interface ColorOverlayEffect extends BaseEffect {
type: 'colorOverlay';
color: string;
}
/**
* Gradient Overlay Effect
* Fills the layer with a gradient
*/
export interface GradientOverlayEffect extends BaseEffect {
type: 'gradientOverlay';
gradient: {
type: GradientType;
colors: Array<{ color: string; position: number }>;
angle: number;
scale: number; // 0-150%
reverse: boolean;
alignWithLayer: boolean;
};
}
/**
* Pattern Overlay Effect
* Fills the layer with a repeating pattern
*/
export interface PatternOverlayEffect extends BaseEffect {
type: 'patternOverlay';
pattern: {
id: string;
scale: number; // percentage
snapToOrigin: boolean;
};
}
/**
* Satin Effect
* Creates soft interior shading
*/
export interface SatinEffect extends BaseEffect {
type: 'satin';
color: string;
angle: number;
distance: number;
size: number; // blur
contour: 'linear' | 'gaussian' | 'cone' | 'cove';
invert: boolean;
}
/**
* Union type of all effect types
*/
export type LayerEffect =
| DropShadowEffect
| InnerShadowEffect
| OuterGlowEffect
| InnerGlowEffect
| StrokeEffect
| BevelEffect
| ColorOverlayEffect
| GradientOverlayEffect
| PatternOverlayEffect
| SatinEffect;
/**
* Layer effects configuration for a single layer
*/
export interface LayerEffectsConfig {
layerId: string;
effects: LayerEffect[];
enabled: boolean; // Master switch for all effects
globalLightAngle: number; // Shared light angle (default 120°)
globalLightAltitude: number; // Shared light altitude (default 30°)
}
/**
* Effect presets for quick application
*/
export interface EffectPreset {
id: string;
name: string;
description?: string;
thumbnail?: string;
effects: Omit<LayerEffect, 'id'>[];
}
/**
* Default effect configurations
*/
export const DEFAULT_DROP_SHADOW: Omit<DropShadowEffect, 'id'> = {
type: 'dropShadow',
enabled: true,
blendMode: 'multiply',
opacity: 0.75,
color: '#000000',
angle: 120,
distance: 5,
spread: 0,
size: 5,
useGlobalLight: true,
};
export const DEFAULT_OUTER_GLOW: Omit<OuterGlowEffect, 'id'> = {
type: 'outerGlow',
enabled: true,
blendMode: 'screen',
opacity: 0.75,
color: '#ffffff',
spread: 0,
size: 5,
technique: 'softer',
range: 50,
};
export const DEFAULT_STROKE: Omit<StrokeEffect, 'id'> = {
type: 'stroke',
enabled: true,
blendMode: 'normal',
opacity: 1,
size: 3,
position: 'outside',
fillType: 'color',
color: '#000000',
};
export const DEFAULT_COLOR_OVERLAY: Omit<ColorOverlayEffect, 'id'> = {
type: 'colorOverlay',
enabled: true,
blendMode: 'normal',
opacity: 1,
color: '#000000',
};
export const DEFAULT_INNER_SHADOW: Omit<InnerShadowEffect, 'id'> = {
type: 'innerShadow',
enabled: true,
blendMode: 'multiply',
opacity: 0.75,
color: '#000000',
angle: 120,
distance: 5,
choke: 0,
size: 5,
useGlobalLight: true,
};
export const DEFAULT_INNER_GLOW: Omit<InnerGlowEffect, 'id'> = {
type: 'innerGlow',
enabled: true,
blendMode: 'screen',
opacity: 0.75,
color: '#ffffff',
source: 'edge',
choke: 0,
size: 5,
technique: 'softer',
range: 50,
};
export const DEFAULT_BEVEL: Omit<BevelEffect, 'id'> = {
type: 'bevel',
enabled: true,
blendMode: 'normal',
opacity: 1,
style: 'inner-bevel',
technique: 'smooth',
depth: 100,
direction: 'up',
size: 5,
soften: 0,
angle: 120,
altitude: 30,
useGlobalLight: true,
highlightMode: 'screen',
highlightOpacity: 0.75,
highlightColor: '#ffffff',
shadowMode: 'multiply',
shadowOpacity: 0.75,
shadowColor: '#000000',
};