fix(ui): fix bbox caching

This commit is contained in:
psychedelicious 2024-04-20 14:45:12 +10:00
parent 1e904d281a
commit 148a6c08ca
3 changed files with 12 additions and 16 deletions

View File

@ -229,8 +229,8 @@ export const regionalPromptsSlice = createSlice({
strokeWidth: state.brushSize, strokeWidth: state.brushSize,
}); });
layer.bboxNeedsUpdate = true; layer.bboxNeedsUpdate = true;
if (!layer.hasEraserStrokes) { if (!layer.hasEraserStrokes && tool === 'eraser') {
layer.hasEraserStrokes = tool === 'eraser'; layer.hasEraserStrokes = true;
} }
} }
}, },

View File

@ -1,8 +1,8 @@
import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL';
import { REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice';
import Konva from 'konva'; import Konva from 'konva';
import type { Layer as KonvaLayerType } from 'konva/lib/Layer'; import type { Layer as KonvaLayerType } from 'konva/lib/Layer';
import type { Node as KonvaNodeType, NodeConfig as KonvaNodeConfigType } from 'konva/lib/Node';
import type { IRect } from 'konva/lib/types'; import type { IRect } from 'konva/lib/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -57,11 +57,7 @@ const getImageDataBbox = (imageData: ImageData): Extents | null => {
* @param filterChildren Optional filter function to exclude certain children from the bounding box calculation. Defaults to including all children. * @param filterChildren Optional filter function to exclude certain children from the bounding box calculation. Defaults to including all children.
* @param preview Whether to open a new tab displaying the rendered layer, which is used to calculate the bbox. * @param preview Whether to open a new tab displaying the rendered layer, which is used to calculate the bbox.
*/ */
export const getKonvaLayerBbox = ( export const getKonvaLayerBbox = (layer: KonvaLayerType, preview: boolean = false): IRect | null => {
layer: KonvaLayerType,
filterChildren?: (item: KonvaNodeType<KonvaNodeConfigType>) => boolean,
preview: boolean = false
): IRect | null => {
// To calculate the layer's bounding box, we must first export it to a pixel array, then do some math. // To calculate the layer's bounding box, we must first export it to a pixel array, then do some math.
// //
// Though it is relatively fast, we can't use Konva's `getClientRect`. It programmatically determines the rect // Though it is relatively fast, we can't use Konva's `getClientRect`. It programmatically determines the rect
@ -69,7 +65,6 @@ export const getKonvaLayerBbox = (
// //
// This doesn't work when some shapes are drawn with composite operations that "erase" pixels, like eraser lines. // This doesn't work when some shapes are drawn with composite operations that "erase" pixels, like eraser lines.
// These shapes' extents are still calculated as if they were solid, leading to a bounding box that is too large. // These shapes' extents are still calculated as if they were solid, leading to a bounding box that is too large.
const stage = layer.getStage(); const stage = layer.getStage();
// Construct and offscreen canvas on which we will do the bbox calculations. // Construct and offscreen canvas on which we will do the bbox calculations.
@ -84,8 +79,12 @@ export const getKonvaLayerBbox = (
const layerClone = layer.clone(); const layerClone = layer.clone();
offscreenStage.add(layerClone); offscreenStage.add(layerClone);
if (filterChildren) { for (const child of layerClone.getChildren()) {
for (const child of layerClone.getChildren(filterChildren)) { if (child.name() === REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME) {
// We need to cache the group to ensure it composites out eraser strokes correctly
child.cache();
} else {
// Filter out unwanted children.
child.destroy(); child.destroy();
} }
} }

View File

@ -16,7 +16,6 @@ import {
} from 'features/regionalPrompts/store/regionalPromptsSlice'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { getKonvaLayerBbox } from 'features/regionalPrompts/util/bbox'; import { getKonvaLayerBbox } from 'features/regionalPrompts/util/bbox';
import Konva from 'konva'; import Konva from 'konva';
import type { Node, NodeConfig } from 'konva/lib/Node';
import type { IRect, Vector2d } from 'konva/lib/types'; import type { IRect, Vector2d } from 'konva/lib/types';
import type { RgbColor } from 'react-colorful'; import type { RgbColor } from 'react-colorful';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -30,9 +29,6 @@ const BRUSH_PREVIEW_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)';
const GET_CLIENT_RECT_CONFIG = { skipTransform: true }; const GET_CLIENT_RECT_CONFIG = { skipTransform: true };
const mapId = (object: { id: string }) => object.id; const mapId = (object: { id: string }) => object.id;
const selectPromptLayerObjectGroup = (item: Node<NodeConfig>) => {
return item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME;
};
const getIsSelected = (layerId?: string | null) => { const getIsSelected = (layerId?: string | null) => {
if (!layerId) { if (!layerId) {
return false; return false;
@ -345,7 +341,7 @@ export const renderBbox = (
if (reduxLayer.bboxNeedsUpdate && reduxLayer.objects.length) { if (reduxLayer.bboxNeedsUpdate && reduxLayer.objects.length) {
// We only need to use the pixel-perfect bounding box if the layer has eraser strokes // We only need to use the pixel-perfect bounding box if the layer has eraser strokes
bbox = reduxLayer.hasEraserStrokes bbox = reduxLayer.hasEraserStrokes
? getKonvaLayerBbox(konvaLayer, selectPromptLayerObjectGroup) ? getKonvaLayerBbox(konvaLayer)
: konvaLayer.getClientRect(GET_CLIENT_RECT_CONFIG); : konvaLayer.getClientRect(GET_CLIENT_RECT_CONFIG);
// Update the layer's bbox in the redux store // Update the layer's bbox in the redux store
@ -382,6 +378,7 @@ export const renderBbox = (
}); });
konvaLayer.add(rect); konvaLayer.add(rect);
} }
rect.setAttrs({ rect.setAttrs({
visible: true, visible: true,
listening: true, listening: true,