Skip to content

Commit d4bd43b

Browse files
Revert "Fix bugs. Extract into transform utilities"
This reverts commit f9a49e2.
1 parent 312e21a commit d4bd43b

File tree

6 files changed

+133
-687
lines changed

6 files changed

+133
-687
lines changed

invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskBboxAdjuster.tsx

Lines changed: 68 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
33
import { bboxChangedFromCanvas } from 'features/controlLayers/store/canvasSlice';
44
import { selectMaskBlur } from 'features/controlLayers/store/paramsSlice';
55
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
6-
import type {
7-
Rect,
8-
CanvasBrushLineState,
9-
CanvasBrushLineWithPressureState,
10-
CanvasEraserLineState,
11-
CanvasEraserLineWithPressureState,
12-
CanvasRectState,
13-
CanvasImageState,
14-
} from 'features/controlLayers/store/types';
15-
import { transformMaskObjectsRelativeToBbox, calculateMaskBoundsFromBitmap } from 'features/controlLayers/util/maskObjectTransform';
16-
import { convertTransformedToOriginal } from 'features/controlLayers/util/coordinateTransform';
6+
import type { Rect } from 'features/controlLayers/store/types';
177
import { memo, useCallback, useMemo } from 'react';
188
import { useTranslation } from 'react-i18next';
199
import { PiCropBold } from 'react-icons/pi';
@@ -24,57 +14,93 @@ export const InpaintMaskBboxAdjuster = memo(() => {
2414
const canvasSlice = useAppSelector(selectCanvasSlice);
2515
const maskBlur = useAppSelector(selectMaskBlur);
2616

27-
// Get all inpaint mask entities and bbox
17+
// Get all inpaint mask entities
2818
const inpaintMasks = canvasSlice.inpaintMasks.entities;
29-
const bboxRect = canvasSlice.bbox.rect;
3019

3120
// Calculate the bounding box that contains all inpaint masks
3221
const calculateMaskBbox = useCallback((): Rect | null => {
3322
if (inpaintMasks.length === 0) {
3423
return null;
3524
}
3625

37-
// Collect all mask objects from enabled masks
38-
const allObjects: (
39-
| CanvasBrushLineState
40-
| CanvasBrushLineWithPressureState
41-
| CanvasEraserLineState
42-
| CanvasEraserLineWithPressureState
43-
| CanvasRectState
44-
| CanvasImageState
45-
)[] = [];
46-
26+
let minX = Infinity;
27+
let minY = Infinity;
28+
let maxX = -Infinity;
29+
let maxY = -Infinity;
30+
31+
// Iterate through all inpaint masks to find the overall bounds
4732
for (const mask of inpaintMasks) {
48-
if (!mask.isEnabled || !mask.objects || mask.objects.length === 0) {
33+
if (!mask.isEnabled || mask.objects.length === 0) {
4934
continue;
5035
}
5136

52-
// Transform objects to be relative to the bbox
53-
const transformedObjects = transformMaskObjectsRelativeToBbox(mask.objects, bboxRect);
54-
// Convert back to original types for compatibility
55-
const originalObjects = transformedObjects.map(convertTransformedToOriginal);
56-
allObjects.push(...originalObjects);
57-
}
37+
// Calculate bounds for this mask's objects
38+
for (const obj of mask.objects) {
39+
let objMinX = 0;
40+
let objMinY = 0;
41+
let objMaxX = 0;
42+
let objMaxY = 0;
5843

59-
if (allObjects.length === 0) {
60-
return null;
44+
if (obj.type === 'rect') {
45+
objMinX = mask.position.x + obj.rect.x;
46+
objMinY = mask.position.y + obj.rect.y;
47+
objMaxX = objMinX + obj.rect.width;
48+
objMaxY = objMinY + obj.rect.height;
49+
} else if (
50+
obj.type === 'brush_line' ||
51+
obj.type === 'brush_line_with_pressure' ||
52+
obj.type === 'eraser_line' ||
53+
obj.type === 'eraser_line_with_pressure'
54+
) {
55+
// For lines, find the min/max points
56+
for (let i = 0; i < obj.points.length; i += 2) {
57+
const x = mask.position.x + (obj.points[i] ?? 0);
58+
const y = mask.position.y + (obj.points[i + 1] ?? 0);
59+
60+
if (i === 0) {
61+
objMinX = objMaxX = x;
62+
objMinY = objMaxY = y;
63+
} else {
64+
objMinX = Math.min(objMinX, x);
65+
objMinY = Math.min(objMinY, y);
66+
objMaxX = Math.max(objMaxX, x);
67+
objMaxY = Math.max(objMaxY, y);
68+
}
69+
}
70+
// Add stroke width to account for line thickness
71+
const strokeRadius = (obj.strokeWidth ?? 50) / 2;
72+
objMinX -= strokeRadius;
73+
objMinY -= strokeRadius;
74+
objMaxX += strokeRadius;
75+
objMaxY += strokeRadius;
76+
} else if (obj.type === 'image') {
77+
// Image objects are positioned at the entity's position
78+
objMinX = mask.position.x;
79+
objMinY = mask.position.y;
80+
objMaxX = objMinX + obj.image.width;
81+
objMaxY = objMinY + obj.image.height;
82+
}
83+
84+
// Update overall bounds
85+
minX = Math.min(minX, objMinX);
86+
minY = Math.min(minY, objMinY);
87+
maxX = Math.max(maxX, objMaxX);
88+
maxY = Math.max(maxY, objMaxY);
89+
}
6190
}
6291

63-
// Calculate bounds from the rendered bitmap for accurate results
64-
const maskBounds = calculateMaskBoundsFromBitmap(allObjects, bboxRect.width, bboxRect.height);
65-
66-
if (!maskBounds) {
92+
// If no valid bounds found, return null
93+
if (minX === Infinity || minY === Infinity || maxX === -Infinity || maxY === -Infinity) {
6794
return null;
6895
}
6996

70-
// Convert back to world coordinates relative to the bbox
7197
return {
72-
x: bboxRect.x + maskBounds.x,
73-
y: bboxRect.y + maskBounds.y,
74-
width: maskBounds.width,
75-
height: maskBounds.height,
98+
x: minX,
99+
y: minY,
100+
width: maxX - minX,
101+
height: maxY - minY,
76102
};
77-
}, [inpaintMasks, bboxRect]);
103+
}, [inpaintMasks]);
78104

79105
const maskBbox = useMemo(() => calculateMaskBbox(), [calculateMaskBbox]);
80106

invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import { CanvasToolbarResetViewButton } from 'features/controlLayers/components/
99
import { CanvasToolbarSaveToGalleryButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarSaveToGalleryButton';
1010
import { CanvasToolbarScale } from 'features/controlLayers/components/Toolbar/CanvasToolbarScale';
1111
import { CanvasToolbarUndoButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarUndoButton';
12-
import { useCanvasAdjustBboxHotkey } from 'features/controlLayers/hooks/useCanvasAdjustBboxHotkey';
1312
import { useCanvasDeleteLayerHotkey } from 'features/controlLayers/hooks/useCanvasDeleteLayerHotkey';
1413
import { useCanvasEntityQuickSwitchHotkey } from 'features/controlLayers/hooks/useCanvasEntityQuickSwitchHotkey';
1514
import { useCanvasFilterHotkey } from 'features/controlLayers/hooks/useCanvasFilterHotkey';
16-
import { useCanvasInvertMaskHotkey } from 'features/controlLayers/hooks/useCanvasInvertMaskHotkey';
1715
import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
1816
import { useCanvasToggleNonRasterLayersHotkey } from 'features/controlLayers/hooks/useCanvasToggleNonRasterLayersHotkey';
1917
import { useCanvasTransformHotkey } from 'features/controlLayers/hooks/useCanvasTransformHotkey';
2018
import { useCanvasUndoRedoHotkeys } from 'features/controlLayers/hooks/useCanvasUndoRedoHotkeys';
19+
import { useCanvasInvertMaskHotkey } from 'features/controlLayers/hooks/useCanvasInvertMaskHotkey';
20+
import { useCanvasAdjustBboxHotkey } from 'features/controlLayers/hooks/useCanvasAdjustBboxHotkey';
2121
import { useNextPrevEntityHotkeys } from 'features/controlLayers/hooks/useNextPrevEntity';
2222
import { memo } from 'react';
2323

invokeai/frontend/web/src/features/controlLayers/hooks/useCanvasAdjustBboxHotkey.ts

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,8 @@ import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
44
import { bboxChangedFromCanvas } from 'features/controlLayers/store/canvasSlice';
55
import { selectMaskBlur } from 'features/controlLayers/store/paramsSlice';
66
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
7-
import type {
8-
Rect,
9-
CanvasBrushLineState,
10-
CanvasBrushLineWithPressureState,
11-
CanvasEraserLineState,
12-
CanvasEraserLineWithPressureState,
13-
CanvasRectState,
14-
CanvasImageState,
15-
} from 'features/controlLayers/store/types';
16-
import { transformMaskObjectsRelativeToBbox, calculateMaskBoundsFromBitmap } from 'features/controlLayers/util/maskObjectTransform';
17-
import { convertTransformedToOriginal } from 'features/controlLayers/util/coordinateTransform';
187
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
8+
import type { Rect } from 'features/controlLayers/store/types';
199
import { useCallback, useMemo } from 'react';
2010

2111
export const useCanvasAdjustBboxHotkey = () => {
@@ -25,55 +15,71 @@ export const useCanvasAdjustBboxHotkey = () => {
2515
const maskBlur = useAppSelector(selectMaskBlur);
2616
const isBusy = useCanvasIsBusy();
2717
const inpaintMasks = canvasSlice.inpaintMasks.entities;
28-
const bboxRect = canvasSlice.bbox.rect;
2918

3019
// Calculate the bounding box that contains all inpaint masks
3120
const calculateMaskBbox = useCallback((): Rect | null => {
3221
if (inpaintMasks.length === 0) {
3322
return null;
3423
}
35-
36-
// Collect all mask objects from enabled masks
37-
const allObjects: (
38-
| CanvasBrushLineState
39-
| CanvasBrushLineWithPressureState
40-
| CanvasEraserLineState
41-
| CanvasEraserLineWithPressureState
42-
| CanvasRectState
43-
| CanvasImageState
44-
)[] = [];
45-
24+
let minX = Infinity;
25+
let minY = Infinity;
26+
let maxX = -Infinity;
27+
let maxY = -Infinity;
4628
for (const mask of inpaintMasks) {
4729
if (!mask.isEnabled || !mask.objects || mask.objects.length === 0) {
4830
continue;
4931
}
50-
51-
// Transform objects to be relative to the bbox
52-
const transformedObjects = transformMaskObjectsRelativeToBbox(mask.objects, bboxRect);
53-
// Convert back to original types for compatibility
54-
const originalObjects = transformedObjects.map(convertTransformedToOriginal);
55-
allObjects.push(...originalObjects);
56-
}
57-
58-
if (allObjects.length === 0) {
59-
return null;
32+
for (const obj of mask.objects) {
33+
let objMinX = 0;
34+
let objMinY = 0;
35+
let objMaxX = 0;
36+
let objMaxY = 0;
37+
if (obj.type === 'rect') {
38+
objMinX = mask.position.x + obj.rect.x;
39+
objMinY = mask.position.y + obj.rect.y;
40+
objMaxX = objMinX + obj.rect.width;
41+
objMaxY = objMinY + obj.rect.height;
42+
} else if (
43+
obj.type === 'brush_line' ||
44+
obj.type === 'brush_line_with_pressure' ||
45+
obj.type === 'eraser_line' ||
46+
obj.type === 'eraser_line_with_pressure'
47+
) {
48+
for (let i = 0; i < obj.points.length; i += 2) {
49+
const x = mask.position.x + (obj.points[i] ?? 0);
50+
const y = mask.position.y + (obj.points[i + 1] ?? 0);
51+
if (i === 0) {
52+
objMinX = objMaxX = x;
53+
objMinY = objMaxY = y;
54+
} else {
55+
objMinX = Math.min(objMinX, x);
56+
objMinY = Math.min(objMinY, y);
57+
objMaxX = Math.max(objMaxX, x);
58+
objMaxY = Math.max(objMaxY, y);
59+
}
60+
}
61+
const strokeRadius = (obj.strokeWidth ?? 50) / 2;
62+
objMinX -= strokeRadius;
63+
objMinY -= strokeRadius;
64+
objMaxX += strokeRadius;
65+
objMaxY += strokeRadius;
66+
} else if (obj.type === 'image') {
67+
objMinX = mask.position.x;
68+
objMinY = mask.position.y;
69+
objMaxX = objMinX + obj.image.width;
70+
objMaxY = objMinY + obj.image.height;
71+
}
72+
minX = Math.min(minX, objMinX);
73+
minY = Math.min(minY, objMinY);
74+
maxX = Math.max(maxX, objMaxX);
75+
maxY = Math.max(maxY, objMaxY);
76+
}
6077
}
61-
62-
// Calculate bounds from the rendered bitmap for accurate results
63-
const maskBounds = calculateMaskBoundsFromBitmap(allObjects, bboxRect.width, bboxRect.height);
64-
65-
if (!maskBounds) {
78+
if (minX === Infinity || minY === Infinity || maxX === -Infinity || maxY === -Infinity) {
6679
return null;
6780
}
68-
69-
// Convert back to world coordinates relative to the bbox
70-
return {
71-
x: bboxRect.x + maskBounds.x,
72-
y: bboxRect.y + maskBounds.y,
73-
width: maskBounds.width,
74-
height: maskBounds.height,
75-
};
76-
}, [inpaintMasks, bboxRect]);
81+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
82+
}, [inpaintMasks]);
7783

7884
const handleAdjustBbox = useCallback(() => {
7985
const maskBbox = calculateMaskBbox();

invokeai/frontend/web/src/features/controlLayers/store/canvasSlice.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,10 @@ export const canvasSlice = createSlice({
10101010
return;
10111011
}
10121012

1013-
// Get the current bbox dimensions for the mask
1013+
// For now, we'll use a simple approach: create a full rectangle and add eraser lines
1014+
// This is a temporary solution until we can properly handle the bitmap conversion
1015+
1016+
// Get the bbox dimensions for the mask
10141017
const bboxRect = state.bbox.rect;
10151018

10161019
// Create a full rectangle covering the bbox
@@ -1035,23 +1038,15 @@ export const canvasSlice = createSlice({
10351038
| CanvasBrushLineWithPressureState
10361039
)[] = [fillRect];
10371040

1038-
// Create a clip region that constrains all objects to the bbox
1039-
const bboxClip = {
1040-
x: bboxRect.x - entity.position.x,
1041-
y: bboxRect.y - entity.position.y,
1042-
width: bboxRect.width,
1043-
height: bboxRect.height,
1044-
};
1045-
10461041
for (const obj of entity.objects) {
10471042
if (obj.type === 'brush_line') {
1048-
// Convert brush line to eraser line, ensuring it's clipped to the bbox
1043+
// Convert brush line to eraser line
10491044
const eraserLine: CanvasEraserLineState = {
10501045
id: getPrefixedId('eraser_line'),
10511046
type: 'eraser_line',
10521047
strokeWidth: obj.strokeWidth,
10531048
points: obj.points,
1054-
clip: bboxClip, // Always clip to the current bbox
1049+
clip: obj.clip,
10551050
};
10561051
invertedObjects.push(eraserLine);
10571052
} else if (obj.type === 'brush_line_with_pressure') {
@@ -1061,7 +1056,7 @@ export const canvasSlice = createSlice({
10611056
type: 'eraser_line_with_pressure',
10621057
strokeWidth: obj.strokeWidth,
10631058
points: obj.points,
1064-
clip: bboxClip, // Always clip to the current bbox
1059+
clip: obj.clip,
10651060
};
10661061
invertedObjects.push(eraserLine);
10671062
} else if (obj.type === 'rect') {
@@ -1085,7 +1080,7 @@ export const canvasSlice = createSlice({
10851080
type: 'eraser_line',
10861081
points,
10871082
strokeWidth: Math.max(width, height) / 2, // Use a stroke width that covers the rectangle
1088-
clip: bboxClip, // Always clip to the current bbox
1083+
clip: null,
10891084
};
10901085
invertedObjects.push(eraserLine);
10911086
} else if (obj.type === 'eraser_line') {
@@ -1095,7 +1090,7 @@ export const canvasSlice = createSlice({
10951090
type: 'brush_line',
10961091
strokeWidth: obj.strokeWidth,
10971092
points: obj.points,
1098-
clip: bboxClip, // Always clip to the current bbox
1093+
clip: obj.clip,
10991094
color: { r: 255, g: 255, b: 255, a: 1 },
11001095
};
11011096
invertedObjects.push(brushLine);
@@ -1106,7 +1101,7 @@ export const canvasSlice = createSlice({
11061101
type: 'brush_line_with_pressure',
11071102
strokeWidth: obj.strokeWidth,
11081103
points: obj.points,
1109-
clip: bboxClip, // Always clip to the current bbox
1104+
clip: obj.clip,
11101105
color: { r: 255, g: 255, b: 255, a: 1 },
11111106
};
11121107
invertedObjects.push(brushLine);

0 commit comments

Comments
 (0)