feat: add Painter Node (#8521)

## Summary
Add PainterNode widget for freehand mask drawing directly on the canvas,
with brush/eraser tools, opacity, hardness, and background color
controls.

need BE changes https://github.com/Comfy-Org/ComfyUI/pull/12294

## Screenshots (if applicable)


https://github.com/user-attachments/assets/7222063a-0e40-40bb-b72e-b42c8984beb9



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8521-feat-add-Painter-Node-2fa6d73d36508124ab2ede449a0cc67a)
by [Unito](https://www.unito.io)
This commit is contained in:
Terry Jia
2026-02-26 00:08:49 -05:00
committed by GitHub
parent 2cb4c5eff3
commit 5cfd1aa77e
11 changed files with 1243 additions and 1 deletions

View File

@@ -137,6 +137,7 @@ export type IWidget =
| IImageCropWidget
| IBoundingBoxWidget
| ICurveWidget
| IPainterWidget
export interface IBooleanWidget extends IBaseWidget<boolean, 'toggle'> {
type: 'toggle'
@@ -336,6 +337,11 @@ export interface ICurveWidget extends IBaseWidget<CurvePoint[], 'curve'> {
value: CurvePoint[]
}
export interface IPainterWidget extends IBaseWidget<string, 'painter'> {
type: 'painter'
value: string
}
/**
* Valid widget types. TS cannot provide easily extensible type safety for this at present.
* Override linkedWidgets[]

View File

@@ -0,0 +1,22 @@
import type { IPainterWidget } from '../types/widgets'
import { BaseWidget } from './BaseWidget'
import type { DrawWidgetOptions, WidgetEventOptions } from './BaseWidget'
/**
* Widget for the Painter node canvas drawing tool.
* This is a widget that only has a Vue widgets implementation.
*/
export class PainterWidget
extends BaseWidget<IPainterWidget>
implements IPainterWidget
{
override type = 'painter' as const
drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void {
this.drawVueOnlyWarning(ctx, options, 'Painter')
}
onClick(_options: WidgetEventOptions): void {
// This is a widget that only has a Vue widgets implementation
}
}

View File

@@ -21,6 +21,7 @@ import { FileUploadWidget } from './FileUploadWidget'
import { GalleriaWidget } from './GalleriaWidget'
import { GradientSliderWidget } from './GradientSliderWidget'
import { ImageCompareWidget } from './ImageCompareWidget'
import { PainterWidget } from './PainterWidget'
import { ImageCropWidget } from './ImageCropWidget'
import { KnobWidget } from './KnobWidget'
import { LegacyWidget } from './LegacyWidget'
@@ -58,6 +59,7 @@ export type WidgetTypeMap = {
imagecrop: ImageCropWidget
boundingbox: BoundingBoxWidget
curve: CurveWidget
painter: PainterWidget
[key: string]: BaseWidget
}
@@ -136,6 +138,8 @@ export function toConcreteWidget<TWidget extends IWidget | IBaseWidget>(
return toClass(BoundingBoxWidget, narrowedWidget, node)
case 'curve':
return toClass(CurveWidget, narrowedWidget, node)
case 'painter':
return toClass(PainterWidget, narrowedWidget, node)
default: {
if (wrapLegacyWidgets) return toClass(LegacyWidget, widget, node)
}