- Implemented a new crate `hcie-watercolor-brushes` containing watercolor brush presets and a procedural splat preview renderer. - Integrated watercolor brushes into the iced GUI's Brushes panel, adding a new category for watercolor brushes. - Updated the `BrushCategory` enum to include `Watercolor` and modified the rendering logic to display watercolor presets. - Added functionality to generate watercolor splat thumbnails using a noise-based algorithm. - Ensured that existing brush styles and engine crates remain unmodified, adhering to project constraints.
7.7 KiB
Watercolor Brush Category — Implementation Plan
Goal
Add a new Watercolor brush category to the iced GUI's Brushes panel. Each brush has a watercolor-splat preview (not the standard sine-wave stroke). The brushes simulate realistic watercolor effects. Implemented as a new crate with zero modification to existing brush/engine crates.
Architecture Overview
Existing brush flow
hcie-brush-enginedefinesBrushStyleenum (33 variants includingWatercolor) andBrushPresetstructhcie-engine-apire-exportsBrushPresetand providesdraw_specialized_stroke→ dispatches todraw_watercolor_brushforBrushStyle::Watercolorhcie-iced-gui/src/panels/brushes.rsdefinesBrushCategoryenum,BRUSH_STYLESstatic array, andgenerate_brush_preview()for thumbnails- When a preset is selected →
Message::BrushPresetSelected(preset)→ setstool_state.brush_style,brush_size,brush_opacity,brush_hardness,brush_spacing,active_brush_preset
Key constraint
Engine crates (hcie-brush-engine, hcie-protocol, hcie-engine-api, hcie-draw) are locked — agents must not modify them. The new crate lives in the GUI layer and reuses the engine's existing BrushStyle::Watercolor rendering path.
New crate strategy
- Define watercolor presets using existing
BrushPreset(fromhcie-engine-api) withstyle: BrushStyle::Watercolor - Provide a watercolor splat preview renderer that generates RGBA pixels for thumbnails
- The brushes panel integrates the new crate's presets under a "Watercolor" category tab
- Actual drawing uses the engine's existing watercolor brush rendering — no engine changes needed
Step 1: Create the new crate hcie-watercolor-brushes
Path: hcie-iced-app/crates/hcie-watercolor-brushes/
Cargo.toml
[package]
name = "hcie-watercolor-brushes"
version.workspace = true
edition.workspace = true
[dependencies]
hcie-engine-api = { path = "../../../hcie-engine-api" }
src/lib.rs — Presets + Preview Renderer
Exports:
pub fn watercolor_presets() -> Vec<BrushPreset>— returns 8 watercolor brush presetspub fn render_watercolor_splat(style: BrushStyle, base_color: [u8; 3]) -> Vec<u8>— generates 64×64 RGBA watercolor splat preview
Presets (8 brushes)
| ID | Name | Size | Opacity | Hardness | Spacing | Description |
|---|---|---|---|---|---|---|
wc_wash |
Watercolor Wash | 80 | 0.25 | 0.0 | 0.08 | Large, transparent wash |
wc_wet |
Wet Watercolor | 60 | 0.35 | 0.0 | 0.06 | Medium, wet-on-wet |
wc_dry |
Dry Brush | 30 | 0.70 | 0.6 | 0.12 | Small, textured, high opacity |
wc_glaze |
Glazing | 50 | 0.15 | 0.0 | 0.10 | Very transparent, layered |
wc_splat |
Splatter | 40 | 0.50 | 0.2 | 0.30 | Scattered droplets |
wc_bleed |
Color Bleed | 70 | 0.30 | 0.0 | 0.07 | Heavy diffusion edges |
wc_grain |
Granulation | 45 | 0.40 | 0.3 | 0.09 | Visible paper grain texture |
wc_bloom |
Bloom | 90 | 0.20 | 0.0 | 0.05 | Soft, blooming edges |
All use BrushStyle::Watercolor as the engine style — the engine's existing draw_watercolor_brush handles the actual stroke rendering.
Watercolor Splat Preview Algorithm (64×64 RGBA)
The splat is generated procedurally using multi-octave value noise:
- Base shape: Radial gradient from center,
alpha = (1 - dist/radius)^1.5 - Edge displacement: Displace the radius at each angle using 3-octave value noise → irregular organic boundary
- Multiple layers: 3-5 overlapping semi-transparent blobs at slightly offset positions
- Color variation: Shift hue slightly per layer (±10°) for color separation
- Granulation: High-frequency noise modulates alpha in the outer 40% of the radius
- Wet edge: Slightly darker/more saturated ring near the boundary (wet-edge accumulation effect)
The noise function is a simple hash-based value noise (no external deps):
fn value_noise(x: f32, y: f32) -> f32 {
// hash-based pseudo-random, returns 0.0..1.0
}
fn fbm(x: f32, y: f32, octaves: u32) -> f32 {
// fractal Brownian motion using value_noise
}
Step 2: Register the crate in the workspace
File: Cargo.toml (workspace root)
Add to [workspace] members:
"hcie-iced-app/crates/hcie-watercolor-brushes",
Step 3: Add hcie-watercolor-brushes dependency to hcie-iced-gui
File: hcie-iced-app/crates/hcie-iced-gui/Cargo.toml
Add:
hcie-watercolor-brushes = { path = "../hcie-watercolor-brushes" }
Step 4: Update the Brushes panel
File: hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs
4a. Add Watercolor variant to BrushCategory
pub enum BrushCategory {
All,
Drawing,
Painting,
Watercolor, // ← NEW
Effects,
Custom,
Imported,
}
4b. Update category_matches_style for the new variant
BrushCategory::Watercolor => style_category == BrushCategory::Watercolor,
4c. Add Watercolor tab to the category tabs array
(BrushCategory::Watercolor, "Watercolor", "icons/brush.svg"),
4d. Render watercolor presets when Watercolor category is active
When active_category == BrushCategory::Watercolor:
- Call
hcie_watercolor_brushes::watercolor_presets()to get the preset list - For each preset, call
hcie_watercolor_brushes::render_watercolor_splat(...)for the thumbnail - Render as a grid (same 2-column layout as existing brushes)
- Each cell sends
Message::BrushPresetSelected(preset)on click
4e. Update view function signature
The function already accepts imported_presets: &[BrushPreset]. No signature change needed — the watercolor presets are fetched internally from the new crate.
4f. Update test catalog_contains_every_engine_brush_style
The test asserts BRUSH_STYLES.len() == 34. This doesn't change (watercolor presets are separate from BRUSH_STYLES). The test remains valid.
Step 5: Add "Watercolor" to the egui GUI category filter (optional, for parity)
File: hcie-egui-app/crates/hcie-gui-egui/src/app/tools/brushes_panel.rs
Add "Watercolor" to the categories array and update matches_style_category to map it to BrushStyle::Watercolor. This is a minimal change for parity — the watercolor presets themselves would need separate integration in the egui panel if desired.
Note: This step is optional for the initial implementation. The user's request focuses on the iced GUI.
Files Changed
| File | Change |
|---|---|
hcie-iced-app/crates/hcie-watercolor-brushes/Cargo.toml |
New — crate manifest |
hcie-iced-app/crates/hcie-watercolor-brushes/src/lib.rs |
New — presets + splat renderer |
Cargo.toml (workspace root) |
Add member hcie-iced-app/crates/hcie-watercolor-brushes |
hcie-iced-app/crates/hcie-iced-gui/Cargo.toml |
Add dependency hcie-watercolor-brushes |
hcie-iced-app/crates/hcie-iced-gui/src/panels/brushes.rs |
Add Watercolor category, tab, and grid rendering |
Files NOT modified: All engine crates (hcie-brush-engine, hcie-protocol, hcie-engine-api, hcie-draw), existing brush definitions, existing BRUSH_STYLES array.
Validation
cargo check -p hcie-watercolor-brushes— new crate compilescargo check -p hcie-iced-gui— GUI compiles with new dependencycargo test -p hcie-iced-gui— all existing tests passcargo test -p hcie-watercolor-brushes— new crate tests passcargo test --test feature_scorecard— scorecard passes- Visual: launch the app, open Brushes panel, click "Watercolor" tab → 8 watercolor presets with splat previews appear
- Visual: select a watercolor brush → draw on canvas → realistic watercolor stroke rendered
- Regression: existing Drawing/Painting/Effects brushes unchanged