Skip to content

Add a HeadPaint tool

Add a custom painting tool using HeadPaint's supported extension interface.

Let’s add a single-pixel brush to HeadPaint. It receives pixel coordinates from the editor, paints with the current color, and reports which part of the texture changed. The example demonstrates extending a tool without adding a custom layer or history system.

Download the complete headpaint-tool.js example. After installation, open HeadPaint and select the “Sample pixel” tool with the pencil icon.

Define the tool’s task

The brush paints one pixel under the pointer. The base layer is always opaque; the outer layer uses HeadPaint’s current alpha. In replace mode, the outer-layer pixel is cleared before the new color is applied. In other modes, the example uses the standard Canvas source-over compositing operation.

The built-in brush’s size and shape, smart grid, and special blend modes are not automatically applied to a custom handler. The example deliberately performs one specific action. If you need those settings, implement their behavior separately.

Check the available API

The public extension point is window.editorAPI.headPaint.addTool(tool). The editor accepts an object with a unique id, label, icon, and synchronous onPaint handler. There is no registerBrush() method, and onPaint is not a method of the API itself.

const paint = window.editorAPI?.headPaint;
if (typeof paint?.addTool !== 'function') {
throw new Error('The HeadPaint API is unavailable.');
}

The method adds a tool to HeadPaint’s set but does not enable painting mode or select the tool for the user. There is currently no public removeTool() method. Registering the same id again replaces its description in the list; a different id creates a separate tool. addTool() does not return a removal handle.

All fields and restrictions are listed in the HeadPaint API reference.

Register the tool and handler

A shortened example that paints an opaque pixel:

window.editorAPI.headPaint.addTool({
id: 'my-plugin.pixel',
name: 'My pixel',
icon: 'icon-pencil',
onPaint({context, drawX, drawY, color}) {
context.save();
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
context.fillStyle = color;
context.fillRect(drawX, drawY, 1, 1);
context.restore();
return [{drawX, drawY}];
},
});

Use drawX and drawY for Canvas coordinates: the editor has already accounted for the texture’s vertical direction and the selected layer. Do not replace them directly with the surface hit coordinates x and y. Paint only within canvas.width and canvas.height.

The handler changes pixels through context. The returned array of coordinates does not paint for you; it tells the editor which locations changed. Return false when nothing changed. For any other return value, the editor treats the current pixel as changed. Coordinates already processed during the gesture are remembered, so this handler is not designed to build up color at the same position with every mouse movement.

onPaint is called synchronously. Do not make it async or load images during a stroke; prepare data in advance. Once registered, the tool is selected with its normal HeadPaint button. The optional onSelect receives internal editor and headPaint objects, but this example does not use them.

The complete file also preserves Canvas state with save()/restore(), handles outer layer alpha, and compares the pixel before and after painting to return false when there is no change.

Color, alpha, and the palette

The handler receives color in #RRGGBB format and opacity in the range 0-255. The public method lets you change the current color and, optionally, its alpha:

window.editorAPI.headPaint.setColor('#f90', 128);

Short HEX values are expanded and the color is stored in uppercase. Numeric alpha is rounded and clamped to 0-255. Without the second argument, the current alpha is preserved. An invalid color or nonnumeric alpha is ignored.

setUpdateColorList(false) changes the palette-update flag where built-in tools check it; true restores that behavior. The method does not clear the palette or add the supplied color to it. The custom onPaint path does not automatically populate the built-in palette. The example leaves the user’s flag unchanged.

Layers and texture preparation are explained in painting in HeadPaint.

Check layers, Undo, and cleanup

  1. Create a separate test head. Draw a short stroke on the base layer: pixels should receive the selected color at full opacity.
  2. Switch to the outer layer and reduce alpha. Check normal compositing and pixel replacement; painting near an edge must not affect the adjacent layer.
  3. Select the built-in brush, then return to the sample tool. Check that each button selects its own action.
  4. Reinstall the file with the same namespace and id, then reload the editor. There should be one button for the example.
  5. Delete the plugin and reload the editor to remove its registration.

The current custom tool path sends bde:headPaint:paint notifications and updates the texture, but does not set the history change marker used by the built-in brush. A paint event does not imply a started/ended pair or Undo support. The example does not work around this by accessing the internal history command; such access would introduce separate compatibility risks.

The contract was checked against publicFunctions.js, gui/headPaint.js, and commands/headPaint.js. Before releasing your own tool, test layers and history in the editor version you use. Tool registration alone does not verify those behaviors.