mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Abandons "inpainting" canvas lock
This commit is contained in:
parent
c0005eb063
commit
635e7da05d
@ -84,14 +84,14 @@ export const store = configureStore({
|
||||
devTools: {
|
||||
// Uncommenting these very rapidly called actions makes the redux dev tools output much more readable
|
||||
actionsDenylist: [
|
||||
// 'canvas/setCursorPosition',
|
||||
// 'canvas/setStageCoordinates',
|
||||
// 'canvas/setStageScale',
|
||||
// 'canvas/setIsDrawing',
|
||||
'canvas/setCursorPosition',
|
||||
'canvas/setStageCoordinates',
|
||||
'canvas/setStageScale',
|
||||
'canvas/setIsDrawing',
|
||||
// 'canvas/setBoundingBoxCoordinates',
|
||||
// 'canvas/setBoundingBoxDimensions',
|
||||
// 'canvas/setIsDrawing',
|
||||
// 'canvas/addPointToCurrentLine',
|
||||
'canvas/setIsDrawing',
|
||||
'canvas/addPointToCurrentLine',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { MutableRefObject, useCallback, useRef } from 'react';
|
||||
import { MutableRefObject, useRef } from 'react';
|
||||
import Konva from 'konva';
|
||||
import { Layer, Stage } from 'react-konva';
|
||||
import { Stage as StageType } from 'konva/lib/Stage';
|
||||
import { useAppSelector } from 'app/store';
|
||||
import {
|
||||
initialCanvasImageSelector,
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
shouldLockToInitialImageSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import IAICanvasMaskLines from './IAICanvasMaskLines';
|
||||
import IAICanvasBrushPreview from './IAICanvasBrushPreview';
|
||||
@ -16,7 +14,6 @@ import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
|
||||
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
|
||||
import _ from 'lodash';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { activeTabNameSelector } from 'features/options/optionsSelectors';
|
||||
import IAICanvasMaskCompositer from './IAICanvasMaskCompositer';
|
||||
import useCanvasWheel from '../hooks/useCanvasZoom';
|
||||
import useCanvasMouseDown from '../hooks/useCanvasMouseDown';
|
||||
@ -33,20 +30,8 @@ import IAICanvasStagingArea from './IAICanvasStagingArea';
|
||||
import IAICanvasStagingAreaToolbar from './IAICanvasStagingAreaToolbar';
|
||||
|
||||
const selector = createSelector(
|
||||
[
|
||||
shouldLockToInitialImageSelector,
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
activeTabNameSelector,
|
||||
initialCanvasImageSelector,
|
||||
],
|
||||
(
|
||||
shouldLockToInitialImage,
|
||||
canvas,
|
||||
isStaging,
|
||||
activeTabName,
|
||||
initialCanvasImage
|
||||
) => {
|
||||
[canvasSelector, isStagingSelector],
|
||||
(canvas, isStaging) => {
|
||||
const {
|
||||
isMaskEnabled,
|
||||
stageScale,
|
||||
@ -90,8 +75,6 @@ const selector = createSelector(
|
||||
tool,
|
||||
isStaging,
|
||||
shouldShowIntermediates,
|
||||
shouldLockToInitialImage,
|
||||
initialCanvasImage,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -118,8 +101,6 @@ const IAICanvas = () => {
|
||||
tool,
|
||||
isStaging,
|
||||
shouldShowIntermediates,
|
||||
shouldLockToInitialImage,
|
||||
initialCanvasImage,
|
||||
} = useAppSelector(selector);
|
||||
useCanvasHotkeys();
|
||||
|
||||
@ -145,34 +126,6 @@ const IAICanvas = () => {
|
||||
const { handleDragStart, handleDragMove, handleDragEnd } =
|
||||
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 (
|
||||
<div className="inpainting-canvas-container">
|
||||
<div className="inpainting-canvas-wrapper">
|
||||
@ -188,7 +141,6 @@ const IAICanvas = () => {
|
||||
width={stageDimensions.width}
|
||||
height={stageDimensions.height}
|
||||
scale={{ x: stageScale, y: stageScale }}
|
||||
dragBoundFunc={dragBoundFunc}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseOut}
|
||||
|
@ -38,22 +38,20 @@ const IAICanvasObjectRenderer = () => {
|
||||
);
|
||||
} else if (isCanvasBaseLine(obj)) {
|
||||
return (
|
||||
<Group {...obj.clipRect}>
|
||||
<Line
|
||||
key={i}
|
||||
points={obj.points}
|
||||
stroke={obj.color ? rgbaColorToString(obj.color) : 'rgb(0,0,0)'} // The lines can be any color, just need alpha > 0
|
||||
strokeWidth={obj.strokeWidth * 2}
|
||||
tension={0}
|
||||
lineCap="round"
|
||||
lineJoin="round"
|
||||
shadowForStrokeEnabled={false}
|
||||
listening={false}
|
||||
globalCompositeOperation={
|
||||
obj.tool === 'brush' ? 'source-over' : 'destination-out'
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Line
|
||||
key={i}
|
||||
points={obj.points}
|
||||
stroke={obj.color ? rgbaColorToString(obj.color) : 'rgb(0,0,0)'} // The lines can be any color, just need alpha > 0
|
||||
strokeWidth={obj.strokeWidth * 2}
|
||||
tension={0}
|
||||
lineCap="round"
|
||||
lineJoin="round"
|
||||
shadowForStrokeEnabled={false}
|
||||
listening={false}
|
||||
globalCompositeOperation={
|
||||
obj.tool === 'brush' ? 'source-over' : 'destination-out'
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
|
@ -9,21 +9,19 @@ import {
|
||||
setDoesCanvasNeedScaling,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { canvasSelector, initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
canvasSelector,
|
||||
initialCanvasImageSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
|
||||
const canvasResizerSelector = createSelector(
|
||||
canvasSelector,
|
||||
initialCanvasImageSelector,
|
||||
activeTabNameSelector,
|
||||
(canvas, initialCanvasImage, activeTabName) => {
|
||||
const {
|
||||
doesCanvasNeedScaling,
|
||||
shouldLockToInitialImage,
|
||||
isCanvasInitialized,
|
||||
} = canvas;
|
||||
const { doesCanvasNeedScaling, isCanvasInitialized } = canvas;
|
||||
return {
|
||||
doesCanvasNeedScaling,
|
||||
shouldLockToInitialImage,
|
||||
activeTabName,
|
||||
initialCanvasImage,
|
||||
isCanvasInitialized,
|
||||
@ -35,7 +33,6 @@ const IAICanvasResizer = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
doesCanvasNeedScaling,
|
||||
shouldLockToInitialImage,
|
||||
activeTabName,
|
||||
initialCanvasImage,
|
||||
isCanvasInitialized,
|
||||
@ -58,11 +55,7 @@ const IAICanvasResizer = () => {
|
||||
})
|
||||
);
|
||||
|
||||
if (!isCanvasInitialized || shouldLockToInitialImage) {
|
||||
dispatch(resizeAndScaleCanvas());
|
||||
} else {
|
||||
dispatch(resizeCanvas());
|
||||
}
|
||||
dispatch(resizeCanvas());
|
||||
|
||||
dispatch(setDoesCanvasNeedScaling(false));
|
||||
}, 0);
|
||||
@ -72,7 +65,6 @@ const IAICanvasResizer = () => {
|
||||
doesCanvasNeedScaling,
|
||||
activeTabName,
|
||||
isCanvasInitialized,
|
||||
shouldLockToInitialImage,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import Konva from 'konva';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { Box } from 'konva/lib/shapes/Transformer';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Group, Rect, Transformer } from 'react-konva';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import {
|
||||
initialCanvasImageSelector,
|
||||
canvasSelector,
|
||||
shouldLockToInitialImageSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
roundDownToMultiple,
|
||||
roundToMultiple,
|
||||
} from 'common/util/roundDownToMultiple';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
setBoundingBoxCoordinates,
|
||||
setBoundingBoxDimensions,
|
||||
@ -21,14 +19,10 @@ import {
|
||||
setIsTransformingBoundingBox,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { GroupConfig } from 'konva/lib/Group';
|
||||
import { activeTabNameSelector } from 'features/options/optionsSelectors';
|
||||
|
||||
const boundingBoxPreviewSelector = createSelector(
|
||||
shouldLockToInitialImageSelector,
|
||||
canvasSelector,
|
||||
initialCanvasImageSelector,
|
||||
activeTabNameSelector,
|
||||
(shouldLockToInitialImage, canvas, initialCanvasImage, activeTabName) => {
|
||||
(canvas) => {
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
@ -54,14 +48,11 @@ const boundingBoxPreviewSelector = createSelector(
|
||||
isTransformingBoundingBox,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
initialCanvasImage,
|
||||
activeTabName,
|
||||
shouldSnapToGrid,
|
||||
tool,
|
||||
stageCoordinates,
|
||||
boundingBoxStrokeWidth: (isMouseOverBoundingBox ? 8 : 1) / stageScale,
|
||||
hitStrokeWidth: 20 / stageScale,
|
||||
shouldLockToInitialImage,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -88,13 +79,10 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
stageCoordinates,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
initialCanvasImage,
|
||||
activeTabName,
|
||||
shouldSnapToGrid,
|
||||
tool,
|
||||
boundingBoxStrokeWidth,
|
||||
hitStrokeWidth,
|
||||
shouldLockToInitialImage,
|
||||
} = useAppSelector(boundingBoxPreviewSelector);
|
||||
|
||||
const transformerRef = useRef<Konva.Transformer>(null);
|
||||
@ -139,40 +127,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
[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(() => {
|
||||
/**
|
||||
* The Konva Transformer changes the object's anchor point and scale factor,
|
||||
@ -202,15 +156,15 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
|
||||
dispatch(
|
||||
setBoundingBoxCoordinates({
|
||||
x,
|
||||
y,
|
||||
x: shouldSnapToGrid ? roundDownToMultiple(x, 64) : x,
|
||||
y: shouldSnapToGrid ? roundDownToMultiple(y, 64) : y,
|
||||
})
|
||||
);
|
||||
|
||||
// Reset the scale now that the coords/dimensions have been un-scaled
|
||||
rect.scaleX(1);
|
||||
rect.scaleY(1);
|
||||
}, [dispatch]);
|
||||
}, [dispatch, shouldSnapToGrid]);
|
||||
|
||||
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
|
||||
* 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 {
|
||||
x: roundToMultiple(newPos.x, scaledStep),
|
||||
y: roundToMultiple(newPos.y, scaledStep),
|
||||
// Calculate the offset of the grid.
|
||||
const offsetX = oldPos.x % scaledStep;
|
||||
const offsetY = oldPos.y % scaledStep;
|
||||
|
||||
const newCoordinates = {
|
||||
x: roundDownToMultiple(newPos.x, scaledStep) + offsetX,
|
||||
y: roundDownToMultiple(newPos.y, scaledStep) + offsetY,
|
||||
};
|
||||
|
||||
return newCoordinates;
|
||||
},
|
||||
[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 = () => {
|
||||
dispatch(setIsTransformingBoundingBox(true));
|
||||
};
|
||||
@ -310,11 +250,11 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
globalCompositeOperation={'destination-out'}
|
||||
/>
|
||||
<Rect
|
||||
{...(shouldLockToInitialImage ? { dragBoundFunc } : {})}
|
||||
listening={!isDrawing && tool === 'move'}
|
||||
draggable={true}
|
||||
fillEnabled={false}
|
||||
height={boundingBoxDimensions.height}
|
||||
hitStrokeWidth={hitStrokeWidth}
|
||||
listening={!isDrawing && tool === 'move'}
|
||||
onDragEnd={handleEndedModifying}
|
||||
onDragMove={handleOnDragMove}
|
||||
onMouseDown={handleStartedMoving}
|
||||
@ -329,7 +269,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
width={boundingBoxDimensions.width}
|
||||
x={boundingBoxCoordinates.x}
|
||||
y={boundingBoxCoordinates.y}
|
||||
hitStrokeWidth={hitStrokeWidth}
|
||||
/>
|
||||
<Transformer
|
||||
anchorCornerRadius={3}
|
||||
@ -340,7 +279,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
borderDash={[4, 4]}
|
||||
borderEnabled={true}
|
||||
borderStroke={'black'}
|
||||
boundBoxFunc={boundBoxFunc}
|
||||
draggable={false}
|
||||
enabledAnchors={tool === 'move' ? undefined : []}
|
||||
flipEnabled={false}
|
||||
|
@ -4,7 +4,6 @@ import {
|
||||
resizeAndScaleCanvas,
|
||||
resetCanvas,
|
||||
resetCanvasView,
|
||||
setShouldLockToInitialImage,
|
||||
setTool,
|
||||
fitBoundingBoxToStage,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
@ -39,11 +38,10 @@ import {
|
||||
export const selector = createSelector(
|
||||
[canvasSelector, isStagingSelector],
|
||||
(canvas, isStaging) => {
|
||||
const { tool, shouldLockToInitialImage } = canvas;
|
||||
const { tool } = canvas;
|
||||
return {
|
||||
tool,
|
||||
isStaging,
|
||||
shouldLockToInitialImage,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -55,16 +53,7 @@ export const selector = createSelector(
|
||||
|
||||
const IAICanvasOutpaintingControls = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { tool, isStaging, shouldLockToInitialImage } =
|
||||
useAppSelector(selector);
|
||||
|
||||
const handleToggleShouldLockToInitialImage = (
|
||||
e: ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
dispatch(setShouldLockToInitialImage(e.target.checked));
|
||||
dispatch(resizeAndScaleCanvas());
|
||||
dispatch(fitBoundingBoxToStage());
|
||||
};
|
||||
const { tool, isStaging } = useAppSelector(selector);
|
||||
|
||||
return (
|
||||
<div className="inpainting-settings">
|
||||
@ -151,11 +140,6 @@ const IAICanvasOutpaintingControls = () => {
|
||||
onClick={() => dispatch(resetCanvas())}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<IAICheckbox
|
||||
label={'Lock Canvas to Initial Image'}
|
||||
isChecked={shouldLockToInitialImage}
|
||||
onChange={handleToggleShouldLockToInitialImage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -8,9 +8,11 @@ import { MutableRefObject, useCallback } from 'react';
|
||||
import {
|
||||
canvasSelector,
|
||||
initialCanvasImageSelector,
|
||||
shouldLockToInitialImageSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import { setStageCoordinates, setStageScale } from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
setStageCoordinates,
|
||||
setStageScale,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
CANVAS_SCALE_BY,
|
||||
MAX_CANVAS_SCALE,
|
||||
@ -18,13 +20,8 @@ import {
|
||||
} from '../util/constants';
|
||||
|
||||
const selector = createSelector(
|
||||
[
|
||||
activeTabNameSelector,
|
||||
canvasSelector,
|
||||
initialCanvasImageSelector,
|
||||
shouldLockToInitialImageSelector,
|
||||
],
|
||||
(activeTabName, canvas, initialCanvasImage, shouldLockToInitialImage) => {
|
||||
[activeTabNameSelector, canvasSelector, initialCanvasImageSelector],
|
||||
(activeTabName, canvas, initialCanvasImage) => {
|
||||
const {
|
||||
isMoveStageKeyHeld,
|
||||
stageScale,
|
||||
@ -36,7 +33,6 @@ const selector = createSelector(
|
||||
stageScale,
|
||||
activeTabName,
|
||||
initialCanvasImage,
|
||||
shouldLockToInitialImage,
|
||||
stageDimensions,
|
||||
minimumStageScale,
|
||||
};
|
||||
@ -51,7 +47,6 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||
stageScale,
|
||||
activeTabName,
|
||||
initialCanvasImage,
|
||||
shouldLockToInitialImage,
|
||||
stageDimensions,
|
||||
minimumStageScale,
|
||||
} = useAppSelector(selector);
|
||||
@ -83,7 +78,7 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||
|
||||
const newScale = _.clamp(
|
||||
stageScale * CANVAS_SCALE_BY ** delta,
|
||||
shouldLockToInitialImage ? minimumStageScale : MIN_CANVAS_SCALE,
|
||||
MIN_CANVAS_SCALE,
|
||||
MAX_CANVAS_SCALE
|
||||
);
|
||||
|
||||
@ -92,36 +87,10 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||
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(setStageCoordinates(newCoordinates));
|
||||
},
|
||||
[
|
||||
activeTabName,
|
||||
stageRef,
|
||||
isMoveStageKeyHeld,
|
||||
initialCanvasImage,
|
||||
stageScale,
|
||||
shouldLockToInitialImage,
|
||||
minimumStageScale,
|
||||
dispatch,
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
]
|
||||
[stageRef, isMoveStageKeyHeld, initialCanvasImage, stageScale, dispatch]
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -6,9 +6,6 @@ export const canvasSelector = (state: RootState): CanvasState => state.canvas;
|
||||
export const isStagingSelector = (state: RootState): boolean =>
|
||||
state.canvas.layerState.stagingArea.images.length > 0;
|
||||
|
||||
export const shouldLockToInitialImageSelector = (state: RootState): boolean =>
|
||||
state.canvas.shouldLockToInitialImage;
|
||||
|
||||
export const initialCanvasImageSelector = (
|
||||
state: RootState
|
||||
): CanvasImage | undefined =>
|
||||
|
@ -49,7 +49,6 @@ const initialCanvasState: CanvasState = {
|
||||
eraserSize: 50,
|
||||
futureLayerStates: [],
|
||||
inpaintReplace: 0.1,
|
||||
initialCanvasImageClipRect: undefined,
|
||||
isCanvasInitialized: false,
|
||||
isDrawing: false,
|
||||
isMaskEnabled: true,
|
||||
@ -68,7 +67,6 @@ const initialCanvasState: CanvasState = {
|
||||
shouldAutoSave: false,
|
||||
shouldDarkenOutsideBoundingBox: false,
|
||||
shouldLockBoundingBox: false,
|
||||
shouldLockToInitialImage: false,
|
||||
shouldPreserveMaskedArea: false,
|
||||
shouldShowBoundingBox: true,
|
||||
shouldShowBrush: true,
|
||||
@ -298,10 +296,6 @@ export const canvasSlice = createSlice({
|
||||
tool,
|
||||
strokeWidth: newStrokeWidth,
|
||||
points: action.payload,
|
||||
clipRect:
|
||||
state.shouldLockToInitialImage && state.initialCanvasImageClipRect
|
||||
? state.initialCanvasImageClipRect
|
||||
: undefined,
|
||||
...newColor,
|
||||
});
|
||||
|
||||
@ -375,16 +369,10 @@ export const canvasSlice = createSlice({
|
||||
state.layerState.objects.find(isCanvasBaseImage);
|
||||
|
||||
if (!initialCanvasImage) return;
|
||||
const { shouldLockToInitialImage, initialCanvasImageClipRect } = state;
|
||||
|
||||
let { width: imageWidth, height: imageHeight } = initialCanvasImage;
|
||||
const { width: imageWidth, height: imageHeight } = initialCanvasImage;
|
||||
|
||||
if (shouldLockToInitialImage && initialCanvasImageClipRect) {
|
||||
imageWidth = initialCanvasImageClipRect.clipWidth;
|
||||
imageHeight = initialCanvasImageClipRect.clipHeight;
|
||||
}
|
||||
|
||||
const padding = shouldLockToInitialImage ? 1 : 0.95;
|
||||
const padding = 0.95;
|
||||
|
||||
const newScale = calculateScale(
|
||||
containerWidth,
|
||||
@ -395,12 +383,8 @@ export const canvasSlice = createSlice({
|
||||
);
|
||||
|
||||
const newDimensions = {
|
||||
width: shouldLockToInitialImage
|
||||
? Math.floor(imageWidth * newScale)
|
||||
: Math.floor(containerWidth),
|
||||
height: shouldLockToInitialImage
|
||||
? Math.floor(imageHeight * newScale)
|
||||
: Math.floor(containerHeight),
|
||||
width: Math.floor(containerWidth),
|
||||
height: Math.floor(containerHeight),
|
||||
};
|
||||
|
||||
const newCoordinates = calculateCoordinates(
|
||||
@ -416,7 +400,7 @@ export const canvasSlice = createSlice({
|
||||
if (!_.isEqual(state.stageDimensions, newDimensions)) {
|
||||
state.minimumStageScale = newScale;
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.stageCoordinates = floorCoordinates(newCoordinates);
|
||||
state.stageDimensions = newDimensions;
|
||||
}
|
||||
|
||||
@ -440,23 +424,16 @@ export const canvasSlice = createSlice({
|
||||
const { contentRect } = action.payload;
|
||||
|
||||
const baseCanvasImage = state.layerState.objects.find(isCanvasBaseImage);
|
||||
const { shouldLockToInitialImage, initialCanvasImageClipRect } = state;
|
||||
|
||||
if (!baseCanvasImage) return;
|
||||
|
||||
const {
|
||||
stageDimensions: { width: stageWidth, height: stageHeight },
|
||||
} = state;
|
||||
|
||||
let { x, y, width, height } = contentRect;
|
||||
const { x, y, width, height } = contentRect;
|
||||
|
||||
if (shouldLockToInitialImage && initialCanvasImageClipRect) {
|
||||
x = initialCanvasImageClipRect.clipX;
|
||||
y = initialCanvasImageClipRect.clipY;
|
||||
width = initialCanvasImageClipRect.clipWidth;
|
||||
height = initialCanvasImageClipRect.clipHeight;
|
||||
}
|
||||
|
||||
const padding = shouldLockToInitialImage ? 1 : 0.95;
|
||||
const padding = 0.95;
|
||||
const newScale = calculateScale(
|
||||
stageWidth,
|
||||
stageHeight,
|
||||
@ -515,39 +492,36 @@ export const canvasSlice = createSlice({
|
||||
|
||||
state.futureLayerStates = [];
|
||||
},
|
||||
setShouldLockToInitialImage: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldLockToInitialImage = action.payload;
|
||||
},
|
||||
fitBoundingBoxToStage: (state) => {
|
||||
const { boundingBoxDimensions, boundingBoxCoordinates, stageDimensions } =
|
||||
state;
|
||||
const {
|
||||
boundingBoxDimensions,
|
||||
boundingBoxCoordinates,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
} = state;
|
||||
const scaledStageWidth = stageDimensions.width / stageScale;
|
||||
const scaledStageHeight = stageDimensions.height / stageScale;
|
||||
|
||||
if (
|
||||
boundingBoxCoordinates.x < 0 ||
|
||||
boundingBoxCoordinates.x + boundingBoxDimensions.width >
|
||||
stageDimensions.width ||
|
||||
scaledStageWidth ||
|
||||
boundingBoxCoordinates.y < 0 ||
|
||||
boundingBoxCoordinates.y + boundingBoxDimensions.height >
|
||||
stageDimensions.height
|
||||
scaledStageHeight
|
||||
) {
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
_.clamp(stageDimensions.width, 64, 512),
|
||||
64
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
_.clamp(stageDimensions.height, 64, 512),
|
||||
64
|
||||
),
|
||||
width: roundDownToMultiple(_.clamp(scaledStageWidth, 64, 512), 64),
|
||||
height: roundDownToMultiple(_.clamp(scaledStageHeight, 64, 512), 64),
|
||||
};
|
||||
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
stageDimensions.width / 2 - newBoundingBoxDimensions.width / 2,
|
||||
scaledStageWidth / 2 - newBoundingBoxDimensions.width / 2,
|
||||
64
|
||||
),
|
||||
y: roundToMultiple(
|
||||
stageDimensions.height / 2 - newBoundingBoxDimensions.height / 2,
|
||||
scaledStageHeight / 2 - newBoundingBoxDimensions.height / 2,
|
||||
64
|
||||
),
|
||||
};
|
||||
@ -611,7 +585,6 @@ export const {
|
||||
prevStagingAreaImage,
|
||||
commitStagingAreaImage,
|
||||
discardStagedImages,
|
||||
setShouldLockToInitialImage,
|
||||
resizeAndScaleCanvas,
|
||||
resizeCanvas,
|
||||
resetCanvasView,
|
||||
|
@ -8,13 +8,6 @@ export type CanvasDrawingTool = 'brush' | 'eraser';
|
||||
|
||||
export type CanvasTool = CanvasDrawingTool | 'move';
|
||||
|
||||
export type ClipRect = {
|
||||
clipX: number;
|
||||
clipY: number;
|
||||
clipWidth: number;
|
||||
clipHeight: number;
|
||||
};
|
||||
|
||||
export type Dimensions = {
|
||||
width: number;
|
||||
height: number;
|
||||
@ -25,7 +18,6 @@ export type CanvasAnyLine = {
|
||||
tool: CanvasDrawingTool;
|
||||
strokeWidth: number;
|
||||
points: number[];
|
||||
clipRect: ClipRect | undefined;
|
||||
};
|
||||
|
||||
export type CanvasImage = {
|
||||
@ -86,7 +78,6 @@ export interface CanvasState {
|
||||
doesCanvasNeedScaling: boolean;
|
||||
eraserSize: number;
|
||||
futureLayerStates: CanvasLayerState[];
|
||||
initialCanvasImageClipRect?: ClipRect;
|
||||
inpaintReplace: number;
|
||||
intermediateImage?: InvokeAI.Image;
|
||||
isCanvasInitialized: boolean;
|
||||
@ -107,7 +98,6 @@ export interface CanvasState {
|
||||
shouldAutoSave: boolean;
|
||||
shouldDarkenOutsideBoundingBox: boolean;
|
||||
shouldLockBoundingBox: boolean;
|
||||
shouldLockToInitialImage: boolean;
|
||||
shouldPreserveMaskedArea: boolean;
|
||||
shouldShowBoundingBox: boolean;
|
||||
shouldShowBrush: boolean;
|
||||
|
@ -47,13 +47,6 @@ export const setInitialCanvasImage = (
|
||||
};
|
||||
state.futureLayerStates = [];
|
||||
|
||||
state.initialCanvasImageClipRect = {
|
||||
clipX: 0,
|
||||
clipY: 0,
|
||||
clipWidth: image.width,
|
||||
clipHeight: image.height,
|
||||
};
|
||||
|
||||
state.isCanvasInitialized = false;
|
||||
state.doesCanvasNeedScaling = true;
|
||||
};
|
||||
|
@ -1,11 +1,6 @@
|
||||
import Konva from 'konva';
|
||||
import { IRect } from 'konva/lib/types';
|
||||
|
||||
const layerToDataURL = (
|
||||
layer: Konva.Layer,
|
||||
stageScale: number,
|
||||
boundingBox?: IRect
|
||||
) => {
|
||||
const layerToDataURL = (layer: Konva.Layer, stageScale: number) => {
|
||||
const tempScale = layer.scale();
|
||||
|
||||
const relativeClientRect = layer.getClientRect({
|
||||
@ -20,21 +15,12 @@ const layerToDataURL = (
|
||||
|
||||
const { x, y, width, height } = layer.getClientRect();
|
||||
|
||||
const scaledBoundingBox = boundingBox
|
||||
? {
|
||||
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),
|
||||
y: Math.round(y),
|
||||
width: Math.round(width),
|
||||
height: Math.round(height),
|
||||
};
|
||||
|
||||
const dataURL = layer.toDataURL(scaledBoundingBox);
|
||||
const dataURL = layer.toDataURL({
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
width: Math.round(width),
|
||||
height: Math.round(height),
|
||||
});
|
||||
|
||||
// Unscale the canvas
|
||||
layer.scale(tempScale);
|
||||
|
@ -22,24 +22,12 @@ export const mergeAndUploadCanvas = createAsyncThunk(
|
||||
const state = getState() as RootState;
|
||||
|
||||
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;
|
||||
|
||||
const { dataURL, boundingBox: originalBoundingBox } = layerToDataURL(
|
||||
canvasImageLayerRef.current,
|
||||
stageScale,
|
||||
boundingBox
|
||||
stageScale
|
||||
);
|
||||
|
||||
if (!dataURL) return;
|
||||
|
@ -38,7 +38,6 @@ import {
|
||||
import {
|
||||
setDoesCanvasNeedScaling,
|
||||
setInitialCanvasImage,
|
||||
setShouldLockToInitialImage,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { GalleryState } from './gallerySlice';
|
||||
import { activeTabNameSelector } from 'features/options/optionsSelectors';
|
||||
@ -317,31 +316,10 @@ const CurrentImageButtons = () => {
|
||||
const handleClickShowImageDetails = () =>
|
||||
dispatch(setShouldShowImageDetails(!shouldShowImageDetails));
|
||||
|
||||
const handleSendToInpainting = () => {
|
||||
const handleSendToCanvas = () => {
|
||||
if (!currentImage) return;
|
||||
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(setDoesCanvasNeedScaling(true));
|
||||
|
||||
@ -393,17 +371,10 @@ const CurrentImageButtons = () => {
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size={'sm'}
|
||||
onClick={handleSendToInpainting}
|
||||
onClick={handleSendToCanvas}
|
||||
leftIcon={<FaShare />}
|
||||
>
|
||||
Send to Inpainting
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size={'sm'}
|
||||
onClick={handleSendToOutpainting}
|
||||
leftIcon={<FaShare />}
|
||||
>
|
||||
Send to Outpainting
|
||||
Send to Unified Canvas
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size={'sm'}
|
||||
|
@ -25,7 +25,6 @@ import * as ContextMenu from '@radix-ui/react-context-menu';
|
||||
import {
|
||||
setDoesCanvasNeedScaling,
|
||||
setInitialCanvasImage,
|
||||
setShouldLockToInitialImage,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { hoverableImageSelector } from './gallerySliceSelectors';
|
||||
|
||||
@ -96,10 +95,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSendToInpainting = () => {
|
||||
const handleSendToCanvas = () => {
|
||||
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
|
||||
|
||||
dispatch(setShouldLockToInitialImage(true));
|
||||
dispatch(setInitialCanvasImage(image));
|
||||
dispatch(setDoesCanvasNeedScaling(true));
|
||||
|
||||
@ -108,26 +106,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
}
|
||||
|
||||
toast({
|
||||
title: 'Sent to Inpainting',
|
||||
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',
|
||||
title: 'Sent to Unified Canvas',
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
@ -257,11 +236,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
<ContextMenu.Item onClickCapture={handleSendToImageToImage}>
|
||||
Send to Image To Image
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onClickCapture={handleSendToInpainting}>
|
||||
Send to Inpainting
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onClickCapture={handleSendToOutpainting}>
|
||||
Send to Outpainting
|
||||
<ContextMenu.Item onClickCapture={handleSendToCanvas}>
|
||||
Send to Unified Canvas
|
||||
</ContextMenu.Item>
|
||||
<DeleteImageModal image={image}>
|
||||
<ContextMenu.Item data-warning>Delete Image</ContextMenu.Item>
|
||||
|
Loading…
Reference in New Issue
Block a user