HeadPaint API
editorAPI.headPaint methods: custom tools, painting callbacks, color, opacity, palette updates, and undo history limitations.
editorAPI.headPaint extends the HeadPaint panel. The object becomes available after the interface is initialized, immediately before bde:started. Wait for that point when an installed plugin starts; when running code manually in an editor that is already ready, the object is available immediately. See the events reference for the startup sequence.
All three methods are synchronous and return undefined. They change shared HeadPaint settings used by the editor and other plugins.
addTool: register a tool
editorAPI.headPaint.addTool(tool);The method adds a button to the HeadPaint tool set and updates the panel. Registering a tool with an existing id replaces its definition and moves it to the end of the custom tool list. Use your own prefix, such as my-plugin-picker, and avoid the names of built-in tools.
tool field |
Purpose |
|---|---|
id |
String identifier. Registration is ignored if the object is missing or id is not a string. Use a nonempty value. |
icon |
A single icon CSS class, such as icon-pipette. Defaults to icon-box. |
nameHide |
Tooltip text. Falls back to name, then id. |
name |
Fallback tool name. |
hoverTitle |
Tooltip heading. Defaults to the tool group heading. |
onSelect({ headPaint, editor }) |
Optional button callback, called after selecting the tool. Receives internal editor objects. |
onPaint(data) |
Optional callback for a hit on the texture while painting. Must perform its action and return its result synchronously. |
Provide functions for the callbacks. An async callback is not awaited: its returned Promise is treated as an ordinary result, and the texture change may happen after the display has already been updated.
Fields passed to onPaint:
| Field | Value |
|---|---|
head |
The head object being acted on. |
canvas, context |
The texture canvas and its CanvasRenderingContext2D. The callback must draw any changes itself. |
x, y |
Original hit coordinates on the texture layout. |
drawX, drawY |
Canvas pixel coordinates adjusted for the selected layer and Y-axis direction. Use these to write a pixel. |
localX |
X coordinate within one half of the texture layout, from 0 to 31. |
faceName |
Face name. |
color |
Current color as a HEX string. |
opacity |
Alpha from 0 to 255. |
outerLayer |
Whether the head’s outer layer is selected. |
blendMode |
Current HeadPaint blending mode. |
replacePixel |
true when blendMode === 'replace'. |
smartGrid |
Whether the smart grid is enabled. |
Receiving blendMode, opacity, or smartGrid does not apply those settings to your canvas operations. The plugin must implement the blending and stroke size it needs. For example, context.putImageData() writes RGBA directly and does not use globalAlpha.
The return value of onPaint controls how the result is processed:
| Result | Behavior |
|---|---|
false |
Skip the subsequent texture update and paint event. Writes already made to the canvas are not reverted. |
An array of objects [{ drawX, drawY }, ...] |
Report which pixels were processed. Coordinates are rounded down; pixels outside the canvas and duplicates within the current stroke are skipped. |
Any other result, including undefined |
Treat the single pixel at the original drawX, drawY as processed. |
If valid new pixels remain, the editor dispatches bde:headPaint:paint for each one and calls head.updatePaintTexture(). Returning coordinates does not draw anything by itself. The head, headPaint, and editor objects provide access to internal functionality whose structure may change between versions.
setColor: color and opacity
editorAPI.headPaint.setColor(color, opacity = null);color accepts only a #RGB or #RRGGBB string. Leading and trailing whitespace is removed, shorthand is expanded, and the resulting value is stored in uppercase. CSS color names, rgb(), THREE.Color, and HEX colors with alpha are not supported.
opacity is a separate value in the 0-255 range. It is converted with Number(), clamped to the range, and rounded to an integer. null preserves the current opacity. An invalid color or nonnumeric alpha cancels the entire call without an error.
editorAPI.headPaint.setColor('#fa0', 128); // #FFAA00, alpha 128editorAPI.headPaint.setColor('#336699'); // Alpha stays unchanged.The method sets the color for subsequent painting. It does not recolor the texture, add the color to the palette, or explicitly refresh the panel controls. There is no separate public color getter or setOpacity method; a tool callback receives the current values in onPaint.
setUpdateColorList: palette updates
editorAPI.headPaint.setUpdateColorList(enabled = true);Pass false to disable automatic palette additions in the built-in tools that honor this flag, or true to enable them again. The method assigns the flag without converting its type, so pass a boolean.
This does not lock the palette: the built-in color picker and certain interface actions can add colors regardless of the flag. A custom onPaint does not populate the palette automatically. The public API has no methods to read, clear, or replace the entire palette.
History, activation, and cleanup
Registration does not enable HeadPaint mode or select the new tool. The user opens HeadPaint and clicks its button. There are no public selectTool, removeTool, beginStroke, or endStroke methods. Registering the same id again updates the definition; it does not unload the plugin.
Do not rely on automatic Undo for a custom brush. The current implementation captures the head’s original state before onPaint, but the custom tool branch does not mark the command as changed. Drawing on the canvas and returning coordinates is not enough to add the completed stroke to history. Built-in tools set that flag separately. A fully functional editing brush requires additional integration with the editor’s internal history and compatibility checks.
HeadPaint dispatches the following CustomEvent events on window:
| Event | When it occurs |
|---|---|
bde:headPaint:started |
The first change in a stroke made through the built-in painting branch. |
bde:headPaint:paint |
A processed change or pixel. A custom tool adds a customTool field containing its id. |
bde:headPaint:ended |
The end of a stroke whose changes were added to history. |
bde:headPaint:fillStarted, bde:headPaint:fillEnded |
The start and end of a regular flood fill. The fill branch used with modifier keys does not dispatch these events. |
Common event.detail fields are tool, color, opacity, outerLayer, and blendMode. Painting and fill events additionally include head, x, y, and faceName; paint may include drawX, drawY, region dimensions, and other tool-specific information. ended has no coordinates or head. The custom tool branch does not dispatch started or ended by itself, so do not treat them as universal lifecycle boundaries for all extensions.
Minimal example
The tool below reads the selected pixel’s RGBA value. It changes the current color without editing the texture, so it does not need a stroke history entry.
(() => { function init() { window.editorAPI.headPaint.addTool({ id: 'sample-plugin-pixel-picker', icon: 'icon-pipette', name: 'Pixel color', onPaint({ context, drawX, drawY }) { const [r, g, b, a] = context.getImageData(drawX, drawY, 1, 1).data; const color = '#' + [r, g, b] .map(value => value.toString(16).padStart(2, '0')) .join(''); window.editorAPI.headPaint.setColor(color, a); return false; }, }); }
if (window.editorAPI?.headPaint) init(); else window.addEventListener('bde:started', init, { once: true });})();For file preparation, installation, and testing, see Add a HeadPaint tool.