diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 970bbf2fc1..9457baf5ed 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1710,7 +1710,9 @@ "inpaintMask": "Inpaint Mask", "regionalGuidance": "Regional Guidance", "canvasAsRasterLayer": "$t(controlLayers.canvas) as $t(controlLayers.rasterLayer)", + "canvasAsRasterLayerResize": "$t(controlLayers.canvas) as $t(controlLayers.rasterLayer) (Resize)", "canvasAsControlLayer": "$t(controlLayers.canvas) as $t(controlLayers.controlLayer)", + "canvasAsControlLayerResize": "$t(controlLayers.canvas) as $t(controlLayers.controlLayer) (Resize)", "referenceImage": "Reference Image", "regionalReferenceImage": "Regional Reference Image", "globalReferenceImage": "Global Reference Image", diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewFromImageSubMenu.tsx index 8679791300..6ab8121667 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewFromImageSubMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewFromImageSubMenu.tsx @@ -27,7 +27,7 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => { const onClickNewCanvasWithRasterLayerFromImage = useCallback(() => { const { dispatch, getState } = store; - newCanvasFromImage({ imageDTO, type: 'raster_layer', dispatch, getState }); + newCanvasFromImage({ imageDTO, withResize: false, type: 'raster_layer', dispatch, getState }); dispatch(setActiveTab('canvas')); imageViewer.close(); toast({ @@ -39,7 +39,31 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => { const onClickNewCanvasWithControlLayerFromImage = useCallback(() => { const { dispatch, getState } = store; - newCanvasFromImage({ imageDTO, type: 'control_layer', dispatch, getState }); + newCanvasFromImage({ imageDTO, withResize: false, type: 'control_layer', dispatch, getState }); + dispatch(setActiveTab('canvas')); + imageViewer.close(); + toast({ + id: 'SENT_TO_CANVAS', + title: t('toast.sentToCanvas'), + status: 'success', + }); + }, [imageDTO, imageViewer, store, t]); + + const onClickNewCanvasWithRasterLayerFromImageWithResize = useCallback(() => { + const { dispatch, getState } = store; + newCanvasFromImage({ imageDTO, withResize: true, type: 'raster_layer', dispatch, getState }); + dispatch(setActiveTab('canvas')); + imageViewer.close(); + toast({ + id: 'SENT_TO_CANVAS', + title: t('toast.sentToCanvas'), + status: 'success', + }); + }, [imageDTO, imageViewer, store, t]); + + const onClickNewCanvasWithControlLayerFromImageWithResize = useCallback(() => { + const { dispatch, getState } = store; + newCanvasFromImage({ imageDTO, withResize: true, type: 'control_layer', dispatch, getState }); dispatch(setActiveTab('canvas')); imageViewer.close(); toast({ @@ -111,6 +135,13 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => { } onClickCapture={onClickNewCanvasWithRasterLayerFromImage} isDisabled={isBusy}> {t('controlLayers.canvasAsRasterLayer')} + } + onClickCapture={onClickNewCanvasWithRasterLayerFromImageWithResize} + isDisabled={isBusy} + > + {t('controlLayers.canvasAsRasterLayerResize')} + } onClickCapture={onClickNewCanvasWithControlLayerFromImage} @@ -118,6 +149,13 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => { > {t('controlLayers.canvasAsControlLayer')} + } + onClickCapture={onClickNewCanvasWithControlLayerFromImageWithResize} + isDisabled={isBusy || isSD3} + > + {t('controlLayers.canvasAsControlLayerResize')} + } onClickCapture={onClickNewInpaintMaskFromImage} isDisabled={isBusy}> {t('controlLayers.inpaintMask')} diff --git a/invokeai/frontend/web/src/features/imageActions/actions.ts b/invokeai/frontend/web/src/features/imageActions/actions.ts index ca0e9f9938..c293488b5d 100644 --- a/invokeai/frontend/web/src/features/imageActions/actions.ts +++ b/invokeai/frontend/web/src/features/imageActions/actions.ts @@ -182,21 +182,24 @@ export const createNewCanvasEntityFromImage = (arg: { }; /** - * Creates a new canvas with the given image as the initial image, replicating the img2img flow: + * Creates a new canvas with the given image as the only layer: * - Reset the canvas * - Resize the bbox to the image's aspect ratio at the optimal size for the selected model - * - Add the image as a raster layer - * - Resizes the layer to fit the bbox using the 'fill' strategy + * - Add the image as a layer of the given type + * - If `withResize`: Resizes the layer to fit the bbox using the 'fill' strategy * * This allows the user to immediately generate a new image from the given image without any additional steps. + * + * Using 'raster_layer' for the type and enabling `withResize` replicates the common img2img flow. */ export const newCanvasFromImage = (arg: { imageDTO: ImageDTO; type: CanvasEntityType | 'regional_guidance_with_reference_image'; + withResize: boolean; dispatch: AppDispatch; getState: () => RootState; }) => { - const { type, imageDTO, dispatch, getState } = arg; + const { type, imageDTO, withResize, dispatch, getState } = arg; const state = getState(); const base = selectBboxModelBase(state); @@ -229,7 +232,9 @@ export const newCanvasFromImage = (arg: { objects: [imageObject], position: { x, y }, } satisfies Partial; - addInitCallback(overrides.id); + if (withResize) { + addInitCallback(overrides.id); + } dispatch(canvasReset()); // The `bboxChangedFromCanvas` reducer does no validation! Careful! dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height })); @@ -243,7 +248,9 @@ export const newCanvasFromImage = (arg: { position: { x, y }, controlAdapter: deepClone(initialControlNet), } satisfies Partial; - addInitCallback(overrides.id); + if (withResize) { + addInitCallback(overrides.id); + } dispatch(canvasReset()); // The `bboxChangedFromCanvas` reducer does no validation! Careful! dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height })); @@ -256,7 +263,9 @@ export const newCanvasFromImage = (arg: { objects: [imageObject], position: { x, y }, } satisfies Partial; - addInitCallback(overrides.id); + if (withResize) { + addInitCallback(overrides.id); + } dispatch(canvasReset()); // The `bboxChangedFromCanvas` reducer does no validation! Careful! dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height })); @@ -269,7 +278,9 @@ export const newCanvasFromImage = (arg: { objects: [imageObject], position: { x, y }, } satisfies Partial; - addInitCallback(overrides.id); + if (withResize) { + addInitCallback(overrides.id); + } dispatch(canvasReset()); // The `bboxChangedFromCanvas` reducer does no validation! Careful! dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));