Abandons "inpainting" canvas lock

This commit is contained in:
psychedelicious 2022-11-18 13:02:47 +11:00 committed by blessedcoolant
parent c0005eb063
commit 635e7da05d
15 changed files with 99 additions and 392 deletions

View File

@ -84,14 +84,14 @@ export const store = configureStore({
devTools: { devTools: {
// Uncommenting these very rapidly called actions makes the redux dev tools output much more readable // Uncommenting these very rapidly called actions makes the redux dev tools output much more readable
actionsDenylist: [ actionsDenylist: [
// 'canvas/setCursorPosition', 'canvas/setCursorPosition',
// 'canvas/setStageCoordinates', 'canvas/setStageCoordinates',
// 'canvas/setStageScale', 'canvas/setStageScale',
// 'canvas/setIsDrawing', 'canvas/setIsDrawing',
// 'canvas/setBoundingBoxCoordinates', // 'canvas/setBoundingBoxCoordinates',
// 'canvas/setBoundingBoxDimensions', // 'canvas/setBoundingBoxDimensions',
// 'canvas/setIsDrawing', 'canvas/setIsDrawing',
// 'canvas/addPointToCurrentLine', 'canvas/addPointToCurrentLine',
], ],
}, },
}); });

View File

@ -1,13 +1,11 @@
import { MutableRefObject, useCallback, useRef } from 'react'; import { MutableRefObject, useRef } from 'react';
import Konva from 'konva'; import Konva from 'konva';
import { Layer, Stage } from 'react-konva'; import { Layer, Stage } from 'react-konva';
import { Stage as StageType } from 'konva/lib/Stage'; import { Stage as StageType } from 'konva/lib/Stage';
import { useAppSelector } from 'app/store'; import { useAppSelector } from 'app/store';
import { import {
initialCanvasImageSelector,
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
shouldLockToInitialImageSelector,
} from 'features/canvas/store/canvasSelectors'; } from 'features/canvas/store/canvasSelectors';
import IAICanvasMaskLines from './IAICanvasMaskLines'; import IAICanvasMaskLines from './IAICanvasMaskLines';
import IAICanvasBrushPreview from './IAICanvasBrushPreview'; import IAICanvasBrushPreview from './IAICanvasBrushPreview';
@ -16,7 +14,6 @@ import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
import useCanvasHotkeys from '../hooks/useCanvasHotkeys'; import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
import _ from 'lodash'; import _ from 'lodash';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import IAICanvasMaskCompositer from './IAICanvasMaskCompositer'; import IAICanvasMaskCompositer from './IAICanvasMaskCompositer';
import useCanvasWheel from '../hooks/useCanvasZoom'; import useCanvasWheel from '../hooks/useCanvasZoom';
import useCanvasMouseDown from '../hooks/useCanvasMouseDown'; import useCanvasMouseDown from '../hooks/useCanvasMouseDown';
@ -33,20 +30,8 @@ import IAICanvasStagingArea from './IAICanvasStagingArea';
import IAICanvasStagingAreaToolbar from './IAICanvasStagingAreaToolbar'; import IAICanvasStagingAreaToolbar from './IAICanvasStagingAreaToolbar';
const selector = createSelector( const selector = createSelector(
[ [canvasSelector, isStagingSelector],
shouldLockToInitialImageSelector, (canvas, isStaging) => {
canvasSelector,
isStagingSelector,
activeTabNameSelector,
initialCanvasImageSelector,
],
(
shouldLockToInitialImage,
canvas,
isStaging,
activeTabName,
initialCanvasImage
) => {
const { const {
isMaskEnabled, isMaskEnabled,
stageScale, stageScale,
@ -90,8 +75,6 @@ const selector = createSelector(
tool, tool,
isStaging, isStaging,
shouldShowIntermediates, shouldShowIntermediates,
shouldLockToInitialImage,
initialCanvasImage,
}; };
}, },
{ {
@ -118,8 +101,6 @@ const IAICanvas = () => {
tool, tool,
isStaging, isStaging,
shouldShowIntermediates, shouldShowIntermediates,
shouldLockToInitialImage,
initialCanvasImage,
} = useAppSelector(selector); } = useAppSelector(selector);
useCanvasHotkeys(); useCanvasHotkeys();
@ -145,34 +126,6 @@ const IAICanvas = () => {
const { handleDragStart, handleDragMove, handleDragEnd } = const { handleDragStart, handleDragMove, handleDragEnd } =
useCanvasDragMove(); useCanvasDragMove();
const dragBoundFunc = useCallback(
(newCoordinates: Vector2d) => {
if (shouldLockToInitialImage && initialCanvasImage) {
newCoordinates.x = _.clamp(
newCoordinates.x,
stageDimensions.width -
Math.floor(initialCanvasImage.width * stageScale),
0
);
newCoordinates.y = _.clamp(
newCoordinates.y,
stageDimensions.height -
Math.floor(initialCanvasImage.height * stageScale),
0
);
}
return newCoordinates;
},
[
initialCanvasImage,
shouldLockToInitialImage,
stageDimensions.height,
stageDimensions.width,
stageScale,
]
);
return ( return (
<div className="inpainting-canvas-container"> <div className="inpainting-canvas-container">
<div className="inpainting-canvas-wrapper"> <div className="inpainting-canvas-wrapper">
@ -188,7 +141,6 @@ const IAICanvas = () => {
width={stageDimensions.width} width={stageDimensions.width}
height={stageDimensions.height} height={stageDimensions.height}
scale={{ x: stageScale, y: stageScale }} scale={{ x: stageScale, y: stageScale }}
dragBoundFunc={dragBoundFunc}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseOut} onMouseLeave={handleMouseOut}

View File

@ -38,7 +38,6 @@ const IAICanvasObjectRenderer = () => {
); );
} else if (isCanvasBaseLine(obj)) { } else if (isCanvasBaseLine(obj)) {
return ( return (
<Group {...obj.clipRect}>
<Line <Line
key={i} key={i}
points={obj.points} points={obj.points}
@ -53,7 +52,6 @@ const IAICanvasObjectRenderer = () => {
obj.tool === 'brush' ? 'source-over' : 'destination-out' obj.tool === 'brush' ? 'source-over' : 'destination-out'
} }
/> />
</Group>
); );
} }
})} })}

View File

@ -9,21 +9,19 @@ import {
setDoesCanvasNeedScaling, setDoesCanvasNeedScaling,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { canvasSelector, initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors'; import {
canvasSelector,
initialCanvasImageSelector,
} from 'features/canvas/store/canvasSelectors';
const canvasResizerSelector = createSelector( const canvasResizerSelector = createSelector(
canvasSelector, canvasSelector,
initialCanvasImageSelector, initialCanvasImageSelector,
activeTabNameSelector, activeTabNameSelector,
(canvas, initialCanvasImage, activeTabName) => { (canvas, initialCanvasImage, activeTabName) => {
const { const { doesCanvasNeedScaling, isCanvasInitialized } = canvas;
doesCanvasNeedScaling,
shouldLockToInitialImage,
isCanvasInitialized,
} = canvas;
return { return {
doesCanvasNeedScaling, doesCanvasNeedScaling,
shouldLockToInitialImage,
activeTabName, activeTabName,
initialCanvasImage, initialCanvasImage,
isCanvasInitialized, isCanvasInitialized,
@ -35,7 +33,6 @@ const IAICanvasResizer = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { const {
doesCanvasNeedScaling, doesCanvasNeedScaling,
shouldLockToInitialImage,
activeTabName, activeTabName,
initialCanvasImage, initialCanvasImage,
isCanvasInitialized, isCanvasInitialized,
@ -58,11 +55,7 @@ const IAICanvasResizer = () => {
}) })
); );
if (!isCanvasInitialized || shouldLockToInitialImage) {
dispatch(resizeAndScaleCanvas());
} else {
dispatch(resizeCanvas()); dispatch(resizeCanvas());
}
dispatch(setDoesCanvasNeedScaling(false)); dispatch(setDoesCanvasNeedScaling(false));
}, 0); }, 0);
@ -72,7 +65,6 @@ const IAICanvasResizer = () => {
doesCanvasNeedScaling, doesCanvasNeedScaling,
activeTabName, activeTabName,
isCanvasInitialized, isCanvasInitialized,
shouldLockToInitialImage,
]); ]);
return ( return (

View File

@ -1,18 +1,16 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import Konva from 'konva'; import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node'; import { KonvaEventObject } from 'konva/lib/Node';
import { Box } from 'konva/lib/shapes/Transformer';
import { Vector2d } from 'konva/lib/types'; import { Vector2d } from 'konva/lib/types';
import _ from 'lodash'; import _ from 'lodash';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { Group, Rect, Transformer } from 'react-konva'; import { Group, Rect, Transformer } from 'react-konva';
import { useAppDispatch, useAppSelector } from 'app/store'; import { useAppDispatch, useAppSelector } from 'app/store';
import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { import {
initialCanvasImageSelector, roundDownToMultiple,
canvasSelector, roundToMultiple,
shouldLockToInitialImageSelector, } from 'common/util/roundDownToMultiple';
} from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { import {
setBoundingBoxCoordinates, setBoundingBoxCoordinates,
setBoundingBoxDimensions, setBoundingBoxDimensions,
@ -21,14 +19,10 @@ import {
setIsTransformingBoundingBox, setIsTransformingBoundingBox,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { GroupConfig } from 'konva/lib/Group'; import { GroupConfig } from 'konva/lib/Group';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
const boundingBoxPreviewSelector = createSelector( const boundingBoxPreviewSelector = createSelector(
shouldLockToInitialImageSelector,
canvasSelector, canvasSelector,
initialCanvasImageSelector, (canvas) => {
activeTabNameSelector,
(shouldLockToInitialImage, canvas, initialCanvasImage, activeTabName) => {
const { const {
boundingBoxCoordinates, boundingBoxCoordinates,
boundingBoxDimensions, boundingBoxDimensions,
@ -54,14 +48,11 @@ const boundingBoxPreviewSelector = createSelector(
isTransformingBoundingBox, isTransformingBoundingBox,
stageDimensions, stageDimensions,
stageScale, stageScale,
initialCanvasImage,
activeTabName,
shouldSnapToGrid, shouldSnapToGrid,
tool, tool,
stageCoordinates, stageCoordinates,
boundingBoxStrokeWidth: (isMouseOverBoundingBox ? 8 : 1) / stageScale, boundingBoxStrokeWidth: (isMouseOverBoundingBox ? 8 : 1) / stageScale,
hitStrokeWidth: 20 / stageScale, hitStrokeWidth: 20 / stageScale,
shouldLockToInitialImage,
}; };
}, },
{ {
@ -88,13 +79,10 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
stageCoordinates, stageCoordinates,
stageDimensions, stageDimensions,
stageScale, stageScale,
initialCanvasImage,
activeTabName,
shouldSnapToGrid, shouldSnapToGrid,
tool, tool,
boundingBoxStrokeWidth, boundingBoxStrokeWidth,
hitStrokeWidth, hitStrokeWidth,
shouldLockToInitialImage,
} = useAppSelector(boundingBoxPreviewSelector); } = useAppSelector(boundingBoxPreviewSelector);
const transformerRef = useRef<Konva.Transformer>(null); const transformerRef = useRef<Konva.Transformer>(null);
@ -139,40 +127,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
[dispatch, shouldSnapToGrid] [dispatch, shouldSnapToGrid]
); );
const dragBoundFunc = useCallback(
(position: Vector2d) => {
/**
* This limits the bounding box's drag coordinates.
*/
if (!shouldLockToInitialImage) return boundingBoxCoordinates;
const { x, y } = position;
const maxX =
stageDimensions.width -
boundingBoxDimensions.width -
(stageDimensions.width % 64);
const maxY =
stageDimensions.height -
boundingBoxDimensions.height -
(stageDimensions.height % 64);
const clampedX = Math.floor(_.clamp(x, 0, maxX));
const clampedY = Math.floor(_.clamp(y, 0, maxY));
return { x: clampedX, y: clampedY };
},
[
shouldLockToInitialImage,
boundingBoxCoordinates,
stageDimensions.width,
stageDimensions.height,
boundingBoxDimensions.width,
boundingBoxDimensions.height,
]
);
const handleOnTransform = useCallback(() => { const handleOnTransform = useCallback(() => {
/** /**
* The Konva Transformer changes the object's anchor point and scale factor, * The Konva Transformer changes the object's anchor point and scale factor,
@ -202,15 +156,15 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
dispatch( dispatch(
setBoundingBoxCoordinates({ setBoundingBoxCoordinates({
x, x: shouldSnapToGrid ? roundDownToMultiple(x, 64) : x,
y, y: shouldSnapToGrid ? roundDownToMultiple(y, 64) : y,
}) })
); );
// Reset the scale now that the coords/dimensions have been un-scaled // Reset the scale now that the coords/dimensions have been un-scaled
rect.scaleX(1); rect.scaleX(1);
rect.scaleY(1); rect.scaleY(1);
}, [dispatch]); }, [dispatch, shouldSnapToGrid]);
const anchorDragBoundFunc = useCallback( const anchorDragBoundFunc = useCallback(
( (
@ -225,40 +179,26 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
* *
* We need to snap the new dimensions to steps of 64. But because the whole * We need to snap the new dimensions to steps of 64. But because the whole
* stage is scaled, our actual desired step is actually 64 * the stage scale. * stage is scaled, our actual desired step is actually 64 * the stage scale.
*
* Additionally, we need to ensure we offset the position so that we snap to a
* multiple of 64 that is aligned with the grid, and not from the absolute zero
* coordinate.
*/ */
return { // Calculate the offset of the grid.
x: roundToMultiple(newPos.x, scaledStep), const offsetX = oldPos.x % scaledStep;
y: roundToMultiple(newPos.y, scaledStep), const offsetY = oldPos.y % scaledStep;
const newCoordinates = {
x: roundDownToMultiple(newPos.x, scaledStep) + offsetX,
y: roundDownToMultiple(newPos.y, scaledStep) + offsetY,
}; };
return newCoordinates;
}, },
[scaledStep] [scaledStep]
); );
const boundBoxFunc = useCallback(
(oldBoundBox: Box, newBoundBox: Box) => {
/**
* The transformer uses this callback to limit valid transformations.
* Unlike anchorDragBoundFunc, it does get a width and height, so
* the logic to constrain the size of the bounding box is very simple.
*/
// On the Inpainting canvas, the bounding box needs to stay in the stage
if (
shouldLockToInitialImage &&
(newBoundBox.width + newBoundBox.x > stageDimensions.width ||
newBoundBox.height + newBoundBox.y > stageDimensions.height ||
newBoundBox.x < 0 ||
newBoundBox.y < 0)
) {
return oldBoundBox;
}
return newBoundBox;
},
[shouldLockToInitialImage, stageDimensions.height, stageDimensions.width]
);
const handleStartedTransforming = () => { const handleStartedTransforming = () => {
dispatch(setIsTransformingBoundingBox(true)); dispatch(setIsTransformingBoundingBox(true));
}; };
@ -310,11 +250,11 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
globalCompositeOperation={'destination-out'} globalCompositeOperation={'destination-out'}
/> />
<Rect <Rect
{...(shouldLockToInitialImage ? { dragBoundFunc } : {})}
listening={!isDrawing && tool === 'move'}
draggable={true} draggable={true}
fillEnabled={false} fillEnabled={false}
height={boundingBoxDimensions.height} height={boundingBoxDimensions.height}
hitStrokeWidth={hitStrokeWidth}
listening={!isDrawing && tool === 'move'}
onDragEnd={handleEndedModifying} onDragEnd={handleEndedModifying}
onDragMove={handleOnDragMove} onDragMove={handleOnDragMove}
onMouseDown={handleStartedMoving} onMouseDown={handleStartedMoving}
@ -329,7 +269,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
width={boundingBoxDimensions.width} width={boundingBoxDimensions.width}
x={boundingBoxCoordinates.x} x={boundingBoxCoordinates.x}
y={boundingBoxCoordinates.y} y={boundingBoxCoordinates.y}
hitStrokeWidth={hitStrokeWidth}
/> />
<Transformer <Transformer
anchorCornerRadius={3} anchorCornerRadius={3}
@ -340,7 +279,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
borderDash={[4, 4]} borderDash={[4, 4]}
borderEnabled={true} borderEnabled={true}
borderStroke={'black'} borderStroke={'black'}
boundBoxFunc={boundBoxFunc}
draggable={false} draggable={false}
enabledAnchors={tool === 'move' ? undefined : []} enabledAnchors={tool === 'move' ? undefined : []}
flipEnabled={false} flipEnabled={false}

View File

@ -4,7 +4,6 @@ import {
resizeAndScaleCanvas, resizeAndScaleCanvas,
resetCanvas, resetCanvas,
resetCanvasView, resetCanvasView,
setShouldLockToInitialImage,
setTool, setTool,
fitBoundingBoxToStage, fitBoundingBoxToStage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
@ -39,11 +38,10 @@ import {
export const selector = createSelector( export const selector = createSelector(
[canvasSelector, isStagingSelector], [canvasSelector, isStagingSelector],
(canvas, isStaging) => { (canvas, isStaging) => {
const { tool, shouldLockToInitialImage } = canvas; const { tool } = canvas;
return { return {
tool, tool,
isStaging, isStaging,
shouldLockToInitialImage,
}; };
}, },
{ {
@ -55,16 +53,7 @@ export const selector = createSelector(
const IAICanvasOutpaintingControls = () => { const IAICanvasOutpaintingControls = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { tool, isStaging, shouldLockToInitialImage } = const { tool, isStaging } = useAppSelector(selector);
useAppSelector(selector);
const handleToggleShouldLockToInitialImage = (
e: ChangeEvent<HTMLInputElement>
) => {
dispatch(setShouldLockToInitialImage(e.target.checked));
dispatch(resizeAndScaleCanvas());
dispatch(fitBoundingBoxToStage());
};
return ( return (
<div className="inpainting-settings"> <div className="inpainting-settings">
@ -151,11 +140,6 @@ const IAICanvasOutpaintingControls = () => {
onClick={() => dispatch(resetCanvas())} onClick={() => dispatch(resetCanvas())}
/> />
</ButtonGroup> </ButtonGroup>
<IAICheckbox
label={'Lock Canvas to Initial Image'}
isChecked={shouldLockToInitialImage}
onChange={handleToggleShouldLockToInitialImage}
/>
</div> </div>
); );
}; };

View File

@ -8,9 +8,11 @@ import { MutableRefObject, useCallback } from 'react';
import { import {
canvasSelector, canvasSelector,
initialCanvasImageSelector, initialCanvasImageSelector,
shouldLockToInitialImageSelector,
} from 'features/canvas/store/canvasSelectors'; } from 'features/canvas/store/canvasSelectors';
import { setStageCoordinates, setStageScale } from 'features/canvas/store/canvasSlice'; import {
setStageCoordinates,
setStageScale,
} from 'features/canvas/store/canvasSlice';
import { import {
CANVAS_SCALE_BY, CANVAS_SCALE_BY,
MAX_CANVAS_SCALE, MAX_CANVAS_SCALE,
@ -18,13 +20,8 @@ import {
} from '../util/constants'; } from '../util/constants';
const selector = createSelector( const selector = createSelector(
[ [activeTabNameSelector, canvasSelector, initialCanvasImageSelector],
activeTabNameSelector, (activeTabName, canvas, initialCanvasImage) => {
canvasSelector,
initialCanvasImageSelector,
shouldLockToInitialImageSelector,
],
(activeTabName, canvas, initialCanvasImage, shouldLockToInitialImage) => {
const { const {
isMoveStageKeyHeld, isMoveStageKeyHeld,
stageScale, stageScale,
@ -36,7 +33,6 @@ const selector = createSelector(
stageScale, stageScale,
activeTabName, activeTabName,
initialCanvasImage, initialCanvasImage,
shouldLockToInitialImage,
stageDimensions, stageDimensions,
minimumStageScale, minimumStageScale,
}; };
@ -51,7 +47,6 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
stageScale, stageScale,
activeTabName, activeTabName,
initialCanvasImage, initialCanvasImage,
shouldLockToInitialImage,
stageDimensions, stageDimensions,
minimumStageScale, minimumStageScale,
} = useAppSelector(selector); } = useAppSelector(selector);
@ -83,7 +78,7 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
const newScale = _.clamp( const newScale = _.clamp(
stageScale * CANVAS_SCALE_BY ** delta, stageScale * CANVAS_SCALE_BY ** delta,
shouldLockToInitialImage ? minimumStageScale : MIN_CANVAS_SCALE, MIN_CANVAS_SCALE,
MAX_CANVAS_SCALE MAX_CANVAS_SCALE
); );
@ -92,36 +87,10 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
y: cursorPos.y - mousePointTo.y * newScale, y: cursorPos.y - mousePointTo.y * newScale,
}; };
if (shouldLockToInitialImage) {
newCoordinates.x = _.clamp(
newCoordinates.x,
stageDimensions.width -
Math.floor(initialCanvasImage.width * newScale),
0
);
newCoordinates.y = _.clamp(
newCoordinates.y,
stageDimensions.height -
Math.floor(initialCanvasImage.height * newScale),
0
);
}
dispatch(setStageScale(newScale)); dispatch(setStageScale(newScale));
dispatch(setStageCoordinates(newCoordinates)); dispatch(setStageCoordinates(newCoordinates));
}, },
[ [stageRef, isMoveStageKeyHeld, initialCanvasImage, stageScale, dispatch]
activeTabName,
stageRef,
isMoveStageKeyHeld,
initialCanvasImage,
stageScale,
shouldLockToInitialImage,
minimumStageScale,
dispatch,
stageDimensions.width,
stageDimensions.height,
]
); );
}; };

View File

@ -6,9 +6,6 @@ export const canvasSelector = (state: RootState): CanvasState => state.canvas;
export const isStagingSelector = (state: RootState): boolean => export const isStagingSelector = (state: RootState): boolean =>
state.canvas.layerState.stagingArea.images.length > 0; state.canvas.layerState.stagingArea.images.length > 0;
export const shouldLockToInitialImageSelector = (state: RootState): boolean =>
state.canvas.shouldLockToInitialImage;
export const initialCanvasImageSelector = ( export const initialCanvasImageSelector = (
state: RootState state: RootState
): CanvasImage | undefined => ): CanvasImage | undefined =>

View File

@ -49,7 +49,6 @@ const initialCanvasState: CanvasState = {
eraserSize: 50, eraserSize: 50,
futureLayerStates: [], futureLayerStates: [],
inpaintReplace: 0.1, inpaintReplace: 0.1,
initialCanvasImageClipRect: undefined,
isCanvasInitialized: false, isCanvasInitialized: false,
isDrawing: false, isDrawing: false,
isMaskEnabled: true, isMaskEnabled: true,
@ -68,7 +67,6 @@ const initialCanvasState: CanvasState = {
shouldAutoSave: false, shouldAutoSave: false,
shouldDarkenOutsideBoundingBox: false, shouldDarkenOutsideBoundingBox: false,
shouldLockBoundingBox: false, shouldLockBoundingBox: false,
shouldLockToInitialImage: false,
shouldPreserveMaskedArea: false, shouldPreserveMaskedArea: false,
shouldShowBoundingBox: true, shouldShowBoundingBox: true,
shouldShowBrush: true, shouldShowBrush: true,
@ -298,10 +296,6 @@ export const canvasSlice = createSlice({
tool, tool,
strokeWidth: newStrokeWidth, strokeWidth: newStrokeWidth,
points: action.payload, points: action.payload,
clipRect:
state.shouldLockToInitialImage && state.initialCanvasImageClipRect
? state.initialCanvasImageClipRect
: undefined,
...newColor, ...newColor,
}); });
@ -375,16 +369,10 @@ export const canvasSlice = createSlice({
state.layerState.objects.find(isCanvasBaseImage); state.layerState.objects.find(isCanvasBaseImage);
if (!initialCanvasImage) return; if (!initialCanvasImage) return;
const { shouldLockToInitialImage, initialCanvasImageClipRect } = state;
let { width: imageWidth, height: imageHeight } = initialCanvasImage; const { width: imageWidth, height: imageHeight } = initialCanvasImage;
if (shouldLockToInitialImage && initialCanvasImageClipRect) { const padding = 0.95;
imageWidth = initialCanvasImageClipRect.clipWidth;
imageHeight = initialCanvasImageClipRect.clipHeight;
}
const padding = shouldLockToInitialImage ? 1 : 0.95;
const newScale = calculateScale( const newScale = calculateScale(
containerWidth, containerWidth,
@ -395,12 +383,8 @@ export const canvasSlice = createSlice({
); );
const newDimensions = { const newDimensions = {
width: shouldLockToInitialImage width: Math.floor(containerWidth),
? Math.floor(imageWidth * newScale) height: Math.floor(containerHeight),
: Math.floor(containerWidth),
height: shouldLockToInitialImage
? Math.floor(imageHeight * newScale)
: Math.floor(containerHeight),
}; };
const newCoordinates = calculateCoordinates( const newCoordinates = calculateCoordinates(
@ -416,7 +400,7 @@ export const canvasSlice = createSlice({
if (!_.isEqual(state.stageDimensions, newDimensions)) { if (!_.isEqual(state.stageDimensions, newDimensions)) {
state.minimumStageScale = newScale; state.minimumStageScale = newScale;
state.stageScale = newScale; state.stageScale = newScale;
state.stageCoordinates = newCoordinates; state.stageCoordinates = floorCoordinates(newCoordinates);
state.stageDimensions = newDimensions; state.stageDimensions = newDimensions;
} }
@ -440,23 +424,16 @@ export const canvasSlice = createSlice({
const { contentRect } = action.payload; const { contentRect } = action.payload;
const baseCanvasImage = state.layerState.objects.find(isCanvasBaseImage); const baseCanvasImage = state.layerState.objects.find(isCanvasBaseImage);
const { shouldLockToInitialImage, initialCanvasImageClipRect } = state;
if (!baseCanvasImage) return; if (!baseCanvasImage) return;
const { const {
stageDimensions: { width: stageWidth, height: stageHeight }, stageDimensions: { width: stageWidth, height: stageHeight },
} = state; } = state;
let { x, y, width, height } = contentRect; const { x, y, width, height } = contentRect;
if (shouldLockToInitialImage && initialCanvasImageClipRect) { const padding = 0.95;
x = initialCanvasImageClipRect.clipX;
y = initialCanvasImageClipRect.clipY;
width = initialCanvasImageClipRect.clipWidth;
height = initialCanvasImageClipRect.clipHeight;
}
const padding = shouldLockToInitialImage ? 1 : 0.95;
const newScale = calculateScale( const newScale = calculateScale(
stageWidth, stageWidth,
stageHeight, stageHeight,
@ -515,39 +492,36 @@ export const canvasSlice = createSlice({
state.futureLayerStates = []; state.futureLayerStates = [];
}, },
setShouldLockToInitialImage: (state, action: PayloadAction<boolean>) => {
state.shouldLockToInitialImage = action.payload;
},
fitBoundingBoxToStage: (state) => { fitBoundingBoxToStage: (state) => {
const { boundingBoxDimensions, boundingBoxCoordinates, stageDimensions } = const {
state; boundingBoxDimensions,
boundingBoxCoordinates,
stageDimensions,
stageScale,
} = state;
const scaledStageWidth = stageDimensions.width / stageScale;
const scaledStageHeight = stageDimensions.height / stageScale;
if ( if (
boundingBoxCoordinates.x < 0 || boundingBoxCoordinates.x < 0 ||
boundingBoxCoordinates.x + boundingBoxDimensions.width > boundingBoxCoordinates.x + boundingBoxDimensions.width >
stageDimensions.width || scaledStageWidth ||
boundingBoxCoordinates.y < 0 || boundingBoxCoordinates.y < 0 ||
boundingBoxCoordinates.y + boundingBoxDimensions.height > boundingBoxCoordinates.y + boundingBoxDimensions.height >
stageDimensions.height scaledStageHeight
) { ) {
const newBoundingBoxDimensions = { const newBoundingBoxDimensions = {
width: roundDownToMultiple( width: roundDownToMultiple(_.clamp(scaledStageWidth, 64, 512), 64),
_.clamp(stageDimensions.width, 64, 512), height: roundDownToMultiple(_.clamp(scaledStageHeight, 64, 512), 64),
64
),
height: roundDownToMultiple(
_.clamp(stageDimensions.height, 64, 512),
64
),
}; };
const newBoundingBoxCoordinates = { const newBoundingBoxCoordinates = {
x: roundToMultiple( x: roundToMultiple(
stageDimensions.width / 2 - newBoundingBoxDimensions.width / 2, scaledStageWidth / 2 - newBoundingBoxDimensions.width / 2,
64 64
), ),
y: roundToMultiple( y: roundToMultiple(
stageDimensions.height / 2 - newBoundingBoxDimensions.height / 2, scaledStageHeight / 2 - newBoundingBoxDimensions.height / 2,
64 64
), ),
}; };
@ -611,7 +585,6 @@ export const {
prevStagingAreaImage, prevStagingAreaImage,
commitStagingAreaImage, commitStagingAreaImage,
discardStagedImages, discardStagedImages,
setShouldLockToInitialImage,
resizeAndScaleCanvas, resizeAndScaleCanvas,
resizeCanvas, resizeCanvas,
resetCanvasView, resetCanvasView,

View File

@ -8,13 +8,6 @@ export type CanvasDrawingTool = 'brush' | 'eraser';
export type CanvasTool = CanvasDrawingTool | 'move'; export type CanvasTool = CanvasDrawingTool | 'move';
export type ClipRect = {
clipX: number;
clipY: number;
clipWidth: number;
clipHeight: number;
};
export type Dimensions = { export type Dimensions = {
width: number; width: number;
height: number; height: number;
@ -25,7 +18,6 @@ export type CanvasAnyLine = {
tool: CanvasDrawingTool; tool: CanvasDrawingTool;
strokeWidth: number; strokeWidth: number;
points: number[]; points: number[];
clipRect: ClipRect | undefined;
}; };
export type CanvasImage = { export type CanvasImage = {
@ -86,7 +78,6 @@ export interface CanvasState {
doesCanvasNeedScaling: boolean; doesCanvasNeedScaling: boolean;
eraserSize: number; eraserSize: number;
futureLayerStates: CanvasLayerState[]; futureLayerStates: CanvasLayerState[];
initialCanvasImageClipRect?: ClipRect;
inpaintReplace: number; inpaintReplace: number;
intermediateImage?: InvokeAI.Image; intermediateImage?: InvokeAI.Image;
isCanvasInitialized: boolean; isCanvasInitialized: boolean;
@ -107,7 +98,6 @@ export interface CanvasState {
shouldAutoSave: boolean; shouldAutoSave: boolean;
shouldDarkenOutsideBoundingBox: boolean; shouldDarkenOutsideBoundingBox: boolean;
shouldLockBoundingBox: boolean; shouldLockBoundingBox: boolean;
shouldLockToInitialImage: boolean;
shouldPreserveMaskedArea: boolean; shouldPreserveMaskedArea: boolean;
shouldShowBoundingBox: boolean; shouldShowBoundingBox: boolean;
shouldShowBrush: boolean; shouldShowBrush: boolean;

View File

@ -47,13 +47,6 @@ export const setInitialCanvasImage = (
}; };
state.futureLayerStates = []; state.futureLayerStates = [];
state.initialCanvasImageClipRect = {
clipX: 0,
clipY: 0,
clipWidth: image.width,
clipHeight: image.height,
};
state.isCanvasInitialized = false; state.isCanvasInitialized = false;
state.doesCanvasNeedScaling = true; state.doesCanvasNeedScaling = true;
}; };

View File

@ -1,11 +1,6 @@
import Konva from 'konva'; import Konva from 'konva';
import { IRect } from 'konva/lib/types';
const layerToDataURL = ( const layerToDataURL = (layer: Konva.Layer, stageScale: number) => {
layer: Konva.Layer,
stageScale: number,
boundingBox?: IRect
) => {
const tempScale = layer.scale(); const tempScale = layer.scale();
const relativeClientRect = layer.getClientRect({ const relativeClientRect = layer.getClientRect({
@ -20,21 +15,12 @@ const layerToDataURL = (
const { x, y, width, height } = layer.getClientRect(); const { x, y, width, height } = layer.getClientRect();
const scaledBoundingBox = boundingBox const dataURL = layer.toDataURL({
? {
x: Math.round(boundingBox.x / stageScale),
y: Math.round(boundingBox.y / stageScale),
width: Math.round(boundingBox.width / stageScale),
height: Math.round(boundingBox.height / stageScale),
}
: {
x: Math.round(x), x: Math.round(x),
y: Math.round(y), y: Math.round(y),
width: Math.round(width), width: Math.round(width),
height: Math.round(height), height: Math.round(height),
}; });
const dataURL = layer.toDataURL(scaledBoundingBox);
// Unscale the canvas // Unscale the canvas
layer.scale(tempScale); layer.scale(tempScale);

View File

@ -22,24 +22,12 @@ export const mergeAndUploadCanvas = createAsyncThunk(
const state = getState() as RootState; const state = getState() as RootState;
const stageScale = state.canvas.stageScale; const stageScale = state.canvas.stageScale;
const clipRect = state.canvas.initialCanvasImageClipRect;
const boundingBox =
state.canvas.shouldLockToInitialImage && clipRect && saveToGallery
? {
x: clipRect.clipX,
y: clipRect.clipY,
width: clipRect.clipWidth,
height: clipRect.clipHeight,
}
: undefined;
if (!canvasImageLayerRef.current) return; if (!canvasImageLayerRef.current) return;
const { dataURL, boundingBox: originalBoundingBox } = layerToDataURL( const { dataURL, boundingBox: originalBoundingBox } = layerToDataURL(
canvasImageLayerRef.current, canvasImageLayerRef.current,
stageScale, stageScale
boundingBox
); );
if (!dataURL) return; if (!dataURL) return;

View File

@ -38,7 +38,6 @@ import {
import { import {
setDoesCanvasNeedScaling, setDoesCanvasNeedScaling,
setInitialCanvasImage, setInitialCanvasImage,
setShouldLockToInitialImage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { GalleryState } from './gallerySlice'; import { GalleryState } from './gallerySlice';
import { activeTabNameSelector } from 'features/options/optionsSelectors'; import { activeTabNameSelector } from 'features/options/optionsSelectors';
@ -317,31 +316,10 @@ const CurrentImageButtons = () => {
const handleClickShowImageDetails = () => const handleClickShowImageDetails = () =>
dispatch(setShouldShowImageDetails(!shouldShowImageDetails)); dispatch(setShouldShowImageDetails(!shouldShowImageDetails));
const handleSendToInpainting = () => { const handleSendToCanvas = () => {
if (!currentImage) return; if (!currentImage) return;
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setShouldLockToInitialImage(true));
dispatch(setInitialCanvasImage(currentImage));
dispatch(setDoesCanvasNeedScaling(true));
if (activeTabName !== 'unifiedCanvas') {
dispatch(setActiveTab('unifiedCanvas'));
}
toast({
title: 'Sent to Unified Canvas',
status: 'success',
duration: 2500,
isClosable: true,
});
};
const handleSendToOutpainting = () => {
if (!currentImage) return;
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setShouldLockToInitialImage(false));
dispatch(setInitialCanvasImage(currentImage)); dispatch(setInitialCanvasImage(currentImage));
dispatch(setDoesCanvasNeedScaling(true)); dispatch(setDoesCanvasNeedScaling(true));
@ -393,17 +371,10 @@ const CurrentImageButtons = () => {
</IAIButton> </IAIButton>
<IAIButton <IAIButton
size={'sm'} size={'sm'}
onClick={handleSendToInpainting} onClick={handleSendToCanvas}
leftIcon={<FaShare />} leftIcon={<FaShare />}
> >
Send to Inpainting Send to Unified Canvas
</IAIButton>
<IAIButton
size={'sm'}
onClick={handleSendToOutpainting}
leftIcon={<FaShare />}
>
Send to Outpainting
</IAIButton> </IAIButton>
<IAIButton <IAIButton
size={'sm'} size={'sm'}

View File

@ -25,7 +25,6 @@ import * as ContextMenu from '@radix-ui/react-context-menu';
import { import {
setDoesCanvasNeedScaling, setDoesCanvasNeedScaling,
setInitialCanvasImage, setInitialCanvasImage,
setShouldLockToInitialImage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { hoverableImageSelector } from './gallerySliceSelectors'; import { hoverableImageSelector } from './gallerySliceSelectors';
@ -96,10 +95,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
}); });
}; };
const handleSendToInpainting = () => { const handleSendToCanvas = () => {
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setShouldLockToInitialImage(true));
dispatch(setInitialCanvasImage(image)); dispatch(setInitialCanvasImage(image));
dispatch(setDoesCanvasNeedScaling(true)); dispatch(setDoesCanvasNeedScaling(true));
@ -108,26 +106,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
} }
toast({ toast({
title: 'Sent to Inpainting', title: 'Sent to Unified Canvas',
status: 'success',
duration: 2500,
isClosable: true,
});
};
const handleSendToOutpainting = () => {
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setShouldLockToInitialImage(false));
dispatch(setInitialCanvasImage(image));
dispatch(setDoesCanvasNeedScaling(true));
if (activeTabName !== 'unifiedCanvas') {
dispatch(setActiveTab('unifiedCanvas'));
}
toast({
title: 'Sent to Outpainting',
status: 'success', status: 'success',
duration: 2500, duration: 2500,
isClosable: true, isClosable: true,
@ -257,11 +236,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
<ContextMenu.Item onClickCapture={handleSendToImageToImage}> <ContextMenu.Item onClickCapture={handleSendToImageToImage}>
Send to Image To Image Send to Image To Image
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Item onClickCapture={handleSendToInpainting}> <ContextMenu.Item onClickCapture={handleSendToCanvas}>
Send to Inpainting Send to Unified Canvas
</ContextMenu.Item>
<ContextMenu.Item onClickCapture={handleSendToOutpainting}>
Send to Outpainting
</ContextMenu.Item> </ContextMenu.Item>
<DeleteImageModal image={image}> <DeleteImageModal image={image}>
<ContextMenu.Item data-warning>Delete Image</ContextMenu.Item> <ContextMenu.Item data-warning>Delete Image</ContextMenu.Item>