mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): clip lines to bbox
This commit is contained in:
parent
4dcab357a0
commit
fc5467150e
@ -116,7 +116,7 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
useLayoutEffect(() => {
|
||||
$toolState.set(tool);
|
||||
$selectedEntity.set(selectedEntity);
|
||||
$bbox.set({ x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height });
|
||||
$bbox.set(bbox);
|
||||
$currentFill.set(currentFill);
|
||||
$document.set(document);
|
||||
}, [selectedEntity, tool, bbox, currentFill, document]);
|
||||
@ -255,6 +255,7 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
|
||||
getSpaceKey: $spaceKey.get,
|
||||
setStageAttrs: $stageAttrs.set,
|
||||
getDocument: $document.get,
|
||||
getBbox: $bbox.get,
|
||||
onBrushLineAdded,
|
||||
onEraserLineAdded,
|
||||
onPointAddedToLine,
|
||||
|
@ -47,6 +47,7 @@ type Arg = {
|
||||
getSelectedEntity: () => CanvasEntity | null;
|
||||
getSpaceKey: () => boolean;
|
||||
getDocument: () => CanvasV2State['document'];
|
||||
getBbox: () => CanvasV2State['bbox'];
|
||||
onBrushLineAdded: (arg: BrushLineAddedArg, entityType: CanvasEntity['type']) => void;
|
||||
onEraserLineAdded: (arg: EraserLineAddedArg, entityType: CanvasEntity['type']) => void;
|
||||
onPointAddedToLine: (arg: PointAddedToLineArg, entityType: CanvasEntity['type']) => void;
|
||||
@ -147,6 +148,7 @@ export const setStageEventHandlers = ({
|
||||
getSelectedEntity,
|
||||
getSpaceKey,
|
||||
getDocument,
|
||||
getBbox,
|
||||
onBrushLineAdded,
|
||||
onEraserLineAdded,
|
||||
onPointAddedToLine,
|
||||
@ -190,6 +192,7 @@ export const setStageEventHandlers = ({
|
||||
setLastMouseDownPos(pos);
|
||||
|
||||
if (toolState.selected === 'brush') {
|
||||
const bbox = getBbox();
|
||||
if (e.evt.shiftKey) {
|
||||
const lastAddedPoint = getLastAddedPoint();
|
||||
// Create a straight line if holding shift
|
||||
@ -205,6 +208,12 @@ export const setStageEventHandlers = ({
|
||||
],
|
||||
color: getCurrentFill(),
|
||||
width: toolState.brush.width,
|
||||
clip: {
|
||||
x: bbox.x,
|
||||
y: bbox.y,
|
||||
width: bbox.width,
|
||||
height: bbox.height,
|
||||
},
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
@ -221,6 +230,12 @@ export const setStageEventHandlers = ({
|
||||
],
|
||||
color: getCurrentFill(),
|
||||
width: toolState.brush.width,
|
||||
clip: {
|
||||
x: bbox.x,
|
||||
y: bbox.y,
|
||||
width: bbox.width,
|
||||
height: bbox.height,
|
||||
},
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
@ -229,6 +244,7 @@ export const setStageEventHandlers = ({
|
||||
}
|
||||
|
||||
if (toolState.selected === 'eraser') {
|
||||
const bbox = getBbox();
|
||||
if (e.evt.shiftKey) {
|
||||
// Create a straight line if holding shift
|
||||
const lastAddedPoint = getLastAddedPoint();
|
||||
@ -243,6 +259,12 @@ export const setStageEventHandlers = ({
|
||||
pos.y - selectedEntity.y,
|
||||
],
|
||||
width: toolState.eraser.width,
|
||||
clip: {
|
||||
x: bbox.x,
|
||||
y: bbox.y,
|
||||
width: bbox.width,
|
||||
height: bbox.height,
|
||||
},
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
@ -258,6 +280,12 @@ export const setStageEventHandlers = ({
|
||||
pos.y - selectedEntity.y,
|
||||
],
|
||||
width: toolState.eraser.width,
|
||||
clip: {
|
||||
x: bbox.x,
|
||||
y: bbox.y,
|
||||
width: bbox.width,
|
||||
height: bbox.height,
|
||||
},
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
@ -348,6 +376,7 @@ export const setStageEventHandlers = ({
|
||||
// Continue the last line
|
||||
maybeAddNextPoint(selectedEntity, pos, getToolState, getLastAddedPoint, setLastAddedPoint, onPointAddedToLine);
|
||||
} else {
|
||||
const bbox = getBbox();
|
||||
// Start a new line
|
||||
onBrushLineAdded(
|
||||
{
|
||||
@ -360,6 +389,12 @@ export const setStageEventHandlers = ({
|
||||
],
|
||||
width: toolState.brush.width,
|
||||
color: getCurrentFill(),
|
||||
clip: {
|
||||
x: bbox.x,
|
||||
y: bbox.y,
|
||||
width: bbox.width,
|
||||
height: bbox.height,
|
||||
},
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
@ -373,6 +408,7 @@ export const setStageEventHandlers = ({
|
||||
// Continue the last line
|
||||
maybeAddNextPoint(selectedEntity, pos, getToolState, getLastAddedPoint, setLastAddedPoint, onPointAddedToLine);
|
||||
} else {
|
||||
const bbox = getBbox();
|
||||
// Start a new line
|
||||
onEraserLineAdded(
|
||||
{
|
||||
@ -384,6 +420,12 @@ export const setStageEventHandlers = ({
|
||||
pos.y - selectedEntity.y,
|
||||
],
|
||||
width: toolState.eraser.width,
|
||||
clip: {
|
||||
x: bbox.x,
|
||||
y: bbox.y,
|
||||
width: bbox.width,
|
||||
height: bbox.height,
|
||||
},
|
||||
},
|
||||
selectedEntity.type
|
||||
);
|
||||
|
@ -23,10 +23,19 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
* @param layerObjectGroup The konva layer's object group to add the line to
|
||||
* @param name The konva name for the line
|
||||
*/
|
||||
export const createBrushLine = (brushLine: BrushLine, layerObjectGroup: Konva.Group, name: string): Konva.Line => {
|
||||
const konvaLine = new Konva.Line({
|
||||
export const getBrushLine = (brushLine: BrushLine, layerObjectGroup: Konva.Group, name: string): Konva.Line => {
|
||||
let konvaLineGroup = layerObjectGroup.findOne<Konva.Group>(`#${brushLine.id}_group`);
|
||||
let konvaLine = konvaLineGroup?.findOne<Konva.Line>(`#${brushLine.id}`);
|
||||
if (konvaLine) {
|
||||
return konvaLine;
|
||||
}
|
||||
|
||||
konvaLineGroup = new Konva.Group({
|
||||
id: `${brushLine.id}_group`,
|
||||
// clip: brushLine.clip,
|
||||
});
|
||||
konvaLine = new Konva.Line({
|
||||
id: brushLine.id,
|
||||
key: brushLine.id,
|
||||
name,
|
||||
strokeWidth: brushLine.strokeWidth,
|
||||
tension: 0,
|
||||
@ -37,7 +46,8 @@ export const createBrushLine = (brushLine: BrushLine, layerObjectGroup: Konva.Gr
|
||||
listening: false,
|
||||
stroke: rgbaColorToString(brushLine.color),
|
||||
});
|
||||
layerObjectGroup.add(konvaLine);
|
||||
konvaLineGroup.add(konvaLine);
|
||||
layerObjectGroup.add(konvaLineGroup);
|
||||
return konvaLine;
|
||||
};
|
||||
|
||||
@ -47,7 +57,7 @@ export const createBrushLine = (brushLine: BrushLine, layerObjectGroup: Konva.Gr
|
||||
* @param layerObjectGroup The konva layer's object group to add the line to
|
||||
* @param name The konva name for the line
|
||||
*/
|
||||
export const createEraserLine = (eraserLine: EraserLine, layerObjectGroup: Konva.Group, name: string): Konva.Line => {
|
||||
export const getEraserLine = (eraserLine: EraserLine, layerObjectGroup: Konva.Group, name: string): Konva.Line => {
|
||||
const konvaLine = new Konva.Line({
|
||||
id: eraserLine.id,
|
||||
key: eraserLine.id,
|
||||
@ -60,6 +70,7 @@ export const createEraserLine = (eraserLine: EraserLine, layerObjectGroup: Konva
|
||||
globalCompositeOperation: 'destination-out',
|
||||
listening: false,
|
||||
stroke: rgbaColorToString(DEFAULT_RGBA_COLOR),
|
||||
clip: eraserLine.clip,
|
||||
});
|
||||
layerObjectGroup.add(konvaLine);
|
||||
return konvaLine;
|
||||
|
@ -253,7 +253,7 @@ export const renderBboxPreview = (
|
||||
stage: Konva.Stage,
|
||||
bbox: IRect,
|
||||
tool: Tool,
|
||||
getBbox: () => IRect,
|
||||
getBbox: () => CanvasV2State['bbox'],
|
||||
onBboxTransformed: (bbox: IRect) => void,
|
||||
getShiftKey: () => boolean,
|
||||
getCtrlKey: () => boolean,
|
||||
|
@ -7,11 +7,11 @@ import {
|
||||
RASTER_LAYER_RECT_SHAPE_NAME,
|
||||
} from 'features/controlLayers/konva/naming';
|
||||
import {
|
||||
createBrushLine,
|
||||
createEraserLine,
|
||||
createImageObjectGroup,
|
||||
createObjectGroup,
|
||||
createRectShape,
|
||||
getBrushLine,
|
||||
getEraserLine,
|
||||
} from 'features/controlLayers/konva/renderers/objects';
|
||||
import { mapId, selectRasterObjects } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasEntity, LayerEntity, PosChangedArg, Tool } from 'features/controlLayers/store/types';
|
||||
@ -92,9 +92,7 @@ export const renderRasterLayer = async (
|
||||
|
||||
for (const obj of layerState.objects) {
|
||||
if (obj.type === 'brush_line') {
|
||||
const konvaBrushLine =
|
||||
konvaObjectGroup.findOne<Konva.Line>(`#${obj.id}`) ??
|
||||
createBrushLine(obj, konvaObjectGroup, RASTER_LAYER_BRUSH_LINE_NAME);
|
||||
const konvaBrushLine = getBrushLine(obj, konvaObjectGroup, RASTER_LAYER_BRUSH_LINE_NAME);
|
||||
// Only update the points if they have changed.
|
||||
if (konvaBrushLine.points().length !== obj.points.length) {
|
||||
konvaBrushLine.points(obj.points);
|
||||
@ -102,7 +100,7 @@ export const renderRasterLayer = async (
|
||||
} else if (obj.type === 'eraser_line') {
|
||||
const konvaEraserLine =
|
||||
konvaObjectGroup.findOne<Konva.Line>(`#${obj.id}`) ??
|
||||
createEraserLine(obj, konvaObjectGroup, RASTER_LAYER_ERASER_LINE_NAME);
|
||||
getEraserLine(obj, konvaObjectGroup, RASTER_LAYER_ERASER_LINE_NAME);
|
||||
// Only update the points if they have changed.
|
||||
if (konvaEraserLine.points().length !== obj.points.length) {
|
||||
konvaEraserLine.points(obj.points);
|
||||
|
@ -12,10 +12,10 @@ import {
|
||||
import { getLayerBboxFast } from 'features/controlLayers/konva/renderers/bbox';
|
||||
import {
|
||||
createBboxRect,
|
||||
createBrushLine,
|
||||
createEraserLine,
|
||||
createObjectGroup,
|
||||
createRectShape,
|
||||
getBrushLine,
|
||||
getEraserLine,
|
||||
} from 'features/controlLayers/konva/renderers/objects';
|
||||
import { mapId, selectVectorMaskObjects } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasEntity, PosChangedArg, RegionEntity, Tool } from 'features/controlLayers/store/types';
|
||||
@ -117,7 +117,7 @@ export const renderRGLayer = (
|
||||
for (const obj of rg.objects) {
|
||||
if (obj.type === 'brush_line') {
|
||||
const konvaBrushLine =
|
||||
stage.findOne<Konva.Line>(`#${obj.id}`) ?? createBrushLine(obj, konvaObjectGroup, RG_LAYER_BRUSH_LINE_NAME);
|
||||
stage.findOne<Konva.Line>(`#${obj.id}`) ?? getBrushLine(obj, konvaObjectGroup, RG_LAYER_BRUSH_LINE_NAME);
|
||||
|
||||
// Only update the points if they have changed. The point values are never mutated, they are only added to the
|
||||
// array, so checking the length is sufficient to determine if we need to re-cache.
|
||||
@ -132,7 +132,7 @@ export const renderRGLayer = (
|
||||
}
|
||||
} else if (obj.type === 'eraser_line') {
|
||||
const konvaEraserLine =
|
||||
stage.findOne<Konva.Line>(`#${obj.id}`) ?? createEraserLine(obj, konvaObjectGroup, RG_LAYER_ERASER_LINE_NAME);
|
||||
stage.findOne<Konva.Line>(`#${obj.id}`) ?? getEraserLine(obj, konvaObjectGroup, RG_LAYER_ERASER_LINE_NAME);
|
||||
|
||||
// Only update the points if they have changed. The point values are never mutated, they are only added to the
|
||||
// array, so checking the length is sufficient to determine if we need to re-cache.
|
||||
|
@ -15,7 +15,7 @@ import { settingsReducers } from 'features/controlLayers/store/settingsReducers'
|
||||
import { toolReducers } from 'features/controlLayers/store/toolReducers';
|
||||
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import type { Vector2d } from 'konva/lib/types';
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
import type { CanvasEntity, CanvasEntityIdentifier, CanvasV2State, RgbaColor, StageAttrs } from './types';
|
||||
@ -327,7 +327,7 @@ export const $stageAttrs = atom<StageAttrs>({
|
||||
export const $toolState = atom<CanvasV2State['tool']>(deepClone(initialState.tool));
|
||||
export const $currentFill = atom<RgbaColor>(DEFAULT_RGBA_COLOR);
|
||||
export const $selectedEntity = atom<CanvasEntity | null>(null);
|
||||
export const $bbox = atom<IRect>({ x: 0, y: 0, width: 0, height: 0 });
|
||||
export const $bbox = atom<CanvasV2State['bbox']>(deepClone(initialState.bbox));
|
||||
export const $document = atom<CanvasV2State['document']>(deepClone(initialState.document));
|
||||
|
||||
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
|
||||
|
@ -136,7 +136,7 @@ export const layersReducers = {
|
||||
},
|
||||
layerBrushLineAdded: {
|
||||
reducer: (state, action: PayloadAction<BrushLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, color, width } = action.payload;
|
||||
const { id, points, lineId, color, width, clip } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
@ -148,6 +148,7 @@ export const layersReducers = {
|
||||
points,
|
||||
strokeWidth: width,
|
||||
color,
|
||||
clip,
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
@ -157,7 +158,7 @@ export const layersReducers = {
|
||||
},
|
||||
layerEraserLineAdded: {
|
||||
reducer: (state, action: PayloadAction<EraserLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, width } = action.payload;
|
||||
const { id, points, lineId, width, clip } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
@ -168,6 +169,7 @@ export const layersReducers = {
|
||||
type: 'eraser_line',
|
||||
points,
|
||||
strokeWidth: width,
|
||||
clip,
|
||||
});
|
||||
layer.bboxNeedsUpdate = true;
|
||||
},
|
||||
|
@ -304,7 +304,7 @@ export const regionsReducers = {
|
||||
},
|
||||
rgBrushLineAdded: {
|
||||
reducer: (state, action: PayloadAction<BrushLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, color, width } = action.payload;
|
||||
const { id, points, lineId, color, width, clip } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
@ -315,6 +315,7 @@ export const regionsReducers = {
|
||||
points,
|
||||
strokeWidth: width,
|
||||
color,
|
||||
clip,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
@ -325,7 +326,7 @@ export const regionsReducers = {
|
||||
},
|
||||
rgEraserLineAdded: {
|
||||
reducer: (state, action: PayloadAction<EraserLineAddedArg & { lineId: string }>) => {
|
||||
const { id, points, lineId, width } = action.payload;
|
||||
const { id, points, lineId, width, clip } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
@ -335,6 +336,7 @@ export const regionsReducers = {
|
||||
type: 'eraser_line',
|
||||
points,
|
||||
strokeWidth: width,
|
||||
clip,
|
||||
});
|
||||
rg.bboxNeedsUpdate = true;
|
||||
rg.imageCache = null;
|
||||
|
@ -498,12 +498,21 @@ export const DEFAULT_RGBA_COLOR: RgbaColor = { r: 255, g: 255, b: 255, a: 1 };
|
||||
|
||||
const zOpacity = z.number().gte(0).lte(1);
|
||||
|
||||
const zRect = z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
width: z.number().min(1),
|
||||
height: z.number().min(1),
|
||||
});
|
||||
export type Rect = z.infer<typeof zRect>;
|
||||
|
||||
const zBrushLine = z.object({
|
||||
id: zId,
|
||||
type: z.literal('brush_line'),
|
||||
strokeWidth: z.number().min(1),
|
||||
points: zPoints,
|
||||
color: zRgbaColor,
|
||||
clip: zRect.nullable(),
|
||||
});
|
||||
export type BrushLine = z.infer<typeof zBrushLine>;
|
||||
|
||||
@ -512,6 +521,7 @@ const zEraserline = z.object({
|
||||
type: z.literal('eraser_line'),
|
||||
strokeWidth: z.number().min(1),
|
||||
points: zPoints,
|
||||
clip: zRect.nullable(),
|
||||
});
|
||||
export type EraserLine = z.infer<typeof zEraserline>;
|
||||
|
||||
@ -566,13 +576,6 @@ const zLayerObject = z.discriminatedUnion('type', [
|
||||
]);
|
||||
export type LayerObject = z.infer<typeof zLayerObject>;
|
||||
|
||||
const zRect = z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
width: z.number().min(1),
|
||||
height: z.number().min(1),
|
||||
});
|
||||
|
||||
export const zLayerEntity = z.object({
|
||||
id: zId,
|
||||
type: z.literal('layer'),
|
||||
@ -614,12 +617,14 @@ const zMaskObject = z
|
||||
...rest,
|
||||
type: 'brush_line',
|
||||
color: { r: 255, g: 255, b: 255, a: 1 },
|
||||
clip: null,
|
||||
};
|
||||
return asBrushline;
|
||||
} else if (tool === 'eraser') {
|
||||
const asEraserLine: EraserLine = {
|
||||
...rest,
|
||||
type: 'eraser_line',
|
||||
clip: null,
|
||||
};
|
||||
return asEraserLine;
|
||||
}
|
||||
@ -881,6 +886,7 @@ export type EraserLineAddedArg = {
|
||||
id: string;
|
||||
points: [number, number, number, number];
|
||||
width: number;
|
||||
clip: Rect | null;
|
||||
};
|
||||
export type BrushLineAddedArg = EraserLineAddedArg & { color: RgbaColor };
|
||||
export type PointAddedToLineArg = { id: string; point: [number, number] };
|
||||
|
Loading…
Reference in New Issue
Block a user