Architecture and patterns for developing image editor tools and UI in the SwarmUI image editor.
Guide to the image editor tool system in src/wwwroot/js/genpage/helpers/image_editor.js.
ImageEditorTool - Base class. Creates this.div (toolbar button) and this.configDiv (bottom bar config area) via makeDivs(). Has lifecycle methods: setActive(), setInactive(), draw(), mouse handlers, onLayerChanged().ImageEditorTempTool - Extends base. Overrides makeDivs() with a no-op, so this.div is undefined. Used for hidden sub-tools (e.g., the eyedropper color picker tool). Any code accessing on a tool must null-check for this case..divImageEditorToolWithColor - Extends base. Adds color control support: getColorControlsHTML(), wireColorControls(), setColor(col), and color-aware onLayerChanged() with dual mask/image color memory. Tools that need a color picker (Brush, Bucket, Shape) extend this.ImageEditorToolSam2Base - Extends base. Shared SAM2 warmup/clear-mask/request-tracking logic. Subclasses override addWarmupGenData(genData, cx, cy) and optionally onClearMask(). SAM2Points and SAM2BBox extend this.ImageEditorToolBrush/ImageEditorToolBucket/ImageEditorToolShape extend ImageEditorToolWithColor; ImageEditorToolSam2Points/ImageEditorToolSam2BBox extend ImageEditorToolSam2Base; others extend ImageEditorTool.Tools are registered in the ImageEditor constructor via this.addTool(new ToolClass(...)). Order matters for toolbar display. The tool ID string (e.g., 'brush', 'paintbucket') is used with this.editor.activateTool(id).
isMask property.onLayerChanged(oldLayer, newLayer) is called on every tool when the active layer changes (via setActiveLayer()). The old layer is passed directly so tools can compare previous vs new state.onLayerChanged handles isMaskOnly tools (hides them when not on a mask).onLayerChanged to adapt (e.g., compress color to grayscale for masks, swap stored color based on oldLayer.isMask vs newLayer.isMask).Tools that use color extend ImageEditorToolWithColor, which provides:
getColorControlsHTML(): Returns HTML for a .image-editor-tool-block div with hex text input (.id-col1), color swatch (.id-col2), and eyedrop button (.id-col3). Uses this.color for the default value.wireColorControls(): Call after setting configDiv.innerHTML to wire up the color picker, swatch click, and eyedropper button. Opens the picker via the singleton colorPickerHelper (from color_picker.js).setColor(col): Updates this.color, the text input, and the swatch background.imageColor and maskColor fields store separate colors for image vs mask layers. The inherited onLayerChanged swaps between them (guarded by this.colorText existing, so tools like the eraser that skip wireColorControls() are unaffected).colorPickerHelper.hexToGrayscale(), and the picker opens in grayscale mode.Each tool's config UI is in this.configDiv, a flex row at the bottom of the editor. Common sub-blocks use .image-editor-tool-block with align-items: center. Sliders use enableSliderForBox() from site.js.
ImageEditorTool (or ImageEditorTempTool if it should be hidden).ImageEditor constructor with this.addTool(new YourTool(this)).src/Pages/Text2Image.cshtml in the @section Scripts block before image_editor.js.createDiv() from util.js for DOM creation.--popup-back, --light-border, etc.) for styling..div when working with tools that might be TempTools.ImageEditorToolWithColor, pass the default color to super(), call this.getColorControlsHTML() when building config HTML, and call this.wireColorControls() after setting configDiv.innerHTML. The base class handles setColor(), onLayerChanged() color swapping, and grayscale enforcement automatically.