How Brushes Are Implemented and Managed in ArmorPaint

TLDR: ArmorPaint implements brushes as JSON-serialized asset definitions that are loaded into a runtime Brush class, coordinated by a singleton BrushManager that synchronizes settings with GPU uniforms, while specialized UI panels and painting shaders consume this data to render strokes in real time.

In the ArmorPaint repository, the brush system forms the critical bridge between user input and GPU-accelerated texture painting. Understanding how brushes are implemented and managed in ArmorPaint requires examining the relationship between declarative asset definitions, JavaScript runtime classes, and shader pipeline integration.

Brush Asset Definitions in JSON

ArmorPaint stores brush presets as JSON asset files within the paint/assets/brushes/ directory. Each definition describes the tip shape, radius, hardness, strength, smoothing, tilt behavior, and texture mask references. At application startup, the engine deserializes these files to instantiate runtime brush objects.

Example structure from paint/assets/brushes/default_brush.json:

{
  "name": "Soft Round",
  "radius": 30,
  "hardness": 0.2,
  "strength": 0.8,
  "tilt": true,
  "texture": "brush_tip_soft.png"
}

The JSON schema supports custom presets that define mathematical properties consumed by both the JavaScript logic and the GPU shaders.

The Brush Class and Runtime State

The Brush class defined in paint/sources/brush/brush.js encapsulates the active brush state and behavior. It holds current parameters in memory, provides methods for sampling the brush tip texture, and exports data structures required by the painting pipelines for stroke calculation, masking, and normal manipulation.

import { Brush } from "./brush/brush.js";

// Instantiate from loaded JSON data
const eraserBrush = new Brush({
  name: "Hard Eraser",
  radius: 50,
  hardness: 1.0,
  strength: 1.0,
  tilt: false,
  texture: "brush_tip_hard.png"
});

This class acts as the data model, separating the asset definition from the runtime execution context.

BrushManager Singleton Coordination

The BrushManager singleton in paint/sources/brush/brush_manager.js serves as the central authority for brush lifecycle and state management. It maintains the registry of available presets, tracks the active Brush instance, reacts to UI parameter changes, and handles GPU synchronization by updating uniform buffers.

When users select a different preset or modify settings, the manager swaps the active brush instance and pushes updates to the graphics pipeline:

import { BrushManager } from "./brush/brush_manager.js";

// Register preset for UI dropdown
BrushManager.registerPreset(eraserBrush);

// UI callback when user selects a brush
function onBrushSelect(presetName) {
  const brush = BrushManager.getPreset(presetName);
  BrushManager.setActiveBrush(brush);
  // Automatically updates u_brushData uniform buffer
}

The manager exposes events that rendering and shader systems subscribe to, ensuring zero-latency updates when brush properties change mid-stroke.

UI Integration via BrushPanel

The interface layer accesses brush data through the BrushPanel class located in paint/sources/ui/brush_panel.js. This panel reads current values from the BrushManager, renders interactive sliders for radius and strength, and writes changes back to the active brush instance. When parameters update, the panel triggers a refresh of the brush preview texture without requiring a full asset reload.

GPU Pipeline and Shader Consumption

The painting shaders consume brush data through two critical uniforms defined in paint/shaders/brush_shader.kong. The sampler u_brushTex provides the brush tip alpha mask, while u_brushData supplies a vec4 containing the radius, hardness, and strength calculations for the current stroke.

uniform sampler2D u_brushTex;
uniform vec4 u_brushData; // xy = radius, zw = hardness/strength

vec4 sampleBrush(in vec2 uv) {
  float dist = length(uv) * u_brushData.x;
  float mask = texture(u_brushTex, uv).r;
  float falloff = smoothstep(1.0, u_brushData.y, dist);
  return vec4(mask * falloff * u_brushData.z);
}

During each paint operation, the shader samples the brush tip texture, applies the falloff calculation based on the radius and hardness parameters, and multiplies by the strength value to determine the final contribution to the target layer.

Summary

  • ArmorPaint serializes brush definitions as JSON assets in paint/assets/brushes/ that describe mathematical and textural properties
  • The Brush class in paint/sources/brush/brush.js represents the runtime state and provides texture sampling methods
  • BrushManager in paint/sources/brush/brush_manager.js coordinates the active brush, maintains the preset registry, and synchronizes GPU uniform buffers
  • UI interaction flows through BrushPanel in paint/sources/ui/brush_panel.js to update brush parameters in real time
  • The shader pipeline in paint/shaders/brush_shader.kong consumes u_brushTex and u_brushData uniforms to calculate stroke contributions per fragment

Frequently Asked Questions

Where are brush presets stored in the ArmorPaint repository?

Brush presets are stored as individual JSON files in the paint/assets/brushes/ directory. Each file contains the name, radius, hardness, strength, tilt support flag, and texture path that the Brush class parses during initialization.

How does ArmorPaint update brush settings when I adjust the UI sliders?

When you modify sliders in the brush panel, the BrushPanel class writes new values to the BrushManager singleton, which immediately updates the GPU uniform buffer u_brushData and refreshes the bound texture reference. This occurs without application restart, ensuring the next stroke reflects the new settings instantly.

Can I create custom brushes by editing JSON files directly?

Yes. You can create custom brushes by adding new JSON files to the brushes asset folder following the established schema. The Brush constructor automatically parses these definitions at runtime, and BrushManager.registerPreset() makes them selectable in the UI dropdown without requiring code changes.

Which shader uniform controls the brush tip shape during painting?

The u_brushTex sampler2D uniform controls the brush tip alpha mask, while u_brushData supplies the mathematical parameters (radius, hardness, strength) used to calculate falloff and intensity in the fragment shader located at paint/shaders/brush_shader.kong.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →