mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-12 08:30:08 +00:00
## Summary Another implementation for image crop node, alternative for https://github.com/Comfy-Org/ComfyUI_frontend/pull/7014 As discussed with @christian-byrne and @DrJKL we could have single widget - IMAGECROP with 4 ints and UI preview. However, this solution requires changing the definition of image crop node in BE (sent [here](https://github.com/comfyanonymous/ComfyUI/pull/11594)), which will break the exsiting workflow, also it would not allow connect separate int node as input, I am not sure it is a good idea. So I keep two PRs openned for references ## Screenshots https://github.com/user-attachments/assets/fde6938c-4395-48f6-ac05-6282c5eb8157 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7825-feat-Add-visual-crop-preview-widget-for-ImageCrop-node-widget-ImageCrop-2dc6d73d3650812bb8a2cdff4615032b) by [Unito](https://www.unito.io)
104 lines
2.6 KiB
TypeScript
104 lines
2.6 KiB
TypeScript
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import type {
|
|
IBaseWidget,
|
|
IBoundingBoxWidget,
|
|
IImageCropWidget,
|
|
INumericWidget
|
|
} from '@/lib/litegraph/src/types/widgets'
|
|
import type { Bounds } from '@/renderer/core/layout/types'
|
|
import type {
|
|
BoundingBoxInputSpec,
|
|
InputSpec as InputSpecV2
|
|
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
|
|
|
function isBoundingBoxLikeWidget(
|
|
widget: IBaseWidget
|
|
): widget is IBoundingBoxWidget | IImageCropWidget {
|
|
return widget.type === 'boundingbox' || widget.type === 'imagecrop'
|
|
}
|
|
|
|
function isNumericWidget(widget: IBaseWidget): widget is INumericWidget {
|
|
return widget.type === 'number'
|
|
}
|
|
|
|
export const useBoundingBoxWidget = (): ComfyWidgetConstructorV2 => {
|
|
return (
|
|
node: LGraphNode,
|
|
inputSpec: InputSpecV2
|
|
): IBoundingBoxWidget | IImageCropWidget => {
|
|
const spec = inputSpec as BoundingBoxInputSpec
|
|
const { name, component } = spec
|
|
const defaultValue: Bounds = spec.default ?? {
|
|
x: 0,
|
|
y: 0,
|
|
width: 512,
|
|
height: 512
|
|
}
|
|
|
|
const widgetType = component === 'ImageCrop' ? 'imagecrop' : 'boundingbox'
|
|
|
|
const fields: (keyof Bounds)[] = ['x', 'y', 'width', 'height']
|
|
const subWidgets: INumericWidget[] = []
|
|
|
|
const rawWidget = node.addWidget(
|
|
widgetType,
|
|
name,
|
|
{ ...defaultValue },
|
|
null,
|
|
{
|
|
serialize: true,
|
|
canvasOnly: false
|
|
}
|
|
)
|
|
|
|
if (!isBoundingBoxLikeWidget(rawWidget)) {
|
|
throw new Error(`Unexpected widget type: ${rawWidget.type}`)
|
|
}
|
|
|
|
const widget = rawWidget
|
|
|
|
widget.callback = () => {
|
|
for (let i = 0; i < fields.length; i++) {
|
|
const field = fields[i]
|
|
const subWidget = subWidgets[i]
|
|
if (subWidget) {
|
|
subWidget.value = widget.value[field]
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const field of fields) {
|
|
const subWidget = node.addWidget(
|
|
'number',
|
|
field,
|
|
defaultValue[field],
|
|
function (this: INumericWidget, v: number) {
|
|
this.value = Math.round(v)
|
|
widget.value[field] = this.value
|
|
widget.callback?.(widget.value)
|
|
},
|
|
{
|
|
min: field === 'width' || field === 'height' ? 1 : 0,
|
|
max: 8192,
|
|
step: 10,
|
|
step2: 1,
|
|
precision: 0,
|
|
serialize: false,
|
|
canvasOnly: true
|
|
}
|
|
)
|
|
|
|
if (!isNumericWidget(subWidget)) {
|
|
throw new Error(`Unexpected widget type: ${subWidget.type}`)
|
|
}
|
|
|
|
subWidgets.push(subWidget)
|
|
}
|
|
|
|
widget.linkedWidgets = subWidgets
|
|
|
|
return widget
|
|
}
|
|
}
|