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>
This commit is contained in:
327
types/layer-effects.ts
Normal file
327
types/layer-effects.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* 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',
|
||||
};
|
||||
Reference in New Issue
Block a user