mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Clips lines drawn while canvas locked
When drawing with the locked canvas, if a brush stroke gets too close to the edge of the canvas and its stroke would extend past the edge of the canvas, the edge of that stroke will be seen after unlocking the canvas. This could cause a problem if you unlock the canvas and now have a bunch of strokes just outside the init image area, which are far back in undo history and you cannot easily erase. With this change, lines drawn while the canvas is locked get clipped to the initial image bbox, fixing this issue. Additionally, the merge and save to gallery functions have been updated to respect the initial image bbox so they function how you'd expect.
This commit is contained in:
parent
3994c28b77
commit
425d3bc95d
@ -10,8 +10,9 @@ import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
const selector = createSelector(
|
||||
[canvasSelector],
|
||||
(canvas) => {
|
||||
const { objects } = canvas.layerState;
|
||||
|
||||
const {
|
||||
layerState: { objects },
|
||||
} = canvas;
|
||||
return {
|
||||
objects,
|
||||
};
|
||||
@ -37,20 +38,22 @@ const IAICanvasObjectRenderer = () => {
|
||||
);
|
||||
} else if (isCanvasBaseLine(obj)) {
|
||||
return (
|
||||
<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 {...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>
|
||||
);
|
||||
}
|
||||
})}
|
||||
|
@ -48,6 +48,7 @@ const initialCanvasState: CanvasState = {
|
||||
eraserSize: 50,
|
||||
futureLayerStates: [],
|
||||
inpaintReplace: 0.1,
|
||||
initialCanvasImageClipRect: undefined,
|
||||
isCanvasInitialized: false,
|
||||
isDrawing: false,
|
||||
isMaskEnabled: true,
|
||||
@ -296,6 +297,10 @@ export const canvasSlice = createSlice({
|
||||
tool,
|
||||
strokeWidth: newStrokeWidth,
|
||||
points: action.payload,
|
||||
clipRect:
|
||||
state.shouldLockToInitialImage && state.initialCanvasImageClipRect
|
||||
? state.initialCanvasImageClipRect
|
||||
: undefined,
|
||||
...newColor,
|
||||
});
|
||||
|
||||
@ -369,10 +374,14 @@ export const canvasSlice = createSlice({
|
||||
state.layerState.objects.find(isCanvasBaseImage);
|
||||
|
||||
if (!initialCanvasImage) return;
|
||||
const { shouldLockToInitialImage, initialCanvasImageClipRect } = state;
|
||||
|
||||
const { width: imageWidth, height: imageHeight } = initialCanvasImage;
|
||||
let { width: imageWidth, height: imageHeight } = initialCanvasImage;
|
||||
|
||||
const { shouldLockToInitialImage } = state;
|
||||
if (shouldLockToInitialImage && initialCanvasImageClipRect) {
|
||||
imageWidth = initialCanvasImageClipRect.clipWidth;
|
||||
imageHeight = initialCanvasImageClipRect.clipHeight;
|
||||
}
|
||||
|
||||
const padding = shouldLockToInitialImage ? 1 : 0.95;
|
||||
|
||||
@ -403,11 +412,13 @@ export const canvasSlice = createSlice({
|
||||
newScale
|
||||
);
|
||||
|
||||
state.stageScale = newScale;
|
||||
state.minimumStageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
if (!_.isEqual(state.stageDimensions, newDimensions)) {
|
||||
state.minimumStageScale = newScale;
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.stageDimensions = newDimensions;
|
||||
}
|
||||
|
||||
state.stageDimensions = newDimensions;
|
||||
state.isCanvasInitialized = true;
|
||||
},
|
||||
resizeCanvas: (state) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as InvokeAI from 'app/invokeai';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { RgbaColor } from 'react-colorful';
|
||||
|
||||
export type CanvasLayer = 'base' | 'mask';
|
||||
@ -8,6 +8,13 @@ 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;
|
||||
@ -18,6 +25,7 @@ export type CanvasAnyLine = {
|
||||
tool: CanvasDrawingTool;
|
||||
strokeWidth: number;
|
||||
points: number[];
|
||||
clipRect: ClipRect | undefined;
|
||||
};
|
||||
|
||||
export type CanvasImage = {
|
||||
@ -78,6 +86,7 @@ export interface CanvasState {
|
||||
doesCanvasNeedScaling: boolean;
|
||||
eraserSize: number;
|
||||
futureLayerStates: CanvasLayerState[];
|
||||
initialCanvasImageClipRect?: ClipRect;
|
||||
inpaintReplace: number;
|
||||
intermediateImage?: InvokeAI.Image;
|
||||
isCanvasInitialized: boolean;
|
||||
|
@ -28,7 +28,6 @@ export const setInitialCanvasImage = (
|
||||
};
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
state.pastLayerStates.push(state.layerState);
|
||||
@ -48,6 +47,13 @@ export const setInitialCanvasImage = (
|
||||
};
|
||||
state.futureLayerStates = [];
|
||||
|
||||
state.initialCanvasImageClipRect = {
|
||||
clipX: 0,
|
||||
clipY: 0,
|
||||
clipWidth: image.width,
|
||||
clipHeight: image.height,
|
||||
};
|
||||
|
||||
state.isCanvasInitialized = false;
|
||||
state.doesCanvasNeedScaling = true;
|
||||
};
|
||||
|
@ -1,6 +1,11 @@
|
||||
import Konva from 'konva';
|
||||
import { IRect } from 'konva/lib/types';
|
||||
|
||||
const layerToDataURL = (layer: Konva.Layer, stageScale: number) => {
|
||||
const layerToDataURL = (
|
||||
layer: Konva.Layer,
|
||||
stageScale: number,
|
||||
boundingBox?: IRect
|
||||
) => {
|
||||
const tempScale = layer.scale();
|
||||
|
||||
const relativeClientRect = layer.getClientRect({
|
||||
@ -15,12 +20,21 @@ const layerToDataURL = (layer: Konva.Layer, stageScale: number) => {
|
||||
|
||||
const { x, y, width, height } = layer.getClientRect();
|
||||
|
||||
const dataURL = layer.toDataURL({
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
width: Math.round(width),
|
||||
height: Math.round(height),
|
||||
});
|
||||
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);
|
||||
|
||||
// Unscale the canvas
|
||||
layer.scale(tempScale);
|
||||
|
@ -22,12 +22,24 @@ 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
|
||||
stageScale,
|
||||
boundingBox
|
||||
);
|
||||
|
||||
if (!dataURL) return;
|
||||
|
Loading…
Reference in New Issue
Block a user