diff --git a/src/extensions/core/maskeditor.ts b/src/extensions/core/maskeditor.ts
index 473bad27c..51ae4eb4f 100644
--- a/src/extensions/core/maskeditor.ts
+++ b/src/extensions/core/maskeditor.ts
@@ -275,11 +275,7 @@ var styles = `
justify-content: center;
align-items: center;
position: relative;
- transition: background-color border 0.2s;
- }
- .maskEditor_toolPanelContainer:hover {
- background-color: var(--p-overlaybadge-outline-color);
- border: none;
+ transition: background-color 0.2s;
}
.maskEditor_toolPanelContainerSelected svg {
fill: var(--p-button-text-primary-color) !important;
@@ -292,6 +288,15 @@ var styles = `
aspect-ratio: 1/1;
fill: var(--p-button-text-secondary-color);
}
+
+ .maskEditor_toolPanelContainerDark:hover {
+ background-color: var(--p-surface-800);
+ }
+
+ .maskEditor_toolPanelContainerLight:hover {
+ background-color: var(--p-surface-300);
+ }
+
.maskEditor_toolPanelIndicator {
display: none;
height: 100%;
@@ -379,16 +384,56 @@ var styles = `
}
#maskEditor_topBarShortcutsContainer {
display: flex;
+ gap: 10px;
+ margin-left: 5px;
}
- .maskEditor_topPanelIconButton {
- width: 53.3px;
+ .maskEditor_topPanelIconButton_dark {
+ width: 50px;
height: 30px;
pointer-events: auto;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.1s;
+ background: var(--p-surface-800);
+ border: 1px solid var(--p-form-field-border-color);
+ border-radius: 10px;
+ }
+
+ .maskEditor_topPanelIconButton_dark:hover {
+ background-color: var(--p-surface-900);
+ }
+
+ .maskEditor_topPanelIconButton_dark svg {
+ width: 25px;
+ height: 25px;
+ pointer-events: none;
+ fill: var(--input-text);
+ }
+
+ .maskEditor_topPanelIconButton_light {
+ width: 50px;
+ height: 30px;
+ pointer-events: auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transition: background-color 0.1s;
+ background: var(--comfy-menu-bg);
+ border: 1px solid var(--p-form-field-border-color);
+ border-radius: 10px;
+ }
+
+ .maskEditor_topPanelIconButton_light:hover {
+ background-color: var(--p-surface-300);
+ }
+
+ .maskEditor_topPanelIconButton_light svg {
+ width: 25px;
+ height: 25px;
+ pointer-events: none;
+ fill: var(--input-text);
}
.maskEditor_topPanelButton_dark {
@@ -657,6 +702,24 @@ var styles = `
margin-left: 15px;
}
+ .maskEditor_toolPanelZoomIndicator {
+ width: var(--sidebar-width);
+ height: var(--sidebar-width);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 5px;
+ color: var(--p-button-text-secondary-color);
+ position: absolute;
+ bottom: 0;
+ transition: background-color 0.2s;
+ }
+
+ #maskEditor_toolPanelDimensionsText {
+ font-size: 12px;
+ }
+
`
var styleSheet = document.createElement('style')
@@ -1229,6 +1292,8 @@ class PaintBucketTool {
this.messageBroker.subscribe('paintBucketFill', (point: Point) =>
this.floodFill(point)
)
+
+ this.messageBroker.subscribe('invert', () => this.invertMask())
}
private addPullTopics() {
@@ -1374,6 +1439,48 @@ class PaintBucketTool {
getTolerance(): number {
return this.tolerance
}
+
+ //invert mask
+
+ private invertMask() {
+ const imageData = this.ctx.getImageData(
+ 0,
+ 0,
+ this.canvas.width,
+ this.canvas.height
+ )
+ const data = imageData.data
+
+ // Find first non-transparent pixel to get mask color
+ let maskR = 0,
+ maskG = 0,
+ maskB = 0
+ for (let i = 0; i < data.length; i += 4) {
+ if (data[i + 3] > 0) {
+ maskR = data[i]
+ maskG = data[i + 1]
+ maskB = data[i + 2]
+ break
+ }
+ }
+
+ // Process each pixel
+ for (let i = 0; i < data.length; i += 4) {
+ const alpha = data[i + 3]
+ // Invert alpha channel (0 becomes 255, 255 becomes 0)
+ data[i + 3] = 255 - alpha
+
+ // If this was originally transparent (now opaque), fill with mask color
+ if (alpha === 0) {
+ data[i] = maskR
+ data[i + 1] = maskG
+ data[i + 2] = maskB
+ }
+ }
+
+ this.ctx.putImageData(imageData, 0, 0)
+ this.messageBroker.publish('saveState')
+ }
}
class ColorSelectTool {
@@ -1809,10 +1916,15 @@ class BrushTool {
smoothingLastDrawTime!: Date
maskCtx: CanvasRenderingContext2D | null = null
+ brushStrokeCanvas: HTMLCanvasElement | null = null
+ brushStrokeCtx: CanvasRenderingContext2D | null = null
+
//brush adjustment
isBrushAdjusting: boolean = false
brushPreviewGradient: HTMLElement | null = null
initialPoint: Point | null = null
+ useDominantAxis: boolean = false
+ brushAdjustmentSpeed: number = 1.0
maskEditor: MaskEditorDialog
messageBroker: MessageBroker
@@ -1823,6 +1935,13 @@ class BrushTool {
this.createListeners()
this.addPullTopics()
+ this.useDominantAxis = app.extensionManager.setting.get(
+ 'Comfy.MaskEditor.UseDominantAxis'
+ )
+ this.brushAdjustmentSpeed = app.extensionManager.setting.get(
+ 'Comfy.MaskEditor.BrushAdjustmentSpeed'
+ )
+
this.brushSettings = {
size: 10,
opacity: 100,
@@ -1860,7 +1979,7 @@ class BrushTool {
)
//drawing
this.messageBroker.subscribe('drawStart', (event: PointerEvent) =>
- this.start_drawing(event)
+ this.startDrawing(event)
)
this.messageBroker.subscribe('draw', (event: PointerEvent) =>
this.handleDrawing(event)
@@ -1897,12 +2016,27 @@ class BrushTool {
)
}
- private async start_drawing(event: PointerEvent) {
+ private async createBrushStrokeCanvas() {
+ if (this.brushStrokeCanvas !== null) {
+ return
+ }
+
+ const maskCanvas = await this.messageBroker.pull('maskCanvas')
+ const canvas = document.createElement('canvas')
+ canvas.width = maskCanvas.width
+ canvas.height = maskCanvas.height
+
+ this.brushStrokeCanvas = canvas
+ this.brushStrokeCtx = canvas.getContext('2d')!
+ }
+
+ private async startDrawing(event: PointerEvent) {
this.isDrawing = true
let compositionOp: CompositionOperation
let currentTool = await this.messageBroker.pull('currentTool')
let coords = { x: event.offsetX, y: event.offsetY }
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
+ await this.createBrushStrokeCanvas()
//set drawing mode
if (currentTool === Tools.Eraser || event.buttons == 2) {
@@ -1931,15 +2065,6 @@ class BrushTool {
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
let currentTool = await this.messageBroker.pull('currentTool')
- /* move to draw
- if (event instanceof PointerEvent && event.pointerType == 'pen') {
- brush_size *= event.pressure
- this.last_pressure = event.pressure
- } else {
- brush_size = this.brush_size //this is the problem with pen pressure
- }
- */
-
if (diff > 20 && !this.isDrawing)
requestAnimationFrame(() => {
this.init_shape(CompositionOperation.SourceOver)
@@ -2051,29 +2176,62 @@ class BrushTool {
private async handleBrushAdjustment(event: PointerEvent) {
const coords = { x: event.offsetX, y: event.offsetY }
+ const brushDeadZone = 5
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
const delta_x = coords_canvas.x - this.initialPoint!.x
const delta_y = coords_canvas.y - this.initialPoint!.y
- // Adjust brush size (horizontal movement)
+ const effectiveDeltaX = Math.abs(delta_x) < brushDeadZone ? 0 : delta_x
+ const effectiveDeltaY = Math.abs(delta_y) < brushDeadZone ? 0 : delta_y
+
+ // New dominant axis logic
+ let finalDeltaX = effectiveDeltaX
+ let finalDeltaY = effectiveDeltaY
+
+ console.log(this.useDominantAxis)
+
+ if (this.useDominantAxis) {
+ // New setting flag
+ const ratio = Math.abs(effectiveDeltaX) / Math.abs(effectiveDeltaY)
+ const threshold = 2.0 // Configurable threshold
+
+ if (ratio > threshold) {
+ finalDeltaY = 0 // X is dominant
+ } else if (ratio < 1 / threshold) {
+ finalDeltaX = 0 // Y is dominant
+ }
+ }
+
+ const cappedDeltaX = Math.max(-100, Math.min(100, finalDeltaX))
+ const cappedDeltaY = Math.max(-100, Math.min(100, finalDeltaY))
+
+ // Rest of the function remains the same
+ const sizeDelta = cappedDeltaX / 40
+ const hardnessDelta = cappedDeltaY / 800
+
const newSize = Math.max(
1,
- Math.min(100, this.brushSettings.size! + delta_x / 10)
+ Math.min(
+ 100,
+ this.brushSettings.size! +
+ (cappedDeltaX / 35) * this.brushAdjustmentSpeed
+ )
)
- // Adjust brush hardness (vertical movement)
const newHardness = Math.max(
0,
- Math.min(1, this.brushSettings!.hardness - delta_y / 200)
+ Math.min(
+ 1,
+ this.brushSettings!.hardness -
+ (cappedDeltaY / 4000) * this.brushAdjustmentSpeed
+ )
)
this.brushSettings.size = newSize
this.brushSettings.hardness = newHardness
this.messageBroker.publish('updateBrushPreview')
-
- return
}
//helper functions
@@ -2263,8 +2421,8 @@ class BrushTool {
}
private setBrushSmoothingPrecision(precision: number) {
- console.log('precision', precision)
- // this.brushSettings.smoothingPrecision = precision
+ //console.log('precision', precision)
+ this.smoothingPrecision = precision
}
}
@@ -2300,6 +2458,9 @@ class UIManager {
private mask_opacity: number = 0.7
private maskBlendMode: MaskBlendMode = MaskBlendMode.Black
+ private zoomTextHTML!: HTMLSpanElement
+ private dimensionsTextHTML!: HTMLSpanElement
+
constructor(rootElement: HTMLElement, maskEditor: MaskEditorDialog) {
this.rootElement = rootElement
this.maskEditor = maskEditor
@@ -2332,6 +2493,10 @@ class UIManager {
)
this.messageBroker.subscribe('updateCursor', () => this.updateCursor())
+
+ this.messageBroker.subscribe('setZoomText', (text: string) =>
+ this.setZoomText(text)
+ )
}
addPullTopics() {
@@ -2975,11 +3140,17 @@ class UIManager {
return separator
}
+ //----------------
+
private async createTopBar() {
const buttonAccentColor = this.darkMode
? 'maskEditor_topPanelButton_dark'
: 'maskEditor_topPanelButton_light'
+ const iconButtonAccentColor = this.darkMode
+ ? 'maskEditor_topPanelIconButton_dark'
+ : 'maskEditor_topPanelIconButton_light'
+
var top_bar = document.createElement('div')
top_bar.id = 'maskEditor_topBar'
@@ -2997,9 +3168,9 @@ class UIManager {
var top_bar_undo_button = document.createElement('div')
top_bar_undo_button.id = 'maskEditor_topBarUndoButton'
- top_bar_undo_button.classList.add('maskEditor_topPanelIconButton')
+ top_bar_undo_button.classList.add(iconButtonAccentColor)
top_bar_undo_button.innerHTML =
- ''
+ ''
top_bar_undo_button.addEventListener('click', () => {
this.messageBroker.publish('undo')
@@ -3007,19 +3178,21 @@ class UIManager {
var top_bar_redo_button = document.createElement('div')
top_bar_redo_button.id = 'maskEditor_topBarRedoButton'
- top_bar_redo_button.classList.add('maskEditor_topPanelIconButton')
+ top_bar_redo_button.classList.add(iconButtonAccentColor)
top_bar_redo_button.innerHTML =
- ''
+ ''
top_bar_redo_button.addEventListener('click', () => {
this.messageBroker.publish('redo')
})
- top_bar_shortcuts_container.appendChild(top_bar_undo_button)
- top_bar_shortcuts_container.appendChild(top_bar_redo_button)
-
- var top_bar_button_container = document.createElement('div')
- top_bar_button_container.id = 'maskEditor_topBarButtonContainer'
+ var top_bar_invert_button = document.createElement('button')
+ top_bar_invert_button.id = 'maskEditor_topBarInvertButton'
+ top_bar_invert_button.classList.add(buttonAccentColor)
+ top_bar_invert_button.innerText = 'Invert'
+ top_bar_invert_button.addEventListener('click', () => {
+ this.messageBroker.publish('invert')
+ })
var top_bar_clear_button = document.createElement('button')
top_bar_clear_button.id = 'maskEditor_topBarClearButton'
@@ -3055,23 +3228,26 @@ class UIManager {
this.maskEditor.close()
})
- top_bar_button_container.appendChild(top_bar_clear_button)
- top_bar_button_container.appendChild(top_bar_save_button)
- top_bar_button_container.appendChild(top_bar_cancel_button)
+ top_bar_shortcuts_container.appendChild(top_bar_undo_button)
+ top_bar_shortcuts_container.appendChild(top_bar_redo_button)
+ top_bar_shortcuts_container.appendChild(top_bar_invert_button)
+ top_bar_shortcuts_container.appendChild(top_bar_clear_button)
+ top_bar_shortcuts_container.appendChild(top_bar_save_button)
+ top_bar_shortcuts_container.appendChild(top_bar_cancel_button)
top_bar.appendChild(top_bar_title_container)
top_bar.appendChild(top_bar_shortcuts_container)
- top_bar.appendChild(top_bar_button_container)
return top_bar
}
- //----------------
-
private createToolPanel() {
- var pen_tool_panel = document.createElement('div')
- pen_tool_panel.id = 'maskEditor_toolPanel'
- this.toolPanel = pen_tool_panel
+ var tool_panel = document.createElement('div')
+ tool_panel.id = 'maskEditor_toolPanel'
+ this.toolPanel = tool_panel
+ var toolPanelHoverAccent = this.darkMode
+ ? 'maskEditor_toolPanelContainerDark'
+ : 'maskEditor_toolPanelContainerLight'
var toolElements: HTMLElement[] = []
@@ -3082,6 +3258,7 @@ class UIManager {
toolPanel_brushToolContainer.classList.add(
'maskEditor_toolPanelContainerSelected'
)
+ toolPanel_brushToolContainer.classList.add(toolPanelHoverAccent)
toolPanel_brushToolContainer.innerHTML = `