feat: add feather brush tool with adjustable settings and blending functionality
- Implemented feather brush tool in the brushRaster module, allowing for feathered edges in brush strokes. - Added new FeatherControls component for UI adjustments of feather settings including size, radius, strength, and smoothing. - Updated brush preview logic to accommodate feather tool alongside existing brush and eraser tools. - Enhanced layer rendering to support feather mask previews and interactions. - Introduced blending logic for feathered strokes to mix blurred mask values with original pixels. - Added unit tests for feather blending functionality and tool keybindings. - Updated cursor handling to reflect feather tool usage.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { Rect, Vec2D } from "@core/geometry";
|
||||
import type { RenderFrame } from "@renderer/index";
|
||||
import type { AppState, BrushPreviewState, BrushStrokePreviewState, EditorState, GenerationState, MaskEditState, SelectionState, ViewportState } from "@editor/state";
|
||||
import type { BrushSettings, InteractionMode } from "@editor/tools";
|
||||
import type { BrushSettings, FeatherSettings, InteractionMode } from "@editor/tools";
|
||||
import type { TransformSession, TransformTarget } from "@editor/transform";
|
||||
|
||||
export function selectCanvasRenderFrame(state: AppState): RenderFrame {
|
||||
@@ -63,7 +63,12 @@ function brushPreviewStatesEqual(a: BrushPreviewState | undefined, b: BrushPrevi
|
||||
function brushStrokePreviewStatesEqual(a: BrushStrokePreviewState | undefined, b: BrushStrokePreviewState | undefined): boolean {
|
||||
if (a === b) return true;
|
||||
if (!a || !b) return false;
|
||||
return a.layerId === b.layerId && a.assetId === b.assetId && a.source === b.source;
|
||||
return a.layerId === b.layerId && a.assetId === b.assetId && a.source === b.source && a.pendingTargetLayerId === b.pendingTargetLayerId && sizesEqual(a.intrinsicSize, b.intrinsicSize);
|
||||
}
|
||||
|
||||
function sizesEqual(a: { w: number; h: number } | undefined, b: { w: number; h: number } | undefined) {
|
||||
if (a === b) return true;
|
||||
return Boolean(a && b && a.w === b.w && a.h === b.h);
|
||||
}
|
||||
|
||||
function generationStatesEqual(a: GenerationState, b: GenerationState): boolean {
|
||||
@@ -71,7 +76,7 @@ function generationStatesEqual(a: GenerationState, b: GenerationState): boolean
|
||||
}
|
||||
|
||||
function visualToolStatesEqual(a: EditorState["tools"], b: EditorState["tools"]): boolean {
|
||||
return a.activeTool === b.activeTool && interactionModesEqual(a.interactionMode, b.interactionMode) && brushSettingsEqual(a.brush, b.brush);
|
||||
return a.activeTool === b.activeTool && interactionModesEqual(a.interactionMode, b.interactionMode) && brushSettingsEqual(a.brush, b.brush) && featherSettingsEqual(a.feather, b.feather);
|
||||
}
|
||||
|
||||
function interactionModesEqual(a: InteractionMode, b: InteractionMode): boolean {
|
||||
@@ -84,6 +89,10 @@ function brushSettingsEqual(a: BrushSettings, b: BrushSettings): boolean {
|
||||
return a.color === b.color && a.size === b.size && a.hardness === b.hardness && a.opacity === b.opacity && a.flow === b.flow && a.smoothing === b.smoothing && a.pressureSize === b.pressureSize;
|
||||
}
|
||||
|
||||
function featherSettingsEqual(a: FeatherSettings, b: FeatherSettings): boolean {
|
||||
return a.size === b.size && a.radius === b.radius && a.strength === b.strength && a.smoothing === b.smoothing && a.pressureSize === b.pressureSize;
|
||||
}
|
||||
|
||||
function vec2Equal(a: Vec2D, b: Vec2D): boolean {
|
||||
return a.x === b.x && a.y === b.y;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user