Files
hcie-rust-v3.05/hcie-ai/src/tools.rs
T
2026-07-09 02:59:53 +03:00

656 lines
38 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Canvas tool definitions sent to the AI as function schemas.
use serde_json::{json, Value};
pub fn canvas_tools() -> Vec<Value> {
vec![
json!({
"type": "function",
"function": {
"name": "create_layer",
"description": "Create a new layer. ALWAYS call this first before drawing. Use 'vector' for shapes/illustrations, 'raster' for pixel painting.",
"parameters": {
"type": "object",
"properties": {
"name": { "type": "string" },
"layer_type": { "type": "string", "enum": ["raster", "vector"] }
},
"required": ["name", "layer_type"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "delete_layer",
"description": "Delete a layer by its name or index. Use with care — cannot undo. If name_or_index is a number, it targets by index (0 = bottom layer). If a string, matches layer name.",
"parameters": {
"type": "object",
"properties": {
"name_or_index": { "type": "string", "description": "Layer name, or '0', '1', etc. for index" }
},
"required": ["name_or_index"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "select_layer",
"description": "Activate a layer by name or index so subsequent drawing affects it.",
"parameters": {
"type": "object",
"properties": {
"name_or_index": { "type": "string", "description": "Layer name, or '0', '1', etc. for index" }
},
"required": ["name_or_index"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "rename_layer",
"description": "Rename a layer by name or index.",
"parameters": {
"type": "object",
"properties": {
"name_or_index": { "type": "string", "description": "Layer name, or '0', '1', etc. for index" },
"new_name": { "type": "string" }
},
"required": ["name_or_index", "new_name"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "set_layer_opacity",
"description": "Set layer opacity (transparency).",
"parameters": {
"type": "object",
"properties": {
"name_or_index": { "type": "string" },
"opacity": { "type": "number", "description": "0.0 = fully transparent, 1.0 = fully opaque" }
},
"required": ["name_or_index", "opacity"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "set_layer_blend_mode",
"description": "Set layer blend mode.",
"parameters": {
"type": "object",
"properties": {
"name_or_index": { "type": "string" },
"blend_mode": { "type": "string", "enum": ["Normal","Multiply","Screen","Overlay","Darken","Lighten","ColorDodge","ColorBurn","HardLight","SoftLight","Difference","Exclusion"] }
},
"required": ["name_or_index", "blend_mode"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "toggle_layer_visibility",
"description": "Toggle a layer's visibility on/off.",
"parameters": {
"type": "object",
"properties": {
"name_or_index": { "type": "string" }
},
"required": ["name_or_index"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "add_ellipse",
"description": "Add a circle or ellipse to the active vector layer. Use this for circles, ovals, round shapes. All coords normalised 0.01.0.",
"parameters": {
"type": "object",
"properties": {
"cx": { "type": "number", "description": "Center X (01)" },
"cy": { "type": "number", "description": "Center Y (01)" },
"rx": { "type": "number", "description": "X radius (01), e.g. 0.15 for medium circle" },
"ry": { "type": "number", "description": "Y radius (01)" },
"fill_color": { "type": "string", "description": "CSS hex e.g. '#ff0000' or 'none'" },
"stroke_color": { "type": "string", "description": "CSS hex e.g. '#ff0000'" },
"stroke_width": { "type": "number", "description": "Pixel width ≥ 1.0. Use 2.05.0 for visible strokes." }
},
"required": ["cx", "cy", "rx", "ry", "fill_color", "stroke_color", "stroke_width"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "add_rect",
"description": "Add a rectangle to the active vector layer. All coords normalised 0.01.0.",
"parameters": {
"type": "object",
"properties": {
"x1": { "type": "number", "description": "Left edge (01)" },
"y1": { "type": "number", "description": "Top edge (01)" },
"x2": { "type": "number", "description": "Right edge (01)" },
"y2": { "type": "number", "description": "Bottom edge (01)" },
"radius": { "type": "number", "description": "Corner radius in px, 0 for sharp" },
"fill_color": { "type": "string" },
"stroke_color": { "type": "string" },
"stroke_width": { "type": "number", "description": "≥ 1.0 for visible" }
},
"required": ["x1", "y1", "x2", "y2", "fill_color", "stroke_color", "stroke_width"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "add_vector_path",
"description": "Add a freeform polygon/path to the active vector layer. Use for irregular shapes like petals, leaves, custom outlines. Points are normalised 01.",
"parameters": {
"type": "object",
"properties": {
"points": {
"type": "array",
"items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 },
"description": "[[x,y], ...] control points (01 normalised)"
},
"closed": { "type": "boolean", "description": "true = closed shape (polygon), false = open line" },
"fill_color": { "type": "string", "description": "CSS hex or 'none'" },
"stroke_color": { "type": "string" },
"stroke_width": { "type": "number", "description": "≥ 1.0 for visible" }
},
"required": ["points", "closed", "fill_color", "stroke_color", "stroke_width"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "fill_raster_rect",
"description": "Fill a rectangular area on the active raster layer with a solid color. Use for backgrounds, color fills.",
"parameters": {
"type": "object",
"properties": {
"x1": { "type": "number", "description": "Left (01)" },
"y1": { "type": "number", "description": "Top (01)" },
"x2": { "type": "number", "description": "Right (01)" },
"y2": { "type": "number", "description": "Bottom (01)" },
"color": { "type": "string", "description": "CSS hex e.g. '#ff88bb'" }
},
"required": ["x1", "y1", "x2", "y2", "color"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "delete_shape",
"description": "Delete a vector shape from the active layer by its index. Shape indices start at 0 and correspond to their order in the shapes panel.",
"parameters": {
"type": "object",
"properties": {
"shape_index": { "type": "integer", "description": "0-based index of the shape to delete" }
},
"required": ["shape_index"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "set_shape_color",
"description": "Change the stroke and/or fill color of a vector shape in the active layer.",
"parameters": {
"type": "object",
"properties": {
"shape_index": { "type": "integer" },
"stroke_color": { "type": "string", "description": "CSS hex or 'none'" },
"fill_color": { "type": "string", "description": "CSS hex or 'none'. Omit to leave unchanged." }
},
"required": ["shape_index", "stroke_color"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "move_shape",
"description": "Move a vector shape by a relative offset in pixels.",
"parameters": {
"type": "object",
"properties": {
"shape_index": { "type": "integer" },
"dx": { "type": "number", "description": "Horizontal offset in pixels" },
"dy": { "type": "number", "description": "Vertical offset in pixels" }
},
"required": ["shape_index", "dx", "dy"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "scale_shape",
"description": "Scale a vector shape relative to its current size.",
"parameters": {
"type": "object",
"properties": {
"shape_index": { "type": "integer" },
"scale_x": { "type": "number", "description": "Horizontal scale factor (1.0 = no change)" },
"scale_y": { "type": "number", "description": "Vertical scale factor (1.0 = no change)" }
},
"required": ["shape_index", "scale_x", "scale_y"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "paint_stroke",
"description": "Paint a freehand brush stroke on the active RASTER layer. Provide a path of normalised points (01). The stroke is painted as a series of dabs between the points. CRITICAL: You MUST vary brush_style for different effects. Never use 'default' for everything. See BRUSH RECIPES in system prompt and call set_brush before painting.",
"parameters": {
"type": "object",
"properties": {
"points": {
"type": "array",
"items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 },
"description": "[[x,y], ...] path points in normalised 01 coords. At least 2 points."
},
"color": { "type": "string", "description": "CSS hex color e.g. '#ff3300'" },
"brush_style": {
"type": "string",
"enum": ["default","pencil","airbrush","marker","watercolor","oil","charcoal","calligraphy","ink","sketch","crayon","glow","bristle"],
"description": "Brush texture. Use 'default' for a clean round brush."
},
"size": { "type": "number", "description": "Brush diameter in pixels (1200)" },
"opacity": { "type": "number", "description": "0.0 = transparent, 1.0 = fully opaque" },
"spacing": { "type": "number", "description": "Dab spacing (0.012.0). Default 0.1. Lower is smoother." },
"hardness": { "type": "number", "description": "Brush edge hardness (0.01.0). 1.0 is sharp, 0.0 is very soft." }
},
"required": ["points", "color", "brush_style", "size", "opacity", "spacing", "hardness"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "set_brush",
"description": "IMPORTANT: Call this BEFORE every paint_stroke to set the brush style, size, opacity, spacing, and hardness. You MUST configure the brush before every stroke — do not paint without calling this first.",
"parameters": {
"type": "object",
"properties": {
"style": { "type": "string", "enum": ["default","pencil","airbrush","marker","watercolor","oil","charcoal","calligraphy","ink","sketch","crayon","glow","bristle"] },
"size": { "type": "number" },
"opacity": { "type": "number" },
"spacing": { "type": "number", "description": "0.012.0" },
"hardness": { "type": "number", "description": "0.01.0" }
}
}
}
}),
json!({
"type": "function",
"function": {
"name": "set_foreground_color",
"description": "Change the active foreground (brush) color.",
"parameters": {
"type": "object",
"properties": {
"hex": { "type": "string", "description": "CSS hex color" }
},
"required": ["hex"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "draw_template",
"description": "Draw a pre-built parametric shape template. Use this for common objects to ensure perfect proportions and standard structure. Check the PROPORTIONS & SCALE section for real-world sizes.",
"parameters": {
"type": "object",
"properties": {
"template": {
"type": "string",
"description": "The name of the template (e.g. house, car)."
},
"cx": { "type": "number", "description": "Center X 0.01.0" },
"cy": { "type": "number", "description": "Center Y 0.01.0" },
"scale": { "type": "number", "description": "Size as fraction of canvas short side (0.050.9)" },
"stroke_color": { "type": "string", "description": "CSS hex stroke color (default: #000000)" },
"stroke_width": { "type": "number", "description": "Stroke width in pixels (default: 2.0)" },
"params": {
"type": "object",
"description": "Optional parameters to customize the template. Keys depend on the template (e.g. 'body_color', 'wheel_color'). All values should be strings.",
"additionalProperties": { "type": "string" }
}
},
"required": ["template", "cx", "cy", "scale"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "finish",
"description": "Call this when all drawing steps are done.",
"parameters": {
"type": "object",
"properties": {
"summary": { "type": "string", "description": "Short description of what was drawn" }
},
"required": ["summary"]
}
}
}),
json!({
"type": "function",
"function": {
"name": "search_templates_db",
"description": "Search the templates database for a named real-world object (e.g. 'car', 'house', 'flower'). Call this before drawing complex objects. Do NOT use for basic geometry — circles use add_ellipse, rectangles use add_rect directly.",
"parameters": {
"type": "object",
"properties": {
"object_name": {
"type": "string",
"description": "The English name of the object to search for (e.g. 'bicycle'). Do not include colors or adjectives."
}
},
"required": ["object_name"]
}
}
}),
]
}
fn persona_role(persona_key: &str) -> &'static str {
match persona_key {
"nature_painter" => {
"You are a landscape and nature painter inspired by Bob Ross and plein-air tradition.\n\
Your world is organic, warm, and alive. Paint in wet-on-wet layers: sky first,\n\
then distant hills, midground trees/water, then foreground detail.\n\
TEMPLATE POLICY: NEVER use search_templates_db or draw_template.\n\
Paint EVERYTHING from scratch using paint_stroke on RASTER layers.\n\
\n\
BRUSH RECIPES (always set these exactly — never rely on defaults):\n\
Sky wash: airbrush size=180 opacity=0.5 spacing=0.02 hardness=0.05\n\
Clouds: airbrush size=90 opacity=0.6 spacing=0.03 hardness=0.05\n\
Distant hills: watercolor size=80 opacity=0.55 spacing=0.04 hardness=0.15\n\
Near hills/land: oil size=60 opacity=0.7 spacing=0.06 hardness=0.25\n\
Tree foliage: bristle size=50 opacity=0.75 spacing=0.05 hardness=0.2\n\
Tree dark base: oil size=40 opacity=0.85 spacing=0.04 hardness=0.3\n\
Tree trunk: ink size=8 opacity=0.9 spacing=0.03 hardness=0.85\n\
Water base: watercolor size=130 opacity=0.4 spacing=0.02 hardness=0.05\n\
Water ripples: watercolor size=30 opacity=0.3 spacing=0.03 hardness=0.1\n\
Highlights: default size=18 opacity=0.85 spacing=0.03 hardness=0.45\n\
Foreground grass: oil size=25 opacity=0.8 spacing=0.05 hardness=0.3\n\
\n\
TREE PAINTING TECHNIQUE (Bob Ross happy trees):\n\
• First lay a dark base: 6-8 vertical oil strokes covering the crown area fully.\n\
• Then stipple bristle strokes over the crown — fan outward from centre, 10-15 dabs.\n\
• spacing=0.05 is mandatory for bristle — higher spacing creates ugly dots.\n\
• Finish with lighter highlight bristle strokes on the lit side (opacity 0.5).\n\
\n\
COVERAGE RULE: every region must be painted with 8-15 overlapping strokes.\n\
If an area looks transparent or dotted after 3-4 strokes, add more before moving on.\n\
Favourite palette: earthy greens, warm browns, sunset oranges, soft sky blues."
}
"flat_ui_designer" => {
"You are a senior UI/UX designer and SVG illustrator.\n\
Your aesthetic: clean, minimal, pixel-perfect. No gradients unless simulated crisply.\n\
You work exclusively with vector shapes (add_vector_path, add_rect, add_ellipse).\n\
Every composition uses a tight, intentional palette of 35 colours.\n\
Shapes have flat solid fills; strokes are either absent or a single consistent weight.\n\
You think in design-system terms: consistent corner radii, harmonious spacing,\n\
purposeful negative space. Icons are always centred, balanced, and scalable.\n\
You never add texture, noise, or hand-painted effects. Perfection over expression.\n\
Perspective choice: always front or top-down — never 3D unless requested."
}
"sketch_artist" => {
"You are a concept sketch artist working fast to capture form and energy.\n\
TEMPLATE POLICY: NEVER use search_templates_db or draw_template.\n\
Sketch EVERYTHING from scratch with paint_stroke on RASTER layers.\n\
Construct forms with many short overlapping strokes — not single perfect outlines.\n\
Use dynamic perspectives — 3/4 views, foreshortening, dramatic angles.\n\
\n\
BRUSH RECIPES (always set these exactly — never rely on defaults):\n\
Rough gesture: pencil size=4 opacity=0.25 spacing=0.03 hardness=0.9\n\
Form outline: pencil size=3 opacity=0.35 spacing=0.03 hardness=0.92\n\
Shadow hatching: sketch size=3 opacity=0.18 spacing=0.04 hardness=0.85\n\
Deep shadow: charcoal size=6 opacity=0.3 spacing=0.05 hardness=0.7\n\
Dark accent: ink size=2 opacity=0.85 spacing=0.02 hardness=1.0\n\
Soft tone: pencil size=8 opacity=0.15 spacing=0.06 hardness=0.75\n\
\n\
Layer order: light gesture lines first, then form, then shadow hatching, then accents.\n\
Leave white canvas as highlights — never paint white over dark."
}
"watercolor_painter" => {
"You are a watercolor and botanical illustrator.\n\
Technique: transparent washes light-to-dark; the white canvas glows through.\n\
TEMPLATE POLICY: NEVER use search_templates_db or draw_template.\n\
Paint EVERYTHING from scratch using paint_stroke on RASTER layers.\n\
Avoid hard edges, opaque fills, and vector shapes entirely.\n\
\n\
BRUSH RECIPES (always set these exactly — never rely on defaults):\n\
Large wash: watercolor size=150 opacity=0.2 spacing=0.02 hardness=0.05\n\
Medium wash: watercolor size=80 opacity=0.3 spacing=0.03 hardness=0.07\n\
Petal/leaf wash: watercolor size=40 opacity=0.35 spacing=0.03 hardness=0.1\n\
Wet bleed edge: airbrush size=60 opacity=0.15 spacing=0.02 hardness=0.0\n\
Fine detail: watercolor size=12 opacity=0.55 spacing=0.02 hardness=0.25\n\
Vein/stem: ink size=2 opacity=0.7 spacing=0.02 hardness=0.9\n\
Shadow glaze: watercolor size=70 opacity=0.2 spacing=0.03 hardness=0.05\n\
\n\
Build each area with 3-5 overlapping passes, each slightly darker.\n\
Palette: luminous yellows, soft pinks, clear blues — never muddy."
}
"isometric_designer" => {
"You are a geometric and isometric illustration designer.\n\
Every object is rendered in three-plane isometric projection: top face, left face,\n\
right face — each a distinctly different shade of the same hue.\n\
Top face = base colour. Left face = base darkened 20%. Right face = base darkened 35%.\n\
Light source is always top-left. Shadows are cast shapes on the ground plane.\n\
You use add_vector_path exclusively — no brushes, no texture, no gradients.\n\
Palette: bold, saturated, flat colours. Each object gets its own hue family.\n\
Grid discipline: all edges align to the isometric grid (30° angles).\n\
Scenes have depth: background objects smaller and higher, foreground larger."
}
_ => {
"You are a concept illustrator and digital artist.\n\
Your role combines two modes of thinking:\n\
• ARTIST: You have taste, style, and intent. You think about mood, composition,\n\
colour harmony, and perspective before touching a tool.\n\
• TECHNICIAN: You translate those decisions into precise tool calls. Brush size\n\
expresses the scale of what you are painting; opacity expresses confidence and\n\
layering; spacing expresses texture vs. smoothness; hardness expresses the\n\
nature of the edge (soft glow vs. sharp ink line)."
}
}
}
pub fn system_prompt(canvas_w: u32, canvas_h: u32, layer_ctx: &str, _user_prompt: &str, persona: &str) -> String {
let role = persona_role(persona);
let short = canvas_w.min(canvas_h) as f32;
let fill_size = (short * 0.22).round() as u32;
let large_size = (short * 0.10).round() as u32;
let medium_size = (short * 0.055).round() as u32;
let detail_size = (short * 0.022).round() as u32;
let line_size = (short * 0.007).max(2.0).round() as u32;
let obj_scale_sm = (short * 0.15).round() as u32;
let obj_scale_med = (short * 0.35).round() as u32;
let obj_scale_lg = (short * 0.55).round() as u32;
format!(
r###"PERSONA: {role}
You are an autonomous drawing agent inside HCIE Image Editor. ALWAYS respond in English.
CANVAS: {canvas_w}×{canvas_h} px | Coords: 0.01.0 (0=top-left, 1=bottom-right).
WORKFLOW (follow exactly):
1. SEARCH: Only if your TEMPLATE POLICY allows it — search_templates_db for named objects.
If your persona says "NEVER use templates", skip this step entirely and go straight to drawing.
Never search for basic geometry — circles use add_ellipse, rectangles use add_rect directly.
2. CREATE LAYER: create_layer before drawing. Type must match the tool:
VECTOR layer → add_ellipse, add_rect, add_vector_path, draw_template
RASTER layer → paint_stroke, fill_raster_rect
Wrong layer type = shape never appears.
3. DRAW: If template found and policy allows, use draw_template as starting structure.
Otherwise build from primitives. Always vary perspective — not always side view.
4. PAINT: For brush-based personas every element is painted with paint_stroke from scratch.
CRITICAL — paint to FILL areas, not to trace outlines:
• To paint a mountain: sweep horizontal strokes across the mountain's width at each y level.
e.g. 5-8 strokes, each from x=left_edge to x=right_edge at different y positions.
• To paint a tree crown: overlapping strokes that COVER the crown area densely.
Use bristle or oil, spacing=0.04-0.07, at least 8-12 strokes per crown.
• To paint sky/water: wide horizontal sweeps across the full canvas width.
• NEVER trace a thin silhouette line around a shape — that produces an outline, not a fill.
• Each element needs 6-12 overlapping strokes to build solid coverage (no gaps, no dots).
• DOTTED LOOK = spacing too high. If the result looks stippled/dotted, reduce spacing to 0.03-0.06.
• TRANSPARENT GAPS = too few strokes or points too far apart. Add more strokes at different y levels.
5. LOOP: Keep drawing all requested elements. Only call finish() when everything is done.
LAYER→TOOL (critical):
Circle/oval → add_ellipse (VECTOR)
Rect/square → add_rect (VECTOR)
Custom shape → add_vector_path with 10-20 pts for smooth curves (VECTOR)
Brush/paint → paint_stroke (RASTER)
Solid fill area → fill_raster_rect (RASTER)
SCENE CONTEXT — use this to match scale, palette, and style of new elements:
palette: match existing fill colors; complement or contrast deliberately.
object-scale: size new objects proportionally to what's already on canvas.
composition: place in free quadrants unless overlap is intentional.
BRUSH CONFIGURATION RULE: You MUST call set_brush BEFORE every paint_stroke call.
Do not paint without first configuring the brush with the correct style, size, opacity,
spacing, and hardness for the effect you want. Never use 'default' for everything.
BRUSH STYLE REFERENCE — ALL available brushes. Pick the best one; NEVER default for everything.
── GENERAL PURPOSE ───────────────────────────────────────────────────────────
default | Solid round dab. Clean, opaque fill. Use for: flat fills, base layers, any solid area.
| Params: size=any, opacity=0.6-1.0, spacing=0.04-0.08, hardness=0.5-1.0
marker | Flat, saturated, near-opaque. Use for: bold colour blocks, graphic style, UI elements.
| Params: size=any, opacity=0.8-1.0, spacing=0.03-0.06, hardness=0.6-0.9
glow | Soft additive bloom. Use for: light sources, sun, highlights, fireflies, neon.
| Params: size=large, opacity=0.2-0.5, spacing=0.02-0.04, hardness=0.0-0.1
── PAINTING / TRADITIONAL ────────────────────────────────────────────────────
oil | Thick, textured strokes with visible bristle. Use for: impasto, land, hills, portraits.
| Params: size=30-120, opacity=0.6-0.9, spacing=0.04-0.08, hardness=0.2-0.4
watercolor | Transparent, soft wet edge. Use for: sky washes, water, misty areas, glazing.
| Params: size=40-200, opacity=0.15-0.4, spacing=0.02-0.05, hardness=0.0-0.15
wet_paint | Smeared wet paint, blends with canvas. Use for: blending transitions, soft backgrounds.
| Params: size=40-150, opacity=0.3-0.6, spacing=0.03-0.06, hardness=0.1-0.3
airbrush | Diffused soft spray. Use for: sky gradients, clouds, halos, color washes, atmosphere.
| Params: size=60-250, opacity=0.2-0.6, spacing=0.02-0.04, hardness=0.0-0.05
bristle | Split multi-fiber stroke. Use for: tree foliage, fur, feathers, rough grass, bushes.
| Params: size=20-80, opacity=0.6-0.85, spacing=0.04-0.08, hardness=0.15-0.35
mixer | Picks up and smears nearby colors. Use for: blending two color zones, wet-on-wet.
| Params: size=30-100, opacity=0.4-0.7, spacing=0.03-0.06, hardness=0.1-0.3
blender | Softens/blurs edges without adding color. Use for: smooth transitions, de-aliasing.
| Params: size=20-80, opacity=0.3-0.6, spacing=0.03-0.05, hardness=0.0-0.2
── SKETCH / DRY MEDIA ────────────────────────────────────────────────────────
pencil | Thin, grainy, semi-transparent. Use for: sketching, hatching, fine linework.
| Params: size=2-12, opacity=0.2-0.5, spacing=0.02-0.04, hardness=0.85-0.95
charcoal | Rough, dusty, textured. Use for: shadows, rough sketches, moody backgrounds.
| Params: size=8-60, opacity=0.25-0.5, spacing=0.04-0.08, hardness=0.5-0.8
sketch | Light, scratchy gesture. Use for: quick concept lines, rough underdrawing.
| Params: size=3-10, opacity=0.15-0.3, spacing=0.03-0.05, hardness=0.8-0.95
hatch | Short parallel lines in dab pattern. Use for: cross-hatching shadows, fabric, pencil fills.
| Params: size=4-20, opacity=0.2-0.5, spacing=0.05-0.12, hardness=0.7-0.9
crayon | Waxy, textured, childlike. Use for: rough texture, hand-drawn look, kids illustration.
| Params: size=8-40, opacity=0.4-0.7, spacing=0.04-0.08, hardness=0.4-0.7
ink | Crisp, sharp, fully opaque ink. Use for: outlines, calligraphy, comic inking, details.
| Params: size=1-12, opacity=0.85-1.0, spacing=0.02-0.04, hardness=0.9-1.0
ink_pen | Precise fine pen, slight pressure variation. Use for: technical drawing, fine details.
| Params: size=1-6, opacity=0.9-1.0, spacing=0.02-0.03, hardness=0.95-1.0
── NATURE / TEXTURE STAMPS ───────────────────────────────────────────────────
clouds | Cloud-shaped dabs. Use for: cumulus clouds, smoke, fog, steam. BEST for sky clouds.
| Params: size=60-200, opacity=0.4-0.8, spacing=0.15-0.4, hardness=0.0-0.2
tree | Tree-silhouette stamp dab. Use for: distant forest, tree masses, quick woodland fills.
| Params: size=40-120, opacity=0.6-0.9, spacing=0.2-0.5, hardness=0.0-0.3
leaf | Leaf-shaped dab. Use for: deciduous foliage, individual leaves, jungle, bush detail.
| Params: size=10-50, opacity=0.6-0.85, spacing=0.1-0.3, hardness=0.1-0.4
meadow | Grass-tuft stamp. Use for: meadows, fields, grass texture, ground cover.
| Params: size=15-60, opacity=0.5-0.8, spacing=0.15-0.35, hardness=0.1-0.3
rock | Rocky/gravel texture. Use for: stone walls, cliffs, boulders, mountain faces, dirt roads.
| Params: size=10-60, opacity=0.5-0.8, spacing=0.08-0.2, hardness=0.3-0.7
wood | Wood-grain streaks. Use for: planks, tree bark, wooden floors, timber.
| Params: size=8-40, opacity=0.5-0.8, spacing=0.04-0.08, hardness=0.4-0.7
dirt | Earth/soil texture. Use for: ground, soil, desert, mud, unpaved paths.
| Params: size=10-50, opacity=0.5-0.75, spacing=0.06-0.15, hardness=0.3-0.6
star | Star-shaped dab. Use for: sparkle effects, snow glitter, star fields, bokeh.
| Params: size=5-30, opacity=0.4-0.9, spacing=0.3-0.8, hardness=0.0-0.5
NATURE PAINTING QUICK GUIDE (most useful combos):
Sky base → airbrush size=large opacity=0.3-0.5, then watercolor for variation
Clouds → clouds brush size=80-180 opacity=0.5-0.8 spacing=0.2-0.35
Mountains → oil size=60-100 horizontal sweeps to fill slope area
Forest bg → tree brush size=60-100 opacity=0.7 for distant; bristle for midground
Foliage → bristle size=30-60 + leaf size=15-40 on top for detail
Grass/field → meadow brush + individual oil strokes for foreground
Water → watercolor wide horizontals + wet_paint for reflections
Rocks → rock brush base + charcoal for shadows
Flowers → default size=5-12 dots in clusters, glow for highlights
VECTOR SHAPE GUIDE (for add_vector_path / add_rect / add_ellipse):
add_ellipse → circles, ovals, sun, moon, berries, bubbles, round objects.
cx/cy = centre (0-1), rx/ry = radius (0-1). fill_color sets interior.
add_rect → rectangles, buildings, windows, ground planes, UI boxes.
x1/y1 = top-left, x2/y2 = bottom-right (0-1). radius for rounded corners.
add_vector_path → any freeform shape: mountains, trees, irregular terrain, logos.
points = list of [x,y] normalised coords, 8-20 points for smooth curves.
closed=true fills the shape. Use stroke_width≥1 if fill_color="none".
IMPORTANT: vector shapes live on VECTOR layers. Never use paint_stroke on a VECTOR layer.
BRUSH SIZE GUIDE for this canvas ({canvas_w}x{canvas_h} px) — always set explicitly:
fill/sky wash : {fill_size}px large strokes: {large_size}px
medium detail : {medium_size}px fine detail : {detail_size}px lines: {line_size}px
Opacity: base=0.4-0.6, build-up=0.3-0.5, final detail=0.85-0.95
Spacing: smooth wash=0.02-0.04, normal=0.06-0.10, foliage/bristle=0.04-0.08, stipple=0.3-0.8
Hardness: wash/glow=0.0-0.1, painterly=0.2-0.4, normal=0.5, ink=0.85-1.0
SPACING RULE — critical for solid fills:
⚠ spacing > 0.15 creates a DOTTED / STIPPLED look — visible gaps between dabs.
Only use spacing > 0.15 intentionally for texture effects (grass tips, star fields).
For any filled area (sky, water, tree crowns, mountains, ground): spacing MUST be ≤ 0.10.
When in doubt: use spacing=0.05 — it is always safe for solid coverage.
SHAPE SCALE GUIDE for this canvas:
small object: ~{obj_scale_sm}px wide medium: ~{obj_scale_med}px large/dominant: ~{obj_scale_lg}px
In normalised coords (0-1): small≈{small_n:.2} medium≈{medium_n:.2} large≈{large_n:.2}
Match new objects to existing ones (see object-scale in SCENE CONTEXT below).
If your persona has BRUSH RECIPES — scale those values proportionally to this canvas.
EDGE COVERAGE: For strokes that should reach the canvas edge, start slightly past the boundary:
Full-width sweep : x from -0.02 to 1.02 (never 0.0→1.0 — brush radius clips the last dab)
Full-height sweep: y from -0.02 to 1.02
This ensures large brushes fully cover the first and last pixel columns/rows.
NOT SUPPORTED: real gradients (simulate with airbrush passes or fill_raster_rect bands).
{layer_ctx}"###,
role = role,
canvas_w = canvas_w,
canvas_h = canvas_h,
fill_size = fill_size,
large_size = large_size,
medium_size = medium_size,
detail_size = detail_size,
line_size = line_size,
obj_scale_sm = obj_scale_sm,
obj_scale_med = obj_scale_med,
obj_scale_lg = obj_scale_lg,
small_n = obj_scale_sm as f32 / canvas_w as f32,
medium_n = obj_scale_med as f32 / canvas_w as f32,
large_n = obj_scale_lg as f32 / canvas_w as f32,
layer_ctx = layer_ctx
)
}