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>
328 lines
6.7 KiB
TypeScript
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',
|
|
};
|