Files
paint-ui/types/layer-effects.ts
Sebastian Krüger 9aa6e2d5d9 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

328 lines
6.7 KiB
TypeScript

/**
* 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',
};