656 lines
38 KiB
Rust
656 lines
38 KiB
Rust
//! 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.0–1.0.",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"cx": { "type": "number", "description": "Center X (0–1)" },
|
||
"cy": { "type": "number", "description": "Center Y (0–1)" },
|
||
"rx": { "type": "number", "description": "X radius (0–1), e.g. 0.15 for medium circle" },
|
||
"ry": { "type": "number", "description": "Y radius (0–1)" },
|
||
"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.0–5.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.0–1.0.",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"x1": { "type": "number", "description": "Left edge (0–1)" },
|
||
"y1": { "type": "number", "description": "Top edge (0–1)" },
|
||
"x2": { "type": "number", "description": "Right edge (0–1)" },
|
||
"y2": { "type": "number", "description": "Bottom edge (0–1)" },
|
||
"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 0–1.",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"points": {
|
||
"type": "array",
|
||
"items": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 },
|
||
"description": "[[x,y], ...] control points (0–1 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 (0–1)" },
|
||
"y1": { "type": "number", "description": "Top (0–1)" },
|
||
"x2": { "type": "number", "description": "Right (0–1)" },
|
||
"y2": { "type": "number", "description": "Bottom (0–1)" },
|
||
"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 (0–1). 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 0–1 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 (1–200)" },
|
||
"opacity": { "type": "number", "description": "0.0 = transparent, 1.0 = fully opaque" },
|
||
"spacing": { "type": "number", "description": "Dab spacing (0.01–2.0). Default 0.1. Lower is smoother." },
|
||
"hardness": { "type": "number", "description": "Brush edge hardness (0.0–1.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.01–2.0" },
|
||
"hardness": { "type": "number", "description": "0.0–1.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.0–1.0" },
|
||
"cy": { "type": "number", "description": "Center Y 0.0–1.0" },
|
||
"scale": { "type": "number", "description": "Size as fraction of canvas short side (0.05–0.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 3–5 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.0–1.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
|
||
)
|
||
}
|