mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 09:19:43 +00:00
improved mouse brush adjustment, added zoom level indicator with reset, added invert button, bug fixes (#1664)
This commit is contained in:
@@ -275,11 +275,7 @@ var styles = `
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: background-color border 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
|
||||||
.maskEditor_toolPanelContainer:hover {
|
|
||||||
background-color: var(--p-overlaybadge-outline-color);
|
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
.maskEditor_toolPanelContainerSelected svg {
|
.maskEditor_toolPanelContainerSelected svg {
|
||||||
fill: var(--p-button-text-primary-color) !important;
|
fill: var(--p-button-text-primary-color) !important;
|
||||||
@@ -292,6 +288,15 @@ var styles = `
|
|||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
fill: var(--p-button-text-secondary-color);
|
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 {
|
.maskEditor_toolPanelIndicator {
|
||||||
display: none;
|
display: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -379,16 +384,56 @@ var styles = `
|
|||||||
}
|
}
|
||||||
#maskEditor_topBarShortcutsContainer {
|
#maskEditor_topBarShortcutsContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskEditor_topPanelIconButton {
|
.maskEditor_topPanelIconButton_dark {
|
||||||
width: 53.3px;
|
width: 50px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: background-color 0.1s;
|
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 {
|
.maskEditor_topPanelButton_dark {
|
||||||
@@ -657,6 +702,24 @@ var styles = `
|
|||||||
margin-left: 15px;
|
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')
|
var styleSheet = document.createElement('style')
|
||||||
@@ -1229,6 +1292,8 @@ class PaintBucketTool {
|
|||||||
this.messageBroker.subscribe('paintBucketFill', (point: Point) =>
|
this.messageBroker.subscribe('paintBucketFill', (point: Point) =>
|
||||||
this.floodFill(point)
|
this.floodFill(point)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.messageBroker.subscribe('invert', () => this.invertMask())
|
||||||
}
|
}
|
||||||
|
|
||||||
private addPullTopics() {
|
private addPullTopics() {
|
||||||
@@ -1374,6 +1439,48 @@ class PaintBucketTool {
|
|||||||
getTolerance(): number {
|
getTolerance(): number {
|
||||||
return this.tolerance
|
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 {
|
class ColorSelectTool {
|
||||||
@@ -1809,10 +1916,15 @@ class BrushTool {
|
|||||||
smoothingLastDrawTime!: Date
|
smoothingLastDrawTime!: Date
|
||||||
maskCtx: CanvasRenderingContext2D | null = null
|
maskCtx: CanvasRenderingContext2D | null = null
|
||||||
|
|
||||||
|
brushStrokeCanvas: HTMLCanvasElement | null = null
|
||||||
|
brushStrokeCtx: CanvasRenderingContext2D | null = null
|
||||||
|
|
||||||
//brush adjustment
|
//brush adjustment
|
||||||
isBrushAdjusting: boolean = false
|
isBrushAdjusting: boolean = false
|
||||||
brushPreviewGradient: HTMLElement | null = null
|
brushPreviewGradient: HTMLElement | null = null
|
||||||
initialPoint: Point | null = null
|
initialPoint: Point | null = null
|
||||||
|
useDominantAxis: boolean = false
|
||||||
|
brushAdjustmentSpeed: number = 1.0
|
||||||
|
|
||||||
maskEditor: MaskEditorDialog
|
maskEditor: MaskEditorDialog
|
||||||
messageBroker: MessageBroker
|
messageBroker: MessageBroker
|
||||||
@@ -1823,6 +1935,13 @@ class BrushTool {
|
|||||||
this.createListeners()
|
this.createListeners()
|
||||||
this.addPullTopics()
|
this.addPullTopics()
|
||||||
|
|
||||||
|
this.useDominantAxis = app.extensionManager.setting.get(
|
||||||
|
'Comfy.MaskEditor.UseDominantAxis'
|
||||||
|
)
|
||||||
|
this.brushAdjustmentSpeed = app.extensionManager.setting.get(
|
||||||
|
'Comfy.MaskEditor.BrushAdjustmentSpeed'
|
||||||
|
)
|
||||||
|
|
||||||
this.brushSettings = {
|
this.brushSettings = {
|
||||||
size: 10,
|
size: 10,
|
||||||
opacity: 100,
|
opacity: 100,
|
||||||
@@ -1860,7 +1979,7 @@ class BrushTool {
|
|||||||
)
|
)
|
||||||
//drawing
|
//drawing
|
||||||
this.messageBroker.subscribe('drawStart', (event: PointerEvent) =>
|
this.messageBroker.subscribe('drawStart', (event: PointerEvent) =>
|
||||||
this.start_drawing(event)
|
this.startDrawing(event)
|
||||||
)
|
)
|
||||||
this.messageBroker.subscribe('draw', (event: PointerEvent) =>
|
this.messageBroker.subscribe('draw', (event: PointerEvent) =>
|
||||||
this.handleDrawing(event)
|
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
|
this.isDrawing = true
|
||||||
let compositionOp: CompositionOperation
|
let compositionOp: CompositionOperation
|
||||||
let currentTool = await this.messageBroker.pull('currentTool')
|
let currentTool = await this.messageBroker.pull('currentTool')
|
||||||
let coords = { x: event.offsetX, y: event.offsetY }
|
let coords = { x: event.offsetX, y: event.offsetY }
|
||||||
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
|
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
|
||||||
|
await this.createBrushStrokeCanvas()
|
||||||
|
|
||||||
//set drawing mode
|
//set drawing mode
|
||||||
if (currentTool === Tools.Eraser || event.buttons == 2) {
|
if (currentTool === Tools.Eraser || event.buttons == 2) {
|
||||||
@@ -1931,15 +2065,6 @@ class BrushTool {
|
|||||||
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
|
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
|
||||||
let currentTool = await this.messageBroker.pull('currentTool')
|
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)
|
if (diff > 20 && !this.isDrawing)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.init_shape(CompositionOperation.SourceOver)
|
this.init_shape(CompositionOperation.SourceOver)
|
||||||
@@ -2051,29 +2176,62 @@ class BrushTool {
|
|||||||
|
|
||||||
private async handleBrushAdjustment(event: PointerEvent) {
|
private async handleBrushAdjustment(event: PointerEvent) {
|
||||||
const coords = { x: event.offsetX, y: event.offsetY }
|
const coords = { x: event.offsetX, y: event.offsetY }
|
||||||
|
const brushDeadZone = 5
|
||||||
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
|
let coords_canvas = await this.messageBroker.pull('screenToCanvas', coords)
|
||||||
|
|
||||||
const delta_x = coords_canvas.x - this.initialPoint!.x
|
const delta_x = coords_canvas.x - this.initialPoint!.x
|
||||||
const delta_y = coords_canvas.y - this.initialPoint!.y
|
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(
|
const newSize = Math.max(
|
||||||
1,
|
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(
|
const newHardness = Math.max(
|
||||||
0,
|
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.size = newSize
|
||||||
this.brushSettings.hardness = newHardness
|
this.brushSettings.hardness = newHardness
|
||||||
|
|
||||||
this.messageBroker.publish('updateBrushPreview')
|
this.messageBroker.publish('updateBrushPreview')
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper functions
|
//helper functions
|
||||||
@@ -2263,8 +2421,8 @@ class BrushTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setBrushSmoothingPrecision(precision: number) {
|
private setBrushSmoothingPrecision(precision: number) {
|
||||||
console.log('precision', precision)
|
//console.log('precision', precision)
|
||||||
// this.brushSettings.smoothingPrecision = precision
|
this.smoothingPrecision = precision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2300,6 +2458,9 @@ class UIManager {
|
|||||||
private mask_opacity: number = 0.7
|
private mask_opacity: number = 0.7
|
||||||
private maskBlendMode: MaskBlendMode = MaskBlendMode.Black
|
private maskBlendMode: MaskBlendMode = MaskBlendMode.Black
|
||||||
|
|
||||||
|
private zoomTextHTML!: HTMLSpanElement
|
||||||
|
private dimensionsTextHTML!: HTMLSpanElement
|
||||||
|
|
||||||
constructor(rootElement: HTMLElement, maskEditor: MaskEditorDialog) {
|
constructor(rootElement: HTMLElement, maskEditor: MaskEditorDialog) {
|
||||||
this.rootElement = rootElement
|
this.rootElement = rootElement
|
||||||
this.maskEditor = maskEditor
|
this.maskEditor = maskEditor
|
||||||
@@ -2332,6 +2493,10 @@ class UIManager {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.messageBroker.subscribe('updateCursor', () => this.updateCursor())
|
this.messageBroker.subscribe('updateCursor', () => this.updateCursor())
|
||||||
|
|
||||||
|
this.messageBroker.subscribe('setZoomText', (text: string) =>
|
||||||
|
this.setZoomText(text)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
addPullTopics() {
|
addPullTopics() {
|
||||||
@@ -2975,11 +3140,17 @@ class UIManager {
|
|||||||
return separator
|
return separator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
|
||||||
private async createTopBar() {
|
private async createTopBar() {
|
||||||
const buttonAccentColor = this.darkMode
|
const buttonAccentColor = this.darkMode
|
||||||
? 'maskEditor_topPanelButton_dark'
|
? 'maskEditor_topPanelButton_dark'
|
||||||
: 'maskEditor_topPanelButton_light'
|
: 'maskEditor_topPanelButton_light'
|
||||||
|
|
||||||
|
const iconButtonAccentColor = this.darkMode
|
||||||
|
? 'maskEditor_topPanelIconButton_dark'
|
||||||
|
: 'maskEditor_topPanelIconButton_light'
|
||||||
|
|
||||||
var top_bar = document.createElement('div')
|
var top_bar = document.createElement('div')
|
||||||
top_bar.id = 'maskEditor_topBar'
|
top_bar.id = 'maskEditor_topBar'
|
||||||
|
|
||||||
@@ -2997,9 +3168,9 @@ class UIManager {
|
|||||||
|
|
||||||
var top_bar_undo_button = document.createElement('div')
|
var top_bar_undo_button = document.createElement('div')
|
||||||
top_bar_undo_button.id = 'maskEditor_topBarUndoButton'
|
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.innerHTML =
|
||||||
'<svg viewBox="0 0 15 15" style="width: 36px;height: 36px;pointer-events: none;fill: var(--input-text);"><path d="M8.77,12.18c-.25,0-.46-.2-.46-.46s.2-.46.46-.46c1.47,0,2.67-1.2,2.67-2.67,0-1.57-1.34-2.67-3.26-2.67h-3.98l1.43,1.43c.18.18.18.47,0,.64-.18.18-.47.18-.64,0l-2.21-2.21c-.18-.18-.18-.47,0-.64l2.21-2.21c.18-.18.47-.18.64,0,.18.18.18.47,0,.64l-1.43,1.43h3.98c2.45,0,4.17,1.47,4.17,3.58,0,1.97-1.61,3.58-3.58,3.58Z"></path> </svg>'
|
'<svg viewBox="0 0 15 15"><path d="M8.77,12.18c-.25,0-.46-.2-.46-.46s.2-.46.46-.46c1.47,0,2.67-1.2,2.67-2.67,0-1.57-1.34-2.67-3.26-2.67h-3.98l1.43,1.43c.18.18.18.47,0,.64-.18.18-.47.18-.64,0l-2.21-2.21c-.18-.18-.18-.47,0-.64l2.21-2.21c.18-.18.47-.18.64,0,.18.18.18.47,0,.64l-1.43,1.43h3.98c2.45,0,4.17,1.47,4.17,3.58,0,1.97-1.61,3.58-3.58,3.58Z"></path> </svg>'
|
||||||
|
|
||||||
top_bar_undo_button.addEventListener('click', () => {
|
top_bar_undo_button.addEventListener('click', () => {
|
||||||
this.messageBroker.publish('undo')
|
this.messageBroker.publish('undo')
|
||||||
@@ -3007,19 +3178,21 @@ class UIManager {
|
|||||||
|
|
||||||
var top_bar_redo_button = document.createElement('div')
|
var top_bar_redo_button = document.createElement('div')
|
||||||
top_bar_redo_button.id = 'maskEditor_topBarRedoButton'
|
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.innerHTML =
|
||||||
'<svg viewBox="0 0 15 15" style="width: 36px;height: 36px;pointer-events: none;fill: var(--input-text);"> <path class="cls-1" d="M6.23,12.18c-1.97,0-3.58-1.61-3.58-3.58,0-2.11,1.71-3.58,4.17-3.58h3.98l-1.43-1.43c-.18-.18-.18-.47,0-.64.18-.18.46-.18.64,0l2.21,2.21c.09.09.13.2.13.32s-.05.24-.13.32l-2.21,2.21c-.18.18-.47.18-.64,0-.18-.18-.18-.47,0-.64l1.43-1.43h-3.98c-1.92,0-3.26,1.1-3.26,2.67,0,1.47,1.2,2.67,2.67,2.67.25,0,.46.2.46.46s-.2.46-.46.46Z"/></svg>'
|
'<svg viewBox="0 0 15 15"> <path class="cls-1" d="M6.23,12.18c-1.97,0-3.58-1.61-3.58-3.58,0-2.11,1.71-3.58,4.17-3.58h3.98l-1.43-1.43c-.18-.18-.18-.47,0-.64.18-.18.46-.18.64,0l2.21,2.21c.09.09.13.2.13.32s-.05.24-.13.32l-2.21,2.21c-.18.18-.47.18-.64,0-.18-.18-.18-.47,0-.64l1.43-1.43h-3.98c-1.92,0-3.26,1.1-3.26,2.67,0,1.47,1.2,2.67,2.67,2.67.25,0,.46.2.46.46s-.2.46-.46.46Z"/></svg>'
|
||||||
|
|
||||||
top_bar_redo_button.addEventListener('click', () => {
|
top_bar_redo_button.addEventListener('click', () => {
|
||||||
this.messageBroker.publish('redo')
|
this.messageBroker.publish('redo')
|
||||||
})
|
})
|
||||||
|
|
||||||
top_bar_shortcuts_container.appendChild(top_bar_undo_button)
|
var top_bar_invert_button = document.createElement('button')
|
||||||
top_bar_shortcuts_container.appendChild(top_bar_redo_button)
|
top_bar_invert_button.id = 'maskEditor_topBarInvertButton'
|
||||||
|
top_bar_invert_button.classList.add(buttonAccentColor)
|
||||||
var top_bar_button_container = document.createElement('div')
|
top_bar_invert_button.innerText = 'Invert'
|
||||||
top_bar_button_container.id = 'maskEditor_topBarButtonContainer'
|
top_bar_invert_button.addEventListener('click', () => {
|
||||||
|
this.messageBroker.publish('invert')
|
||||||
|
})
|
||||||
|
|
||||||
var top_bar_clear_button = document.createElement('button')
|
var top_bar_clear_button = document.createElement('button')
|
||||||
top_bar_clear_button.id = 'maskEditor_topBarClearButton'
|
top_bar_clear_button.id = 'maskEditor_topBarClearButton'
|
||||||
@@ -3055,23 +3228,26 @@ class UIManager {
|
|||||||
this.maskEditor.close()
|
this.maskEditor.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
top_bar_button_container.appendChild(top_bar_clear_button)
|
top_bar_shortcuts_container.appendChild(top_bar_undo_button)
|
||||||
top_bar_button_container.appendChild(top_bar_save_button)
|
top_bar_shortcuts_container.appendChild(top_bar_redo_button)
|
||||||
top_bar_button_container.appendChild(top_bar_cancel_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_title_container)
|
||||||
top_bar.appendChild(top_bar_shortcuts_container)
|
top_bar.appendChild(top_bar_shortcuts_container)
|
||||||
top_bar.appendChild(top_bar_button_container)
|
|
||||||
|
|
||||||
return top_bar
|
return top_bar
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------
|
|
||||||
|
|
||||||
private createToolPanel() {
|
private createToolPanel() {
|
||||||
var pen_tool_panel = document.createElement('div')
|
var tool_panel = document.createElement('div')
|
||||||
pen_tool_panel.id = 'maskEditor_toolPanel'
|
tool_panel.id = 'maskEditor_toolPanel'
|
||||||
this.toolPanel = pen_tool_panel
|
this.toolPanel = tool_panel
|
||||||
|
var toolPanelHoverAccent = this.darkMode
|
||||||
|
? 'maskEditor_toolPanelContainerDark'
|
||||||
|
: 'maskEditor_toolPanelContainerLight'
|
||||||
|
|
||||||
var toolElements: HTMLElement[] = []
|
var toolElements: HTMLElement[] = []
|
||||||
|
|
||||||
@@ -3082,6 +3258,7 @@ class UIManager {
|
|||||||
toolPanel_brushToolContainer.classList.add(
|
toolPanel_brushToolContainer.classList.add(
|
||||||
'maskEditor_toolPanelContainerSelected'
|
'maskEditor_toolPanelContainerSelected'
|
||||||
)
|
)
|
||||||
|
toolPanel_brushToolContainer.classList.add(toolPanelHoverAccent)
|
||||||
toolPanel_brushToolContainer.innerHTML = `
|
toolPanel_brushToolContainer.innerHTML = `
|
||||||
<svg viewBox="0 0 44 44">
|
<svg viewBox="0 0 44 44">
|
||||||
<path class="cls-1" d="M34,13.93c0,.47-.19.94-.55,1.31l-13.02,13.04c-.09.07-.18.15-.27.22-.07-1.39-1.21-2.48-2.61-2.49.07-.12.16-.24.27-.34l13.04-13.04c.72-.72,1.89-.72,2.6,0,.35.35.55.83.55,1.3Z"/>
|
<path class="cls-1" d="M34,13.93c0,.47-.19.94-.55,1.31l-13.02,13.04c-.09.07-.18.15-.27.22-.07-1.39-1.21-2.48-2.61-2.49.07-.12.16-.24.27-.34l13.04-13.04c.72-.72,1.89-.72,2.6,0,.35.35.55.83.55,1.3Z"/>
|
||||||
@@ -3116,6 +3293,7 @@ class UIManager {
|
|||||||
|
|
||||||
var toolPanel_eraserToolContainer = document.createElement('div')
|
var toolPanel_eraserToolContainer = document.createElement('div')
|
||||||
toolPanel_eraserToolContainer.classList.add('maskEditor_toolPanelContainer')
|
toolPanel_eraserToolContainer.classList.add('maskEditor_toolPanelContainer')
|
||||||
|
toolPanel_eraserToolContainer.classList.add(toolPanelHoverAccent)
|
||||||
toolPanel_eraserToolContainer.innerHTML = `
|
toolPanel_eraserToolContainer.innerHTML = `
|
||||||
<svg viewBox="0 0 44 44">
|
<svg viewBox="0 0 44 44">
|
||||||
<g>
|
<g>
|
||||||
@@ -3155,6 +3333,7 @@ class UIManager {
|
|||||||
toolPanel_paintBucketToolContainer.classList.add(
|
toolPanel_paintBucketToolContainer.classList.add(
|
||||||
'maskEditor_toolPanelContainer'
|
'maskEditor_toolPanelContainer'
|
||||||
)
|
)
|
||||||
|
toolPanel_paintBucketToolContainer.classList.add(toolPanelHoverAccent)
|
||||||
toolPanel_paintBucketToolContainer.innerHTML = `
|
toolPanel_paintBucketToolContainer.innerHTML = `
|
||||||
<svg viewBox="0 0 44 44">
|
<svg viewBox="0 0 44 44">
|
||||||
<path class="cls-1" d="M33.4,21.76l-11.42,11.41-.04.05c-.61.61-1.6.61-2.21,0l-8.91-8.91c-.61-.61-.61-1.6,0-2.21l.04-.05.3-.29h22.24Z"/>
|
<path class="cls-1" d="M33.4,21.76l-11.42,11.41-.04.05c-.61.61-1.6.61-2.21,0l-8.91-8.91c-.61-.61-.61-1.6,0-2.21l.04-.05.3-.29h22.24Z"/>
|
||||||
@@ -3198,6 +3377,7 @@ class UIManager {
|
|||||||
toolPanel_colorSelectToolContainer.classList.add(
|
toolPanel_colorSelectToolContainer.classList.add(
|
||||||
'maskEditor_toolPanelContainer'
|
'maskEditor_toolPanelContainer'
|
||||||
)
|
)
|
||||||
|
toolPanel_colorSelectToolContainer.classList.add(toolPanelHoverAccent)
|
||||||
toolPanel_colorSelectToolContainer.innerHTML = `
|
toolPanel_colorSelectToolContainer.innerHTML = `
|
||||||
<svg viewBox="0 0 44 44">
|
<svg viewBox="0 0 44 44">
|
||||||
<path class="cls-1" d="M30.29,13.72c-1.09-1.1-2.85-1.09-3.94,0l-2.88,2.88-.75-.75c-.2-.19-.51-.19-.71,0-.19.2-.19.51,0,.71l1.4,1.4-9.59,9.59c-.35.36-.54.82-.54,1.32,0,.14,0,.28.05.41-.05.04-.1.08-.15.13-.39.39-.39,1.01,0,1.4.38.39,1.01.39,1.4,0,.04-.04.08-.09.11-.13.14.04.3.06.45.06.5,0,.97-.19,1.32-.55l9.59-9.59,1.38,1.38c.1.09.22.14.35.14s.26-.05.35-.14c.2-.2.2-.52,0-.71l-.71-.72,2.88-2.89c1.08-1.08,1.08-2.85-.01-3.94ZM19.43,25.82h-2.46l7.15-7.15,1.23,1.23-5.92,5.92Z"/>
|
<path class="cls-1" d="M30.29,13.72c-1.09-1.1-2.85-1.09-3.94,0l-2.88,2.88-.75-.75c-.2-.19-.51-.19-.71,0-.19.2-.19.51,0,.71l1.4,1.4-9.59,9.59c-.35.36-.54.82-.54,1.32,0,.14,0,.28.05.41-.05.04-.1.08-.15.13-.39.39-.39,1.01,0,1.4.38.39,1.01.39,1.4,0,.04-.04.08-.09.11-.13.14.04.3.06.45.06.5,0,.97-.19,1.32-.55l9.59-9.59,1.38,1.38c.1.09.22.14.35.14s.26-.05.35-.14c.2-.2.2-.52,0-.71l-.71-.72,2.88-2.89c1.08-1.08,1.08-2.85-.01-3.94ZM19.43,25.82h-2.46l7.15-7.15,1.23,1.23-5.92,5.92Z"/>
|
||||||
@@ -3230,17 +3410,35 @@ class UIManager {
|
|||||||
toolPanel_colorSelectToolIndicator
|
toolPanel_colorSelectToolIndicator
|
||||||
)
|
)
|
||||||
|
|
||||||
pen_tool_panel.appendChild(toolPanel_brushToolContainer)
|
//zoom indicator
|
||||||
pen_tool_panel.appendChild(toolPanel_eraserToolContainer)
|
var toolPanel_zoomIndicator = document.createElement('div')
|
||||||
pen_tool_panel.appendChild(toolPanel_paintBucketToolContainer)
|
toolPanel_zoomIndicator.classList.add('maskEditor_toolPanelZoomIndicator')
|
||||||
pen_tool_panel.appendChild(toolPanel_colorSelectToolContainer)
|
toolPanel_zoomIndicator.classList.add(toolPanelHoverAccent)
|
||||||
|
|
||||||
var pen_tool_panel_change_tool_button = document.createElement('button')
|
var toolPanel_zoomText = document.createElement('span')
|
||||||
pen_tool_panel_change_tool_button.id =
|
toolPanel_zoomText.id = 'maskEditor_toolPanelZoomText'
|
||||||
'maskEditor_toolPanelChangeToolButton'
|
toolPanel_zoomText.innerText = '100%'
|
||||||
pen_tool_panel_change_tool_button.innerText = 'change to Eraser'
|
this.zoomTextHTML = toolPanel_zoomText
|
||||||
|
|
||||||
return pen_tool_panel
|
var toolPanel_DimensionsText = document.createElement('span')
|
||||||
|
toolPanel_DimensionsText.id = 'maskEditor_toolPanelDimensionsText'
|
||||||
|
toolPanel_DimensionsText.innerText = ' '
|
||||||
|
this.dimensionsTextHTML = toolPanel_DimensionsText
|
||||||
|
|
||||||
|
toolPanel_zoomIndicator.appendChild(toolPanel_zoomText)
|
||||||
|
toolPanel_zoomIndicator.appendChild(toolPanel_DimensionsText)
|
||||||
|
|
||||||
|
toolPanel_zoomIndicator.addEventListener('click', () => {
|
||||||
|
this.messageBroker.publish('resetZoom')
|
||||||
|
})
|
||||||
|
|
||||||
|
tool_panel.appendChild(toolPanel_brushToolContainer)
|
||||||
|
tool_panel.appendChild(toolPanel_eraserToolContainer)
|
||||||
|
tool_panel.appendChild(toolPanel_paintBucketToolContainer)
|
||||||
|
tool_panel.appendChild(toolPanel_colorSelectToolContainer)
|
||||||
|
tool_panel.appendChild(toolPanel_zoomIndicator)
|
||||||
|
|
||||||
|
return tool_panel
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPointerZone() {
|
private createPointerZone() {
|
||||||
@@ -3384,6 +3582,8 @@ class UIManager {
|
|||||||
maskCanvas.width = this.image.width
|
maskCanvas.width = this.image.width
|
||||||
maskCanvas.height = this.image.height
|
maskCanvas.height = this.image.height
|
||||||
|
|
||||||
|
this.dimensionsTextHTML.innerText = `${this.image.width}x${this.image.height}`
|
||||||
|
|
||||||
await this.invalidateCanvas(this.image, mask_image)
|
await this.invalidateCanvas(this.image, mask_image)
|
||||||
this.messageBroker.publish('initZoomPan', [this.image, this.rootElement])
|
this.messageBroker.publish('initZoomPan', [this.image, this.rootElement])
|
||||||
}
|
}
|
||||||
@@ -3638,6 +3838,14 @@ class UIManager {
|
|||||||
this.updateBrushPreview()
|
this.updateBrushPreview()
|
||||||
this.setBrushPreviewGradientVisibility(false)
|
this.setBrushPreviewGradientVisibility(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setZoomText(zoomText: string) {
|
||||||
|
this.zoomTextHTML.innerText = zoomText
|
||||||
|
}
|
||||||
|
|
||||||
|
setDimensionsText(dimensionsText: string) {
|
||||||
|
this.dimensionsTextHTML.innerText = dimensionsText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ToolManager {
|
class ToolManager {
|
||||||
@@ -3814,6 +4022,7 @@ class PanAndZoomManager {
|
|||||||
lastTouchPoint: Point = { x: 0, y: 0 }
|
lastTouchPoint: Point = { x: 0, y: 0 }
|
||||||
|
|
||||||
zoom_ratio: number = 1
|
zoom_ratio: number = 1
|
||||||
|
interpolatedZoomRatio: number = 1
|
||||||
pan_offset: Offset = { x: 0, y: 0 }
|
pan_offset: Offset = { x: 0, y: 0 }
|
||||||
|
|
||||||
mouseDownPoint: Point | null = null
|
mouseDownPoint: Point | null = null
|
||||||
@@ -3821,8 +4030,11 @@ class PanAndZoomManager {
|
|||||||
|
|
||||||
canvasContainer: HTMLElement | null = null
|
canvasContainer: HTMLElement | null = null
|
||||||
maskCanvas: HTMLCanvasElement | null = null
|
maskCanvas: HTMLCanvasElement | null = null
|
||||||
|
rootElement: HTMLElement | null = null
|
||||||
|
|
||||||
image: HTMLImageElement | null = null
|
image: HTMLImageElement | null = null
|
||||||
|
imageRootWidth: number = 0
|
||||||
|
imageRootHeight: number = 0
|
||||||
|
|
||||||
cursorPoint: Point = { x: 0, y: 0 }
|
cursorPoint: Point = { x: 0, y: 0 }
|
||||||
|
|
||||||
@@ -3878,6 +4090,11 @@ class PanAndZoomManager {
|
|||||||
this.handleTouchEnd(event)
|
this.handleTouchEnd(event)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.messageBroker.subscribe('resetZoom', async () => {
|
||||||
|
if (this.interpolatedZoomRatio === 1) return
|
||||||
|
await this.smoothResetView()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private addPullTopics() {
|
private addPullTopics() {
|
||||||
@@ -4054,14 +4271,25 @@ class PanAndZoomManager {
|
|||||||
const mouseX = cursorPoint.x - rect.left
|
const mouseX = cursorPoint.x - rect.left
|
||||||
const mouseY = cursorPoint.y - rect.top
|
const mouseY = cursorPoint.y - rect.top
|
||||||
|
|
||||||
|
console.log(oldZoom, newZoom)
|
||||||
// Calculate new pan position
|
// Calculate new pan position
|
||||||
const scaleFactor = newZoom / oldZoom
|
const scaleFactor = newZoom / oldZoom
|
||||||
this.pan_offset.x += mouseX - mouseX * scaleFactor
|
this.pan_offset.x += mouseX - mouseX * scaleFactor
|
||||||
this.pan_offset.y += mouseY - mouseY * scaleFactor
|
this.pan_offset.y += mouseY - mouseY * scaleFactor
|
||||||
|
|
||||||
|
console.log(this.imageRootWidth, this.imageRootHeight)
|
||||||
|
|
||||||
// Update pan and zoom immediately
|
// Update pan and zoom immediately
|
||||||
await this.invalidatePanZoom()
|
await this.invalidatePanZoom()
|
||||||
|
|
||||||
|
const newImageWidth = maskCanvas.clientWidth
|
||||||
|
|
||||||
|
const zoomRatio = newImageWidth / this.imageRootWidth
|
||||||
|
|
||||||
|
this.interpolatedZoomRatio = zoomRatio
|
||||||
|
|
||||||
|
this.messageBroker.publish('setZoomText', `${Math.round(zoomRatio * 100)}%`)
|
||||||
|
|
||||||
// Update cursor position with new pan values
|
// Update cursor position with new pan values
|
||||||
this.updateCursorPosition(cursorPoint)
|
this.updateCursorPosition(cursorPoint)
|
||||||
|
|
||||||
@@ -4071,51 +4299,125 @@ class PanAndZoomManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async smoothResetView(duration: number = 500) {
|
||||||
|
// Store initial state
|
||||||
|
const startZoom = this.zoom_ratio
|
||||||
|
const startPan = { ...this.pan_offset }
|
||||||
|
|
||||||
|
// Panel dimensions
|
||||||
|
const sidePanelWidth = 220
|
||||||
|
const toolPanelWidth = 64
|
||||||
|
const topBarHeight = 44
|
||||||
|
|
||||||
|
// Calculate available space
|
||||||
|
const availableWidth =
|
||||||
|
this.rootElement!.clientWidth - sidePanelWidth - toolPanelWidth
|
||||||
|
const availableHeight = this.rootElement!.clientHeight - topBarHeight
|
||||||
|
|
||||||
|
// Calculate target zoom
|
||||||
|
const zoomRatioWidth = availableWidth / this.image!.width
|
||||||
|
const zoomRatioHeight = availableHeight / this.image!.height
|
||||||
|
const targetZoom = Math.min(zoomRatioWidth, zoomRatioHeight)
|
||||||
|
|
||||||
|
// Calculate final dimensions
|
||||||
|
const aspectRatio = this.image!.width / this.image!.height
|
||||||
|
let finalWidth = 0
|
||||||
|
let finalHeight = 0
|
||||||
|
|
||||||
|
// Calculate target pan position
|
||||||
|
const targetPan = { x: toolPanelWidth, y: topBarHeight }
|
||||||
|
|
||||||
|
if (zoomRatioHeight > zoomRatioWidth) {
|
||||||
|
finalWidth = availableWidth
|
||||||
|
finalHeight = finalWidth / aspectRatio
|
||||||
|
targetPan.y = (availableHeight - finalHeight) / 2 + topBarHeight
|
||||||
|
} else {
|
||||||
|
finalHeight = availableHeight
|
||||||
|
finalWidth = finalHeight * aspectRatio
|
||||||
|
targetPan.x = (availableWidth - finalWidth) / 2 + toolPanelWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now()
|
||||||
|
const animate = (currentTime: number) => {
|
||||||
|
const elapsed = currentTime - startTime
|
||||||
|
const progress = Math.min(elapsed / duration, 1)
|
||||||
|
// Cubic easing out for smooth deceleration
|
||||||
|
const eased = 1 - Math.pow(1 - progress, 3)
|
||||||
|
|
||||||
|
// Calculate intermediate zoom and pan values
|
||||||
|
const currentZoom = startZoom + (targetZoom - startZoom) * eased
|
||||||
|
|
||||||
|
this.zoom_ratio = currentZoom
|
||||||
|
this.pan_offset.x = startPan.x + (targetPan.x - startPan.x) * eased
|
||||||
|
this.pan_offset.y = startPan.y + (targetPan.y - startPan.y) * eased
|
||||||
|
|
||||||
|
this.invalidatePanZoom()
|
||||||
|
|
||||||
|
const interpolatedZoomRatio = startZoom + (1.0 - startZoom) * eased
|
||||||
|
|
||||||
|
this.messageBroker.publish(
|
||||||
|
'setZoomText',
|
||||||
|
`${Math.round(interpolatedZoomRatio * 100)}%`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(animate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(animate)
|
||||||
|
this.interpolatedZoomRatio = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
async initializeCanvasPanZoom(
|
async initializeCanvasPanZoom(
|
||||||
image: HTMLImageElement,
|
image: HTMLImageElement,
|
||||||
rootElement: HTMLElement
|
rootElement: HTMLElement
|
||||||
) {
|
) {
|
||||||
// Get side panel width
|
// Get side panel width
|
||||||
let sidePanelWidth = 220
|
let sidePanelWidth = 220
|
||||||
|
const toolPanelWidth = 64
|
||||||
let topBarHeight = 44
|
let topBarHeight = 44
|
||||||
|
|
||||||
|
this.rootElement = rootElement
|
||||||
|
|
||||||
// Calculate available width accounting for both side panels
|
// Calculate available width accounting for both side panels
|
||||||
let availableWidth = rootElement.clientWidth - 2 * sidePanelWidth
|
let availableWidth =
|
||||||
|
rootElement.clientWidth - sidePanelWidth - toolPanelWidth
|
||||||
let availableHeight = rootElement.clientHeight - topBarHeight
|
let availableHeight = rootElement.clientHeight - topBarHeight
|
||||||
|
|
||||||
// Initial dimensions
|
let zoomRatioWidth = availableWidth / image.width
|
||||||
let drawWidth = image.width
|
let zoomRatioHeight = availableHeight / image.height
|
||||||
let drawHeight = image.height
|
|
||||||
|
|
||||||
// First check if width needs scaling
|
let aspectRatio = image.width / image.height
|
||||||
if (drawWidth > availableWidth) {
|
|
||||||
drawWidth = availableWidth
|
|
||||||
drawHeight = (drawWidth / image.width) * image.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then check if height needs scaling
|
let finalWidth = 0
|
||||||
if (drawHeight > availableHeight) {
|
let finalHeight = 0
|
||||||
drawHeight = availableHeight
|
|
||||||
drawWidth = (drawHeight / image.height) * image.width
|
let pan_offset: Offset = { x: toolPanelWidth, y: topBarHeight }
|
||||||
|
|
||||||
|
if (zoomRatioHeight > zoomRatioWidth) {
|
||||||
|
finalWidth = availableWidth
|
||||||
|
finalHeight = finalWidth / aspectRatio
|
||||||
|
pan_offset.y = (availableHeight - finalHeight) / 2 + topBarHeight
|
||||||
|
} else {
|
||||||
|
finalHeight = availableHeight
|
||||||
|
finalWidth = finalHeight * aspectRatio
|
||||||
|
pan_offset.x = (availableWidth - finalWidth) / 2 + toolPanelWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.image === null) {
|
if (this.image === null) {
|
||||||
this.image = image
|
this.image = image
|
||||||
}
|
}
|
||||||
|
|
||||||
this.zoom_ratio = drawWidth / image.width
|
this.imageRootWidth = finalWidth
|
||||||
|
this.imageRootHeight = finalHeight
|
||||||
|
|
||||||
// Center the canvas in the available space
|
this.zoom_ratio = Math.min(zoomRatioWidth, zoomRatioHeight)
|
||||||
const canvasX = sidePanelWidth + (availableWidth - drawWidth) / 2
|
this.pan_offset = pan_offset
|
||||||
const canvasY = (availableHeight - drawHeight) / 2
|
|
||||||
|
|
||||||
this.pan_offset.x = canvasX
|
|
||||||
this.pan_offset.y = canvasY
|
|
||||||
|
|
||||||
await this.invalidatePanZoom()
|
await this.invalidatePanZoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
//probably move to PanZoomManager
|
|
||||||
async invalidatePanZoom() {
|
async invalidatePanZoom() {
|
||||||
// Single validation check upfront
|
// Single validation check upfront
|
||||||
if (
|
if (
|
||||||
@@ -4132,10 +4434,6 @@ class PanAndZoomManager {
|
|||||||
const raw_width = this.image.width * this.zoom_ratio
|
const raw_width = this.image.width * this.zoom_ratio
|
||||||
const raw_height = this.image.height * this.zoom_ratio
|
const raw_height = this.image.height * this.zoom_ratio
|
||||||
|
|
||||||
// Adjust pan offset
|
|
||||||
this.pan_offset.x = Math.max(10 - raw_width, this.pan_offset.x)
|
|
||||||
this.pan_offset.y = Math.max(10 - raw_height, this.pan_offset.y)
|
|
||||||
|
|
||||||
// Get canvas container
|
// Get canvas container
|
||||||
this.canvasContainer ??=
|
this.canvasContainer ??=
|
||||||
await this.messageBroker?.pull('getCanvasContainer')
|
await this.messageBroker?.pull('getCanvasContainer')
|
||||||
@@ -4238,6 +4536,9 @@ class MessageBroker {
|
|||||||
this.createPushTopic('setMaskBoundary')
|
this.createPushTopic('setMaskBoundary')
|
||||||
this.createPushTopic('setMaskTolerance')
|
this.createPushTopic('setMaskTolerance')
|
||||||
this.createPushTopic('setBrushSmoothingPrecision')
|
this.createPushTopic('setBrushSmoothingPrecision')
|
||||||
|
this.createPushTopic('setZoomText')
|
||||||
|
this.createPushTopic('resetZoom')
|
||||||
|
this.createPushTopic('invert')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -4425,12 +4726,38 @@ app.registerExtension({
|
|||||||
settings: [
|
settings: [
|
||||||
{
|
{
|
||||||
id: 'Comfy.MaskEditor.UseNewEditor',
|
id: 'Comfy.MaskEditor.UseNewEditor',
|
||||||
category: ['Comfy', 'Masking'],
|
category: ['Mask Editor', 'NewEditor'],
|
||||||
name: 'Use new mask editor',
|
name: 'Use new mask editor',
|
||||||
tooltip: 'Switch to the new mask editor interface',
|
tooltip: 'Switch to the new mask editor interface',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
experimental: true
|
experimental: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.MaskEditor.BrushAdjustmentSpeed',
|
||||||
|
category: ['Mask Editor', 'BrushAdjustment', 'Sensitivity'],
|
||||||
|
name: 'Brush adjustment speed multiplier',
|
||||||
|
tooltip:
|
||||||
|
'Controls how quickly the brush size and hardness change when adjusting. Higher values mean faster changes.',
|
||||||
|
experimental: true,
|
||||||
|
type: 'slider',
|
||||||
|
attrs: {
|
||||||
|
min: 0.1,
|
||||||
|
max: 2.0,
|
||||||
|
step: 0.1
|
||||||
|
},
|
||||||
|
defaultValue: 1.0,
|
||||||
|
versionAdded: '1.0.0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Comfy.MaskEditor.UseDominantAxis',
|
||||||
|
category: ['Mask Editor', 'BrushAdjustment', 'UseDominantAxis'],
|
||||||
|
name: 'Lock brush adjustment to dominant axis',
|
||||||
|
tooltip:
|
||||||
|
'When enabled, brush adjustments will only affect size OR hardness based on which direction you move more',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: true,
|
||||||
|
experimental: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
init(app) {
|
init(app) {
|
||||||
|
|||||||
Reference in New Issue
Block a user