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 }));