diff --git a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index 998114da22..f0e1e196fd 100644 --- a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -3,6 +3,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { clearCanvasHistory, setShouldAutoSave, + setShouldCropToBoundingBoxOnSave, setShouldDarkenOutsideBoundingBox, setShouldShowCanvasDebugInfo, setShouldShowGrid, @@ -22,21 +23,23 @@ export const canvasControlsSelector = createSelector( [canvasSelector], (canvas) => { const { - shouldDarkenOutsideBoundingBox, - shouldShowIntermediates, - shouldShowGrid, - shouldSnapToGrid, shouldAutoSave, + shouldCropToBoundingBoxOnSave, + shouldDarkenOutsideBoundingBox, shouldShowCanvasDebugInfo, + shouldShowGrid, + shouldShowIntermediates, + shouldSnapToGrid, } = canvas; return { - shouldShowGrid, - shouldSnapToGrid, shouldAutoSave, + shouldCropToBoundingBoxOnSave, shouldDarkenOutsideBoundingBox, - shouldShowIntermediates, shouldShowCanvasDebugInfo, + shouldShowGrid, + shouldShowIntermediates, + shouldSnapToGrid, }; }, { @@ -49,12 +52,13 @@ export const canvasControlsSelector = createSelector( const IAICanvasSettingsButtonPopover = () => { const dispatch = useAppDispatch(); const { - shouldShowIntermediates, - shouldShowGrid, - shouldSnapToGrid, shouldAutoSave, + shouldCropToBoundingBoxOnSave, shouldDarkenOutsideBoundingBox, shouldShowCanvasDebugInfo, + shouldShowGrid, + shouldShowIntermediates, + shouldSnapToGrid, } = useAppSelector(canvasControlsSelector); return ( @@ -98,6 +102,13 @@ const IAICanvasSettingsButtonPopover = () => { isChecked={shouldAutoSave} onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))} /> + + dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)) + } + /> { const { isProcessing } = system; - const { tool } = canvas; + const { tool, shouldCropToBoundingBoxOnSave } = canvas; return { isProcessing, isStaging, tool, + shouldCropToBoundingBoxOnSave, }; }, { @@ -55,7 +56,8 @@ export const selector = createSelector( const IAICanvasOutpaintingControls = () => { const dispatch = useAppDispatch(); - const { isProcessing, isStaging, tool } = useAppSelector(selector); + const { isProcessing, isStaging, tool, shouldCropToBoundingBoxOnSave } = + useAppSelector(selector); const canvasBaseLayer = getCanvasBaseLayer(); const { openUploader } = useImageUploader(); @@ -164,7 +166,8 @@ const IAICanvasOutpaintingControls = () => { const handleSaveToGallery = () => { dispatch( mergeAndUploadCanvas({ - cropVisible: true, + cropVisible: shouldCropToBoundingBoxOnSave ? false : true, + cropToBoundingBox: shouldCropToBoundingBoxOnSave, shouldSaveToGallery: true, }) ); @@ -173,7 +176,8 @@ const IAICanvasOutpaintingControls = () => { const handleCopyImageToClipboard = () => { dispatch( mergeAndUploadCanvas({ - cropVisible: true, + cropVisible: shouldCropToBoundingBoxOnSave ? false : true, + cropToBoundingBox: shouldCropToBoundingBoxOnSave, shouldCopy: true, }) ); @@ -182,7 +186,8 @@ const IAICanvasOutpaintingControls = () => { const handleDownloadAsImage = () => { dispatch( mergeAndUploadCanvas({ - cropVisible: true, + cropVisible: shouldCropToBoundingBoxOnSave ? false : true, + cropToBoundingBox: shouldCropToBoundingBoxOnSave, shouldDownload: true, }) ); diff --git a/frontend/src/features/canvas/store/canvasSlice.ts b/frontend/src/features/canvas/store/canvasSlice.ts index 0e0576c1c0..c668aa12cf 100644 --- a/frontend/src/features/canvas/store/canvasSlice.ts +++ b/frontend/src/features/canvas/store/canvasSlice.ts @@ -64,6 +64,7 @@ const initialCanvasState: CanvasState = { minimumStageScale: 1, pastLayerStates: [], shouldAutoSave: false, + shouldCropToBoundingBoxOnSave: true, shouldDarkenOutsideBoundingBox: false, shouldLockBoundingBox: false, shouldPreserveMaskedArea: false, @@ -676,6 +677,12 @@ export const canvasSlice = createSlice({ setShouldShowCanvasDebugInfo: (state, action: PayloadAction) => { state.shouldShowCanvasDebugInfo = action.payload; }, + setShouldCropToBoundingBoxOnSave: ( + state, + action: PayloadAction + ) => { + state.shouldCropToBoundingBoxOnSave = action.payload; + }, setMergedCanvas: (state, action: PayloadAction) => { state.pastLayerStates.push({ ...state.layerState, @@ -737,6 +744,7 @@ export const { setMaskColor, setMergedCanvas, setShouldAutoSave, + setShouldCropToBoundingBoxOnSave, setShouldDarkenOutsideBoundingBox, setShouldLockBoundingBox, setShouldPreserveMaskedArea, diff --git a/frontend/src/features/canvas/store/canvasTypes.ts b/frontend/src/features/canvas/store/canvasTypes.ts index 47ef56414c..ef28101d75 100644 --- a/frontend/src/features/canvas/store/canvasTypes.ts +++ b/frontend/src/features/canvas/store/canvasTypes.ts @@ -102,6 +102,7 @@ export interface CanvasState { minimumStageScale: number; pastLayerStates: CanvasLayerState[]; shouldAutoSave: boolean; + shouldCropToBoundingBoxOnSave: boolean; shouldDarkenOutsideBoundingBox: boolean; shouldLockBoundingBox: boolean; shouldPreserveMaskedArea: boolean; diff --git a/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts b/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts index 99f457b260..d04c56fb24 100644 --- a/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts +++ b/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts @@ -15,9 +15,11 @@ import { } from 'features/system/store/systemSlice'; import { addImage } from 'features/gallery/store/gallerySlice'; import { setMergedCanvas } from '../canvasSlice'; +import { CanvasState } from '../canvasTypes'; type MergeAndUploadCanvasConfig = { cropVisible?: boolean; + cropToBoundingBox?: boolean; shouldSaveToGallery?: boolean; shouldDownload?: boolean; shouldCopy?: boolean; @@ -26,6 +28,7 @@ type MergeAndUploadCanvasConfig = { const defaultConfig: MergeAndUploadCanvasConfig = { cropVisible: false, + cropToBoundingBox: false, shouldSaveToGallery: false, shouldDownload: false, shouldCopy: false, @@ -37,6 +40,7 @@ export const mergeAndUploadCanvas = async (dispatch, getState) => { const { cropVisible, + cropToBoundingBox, shouldSaveToGallery, shouldDownload, shouldCopy, @@ -48,7 +52,12 @@ export const mergeAndUploadCanvas = const state = getState() as RootState; - const stageScale = state.canvas.stageScale; + const { + stageScale, + boundingBoxCoordinates, + boundingBoxDimensions, + stageCoordinates, + } = state.canvas as CanvasState; const canvasBaseLayer = getCanvasBaseLayer(); @@ -61,7 +70,11 @@ export const mergeAndUploadCanvas = const { dataURL, boundingBox: originalBoundingBox } = layerToDataURL( canvasBaseLayer, - stageScale + stageScale, + stageCoordinates, + cropToBoundingBox + ? { ...boundingBoxCoordinates, ...boundingBoxDimensions } + : undefined ); if (!dataURL) { diff --git a/frontend/src/features/canvas/util/layerToDataURL.ts b/frontend/src/features/canvas/util/layerToDataURL.ts index abdbc0a21d..5e92eba5bc 100644 --- a/frontend/src/features/canvas/util/layerToDataURL.ts +++ b/frontend/src/features/canvas/util/layerToDataURL.ts @@ -1,6 +1,12 @@ import Konva from 'konva'; +import { IRect, Vector2d } from 'konva/lib/types'; -const layerToDataURL = (layer: Konva.Layer, stageScale: number) => { +const layerToDataURL = ( + layer: Konva.Layer, + stageScale: number, + stageCoordinates: Vector2d, + boundingBox?: IRect +) => { const tempScale = layer.scale(); const relativeClientRect = layer.getClientRect({ @@ -14,13 +20,21 @@ const layerToDataURL = (layer: Konva.Layer, stageScale: number) => { }); const { x, y, width, height } = layer.getClientRect(); + const dataURLBoundingBox = boundingBox + ? { + x: Math.round(boundingBox.x + stageCoordinates.x), + y: Math.round(boundingBox.y + stageCoordinates.y), + width: Math.round(boundingBox.width), + height: Math.round(boundingBox.height), + } + : { + x: Math.round(x), + y: Math.round(y), + width: Math.round(width), + height: Math.round(height), + }; - const dataURL = layer.toDataURL({ - x: Math.round(x), - y: Math.round(y), - width: Math.round(width), - height: Math.round(height), - }); + const dataURL = layer.toDataURL(dataURLBoundingBox); // Unscale the canvas layer.scale(tempScale);