feat(ui): inpaint mask transform

This commit is contained in:
psychedelicious 2024-08-06 13:25:26 +10:00
parent 97e0edc549
commit e1cb30bbb4
11 changed files with 102 additions and 89 deletions

View File

@ -19,7 +19,9 @@ export const TransformToolButton = memo(() => {
if (!canvasManager) { if (!canvasManager) {
return; return;
} }
return canvasManager.isTransforming.subscribe(setIsTransforming); return canvasManager.transformingEntity.subscribe((newValue) => {
setIsTransforming(Boolean(newValue));
});
}, [canvasManager]); }, [canvasManager]);
const onTransform = useCallback(() => { const onTransform = useCallback(() => {

View File

@ -5,13 +5,11 @@ import type { CanvasInpaintMaskState, CanvasV2State, GetLoggingContext } from 'f
import Konva from 'konva'; import Konva from 'konva';
import { get } from 'lodash-es'; import { get } from 'lodash-es';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { assert } from 'tsafe';
export class CanvasInpaintMask { export class CanvasInpaintMask {
static TYPE = 'inpaint_mask' as const; static TYPE = 'inpaint_mask' as const;
static NAME_PREFIX = 'inpaint-mask'; static NAME_PREFIX = 'inpaint-mask';
static KONVA_LAYER_NAME = `${CanvasInpaintMask.NAME_PREFIX}_layer`; static KONVA_LAYER_NAME = `${CanvasInpaintMask.NAME_PREFIX}_layer`;
static OBJECT_GROUP_NAME = `${CanvasInpaintMask.NAME_PREFIX}_object-group`;
id = CanvasInpaintMask.TYPE; id = CanvasInpaintMask.TYPE;
type = CanvasInpaintMask.TYPE; type = CanvasInpaintMask.TYPE;
@ -29,7 +27,6 @@ export class CanvasInpaintMask {
konva: { konva: {
layer: Konva.Layer; layer: Konva.Layer;
objectGroup: Konva.Group;
}; };
constructor(state: CanvasInpaintMaskState, manager: CanvasManager) { constructor(state: CanvasInpaintMaskState, manager: CanvasManager) {
@ -44,16 +41,10 @@ export class CanvasInpaintMask {
listening: false, listening: false,
imageSmoothingEnabled: false, imageSmoothingEnabled: false,
}), }),
objectGroup: new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false }),
}; };
this.transformer = new CanvasTransformer(this); this.transformer = new CanvasTransformer(this);
this.renderer = new CanvasObjectRenderer(this); this.renderer = new CanvasObjectRenderer(this);
assert(this.renderer.konva.compositingRect, 'Compositing rect must be set');
this.konva.layer.add(this.konva.objectGroup);
this.konva.layer.add(this.renderer.konva.compositingRect);
this.konva.layer.add(...this.transformer.getNodes());
this.state = state; this.state = state;
this.maskOpacity = this.manager.stateApi.getMaskOpacity(); this.maskOpacity = this.manager.stateApi.getMaskOpacity();
@ -123,12 +114,6 @@ export class CanvasInpaintMask {
} }
}; };
// updateOpacity = (arg?: { opacity: number }) => {
// this.log.trace('Updating opacity');
// const opacity = get(arg, 'opacity', this.state.opacity);
// this.konva.objectGroup.opacity(opacity);
// };
updateVisibility = (arg?: { isEnabled: boolean }) => { updateVisibility = (arg?: { isEnabled: boolean }) => {
this.log.trace('Updating visibility'); this.log.trace('Updating visibility');
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled); const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);

View File

@ -2,13 +2,10 @@ import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer'; import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { konvaNodeToBlob, previewBlob } from 'features/controlLayers/konva/util';
import type { CanvasLayerState, CanvasV2State, GetLoggingContext } from 'features/controlLayers/store/types'; import type { CanvasLayerState, CanvasV2State, GetLoggingContext } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import { get } from 'lodash-es'; import { get } from 'lodash-es';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { uploadImage } from 'services/api/endpoints/images';
export class CanvasLayer { export class CanvasLayer {
static TYPE = 'layer' as const; static TYPE = 'layer' as const;
@ -25,7 +22,6 @@ export class CanvasLayer {
konva: { konva: {
layer: Konva.Layer; layer: Konva.Layer;
objectGroup: Konva.Group;
}; };
transformer: CanvasTransformer; transformer: CanvasTransformer;
renderer: CanvasObjectRenderer; renderer: CanvasObjectRenderer;
@ -47,15 +43,11 @@ export class CanvasLayer {
listening: false, listening: false,
imageSmoothingEnabled: false, imageSmoothingEnabled: false,
}), }),
objectGroup: new Konva.Group({ name: CanvasLayer.KONVA_OBJECT_GROUP_NAME, listening: false }),
}; };
this.transformer = new CanvasTransformer(this); this.transformer = new CanvasTransformer(this);
this.renderer = new CanvasObjectRenderer(this); this.renderer = new CanvasObjectRenderer(this);
this.konva.layer.add(this.konva.objectGroup);
this.konva.layer.add(...this.transformer.getNodes());
this.state = state; this.state = state;
} }
@ -121,26 +113,7 @@ export class CanvasLayer {
updateOpacity = (arg?: { opacity: number }) => { updateOpacity = (arg?: { opacity: number }) => {
this.log.trace('Updating opacity'); this.log.trace('Updating opacity');
const opacity = get(arg, 'opacity', this.state.opacity); const opacity = get(arg, 'opacity', this.state.opacity);
this.konva.objectGroup.opacity(opacity); this.renderer.konva.objectGroup.opacity(opacity);
};
rasterize = async () => {
this.log.debug('Rasterizing layer');
const objectGroupClone = this.konva.objectGroup.clone();
const interactionRectClone = this.transformer.konva.proxyRect.clone();
const rect = interactionRectClone.getClientRect();
const blob = await konvaNodeToBlob(objectGroupClone, rect);
if (this.manager._isDebugging) {
previewBlob(blob, 'Rasterized layer');
}
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
const imageObject = imageDTOToImageObject(imageDTO);
await this.renderer.renderObject(imageObject, true);
this.manager.stateApi.rasterizeEntity(
{ id: this.id, imageObject, position: { x: Math.round(rect.x), y: Math.round(rect.y) } },
this.type
);
}; };
repr = () => { repr = () => {
@ -167,15 +140,15 @@ export class CanvasLayer {
rotation: this.transformer.konva.proxyRect.rotation(), rotation: this.transformer.konva.proxyRect.rotation(),
}, },
objectGroupAttrs: { objectGroupAttrs: {
x: this.konva.objectGroup.x(), x: this.renderer.konva.objectGroup.x(),
y: this.konva.objectGroup.y(), y: this.renderer.konva.objectGroup.y(),
scaleX: this.konva.objectGroup.scaleX(), scaleX: this.renderer.konva.objectGroup.scaleX(),
scaleY: this.konva.objectGroup.scaleY(), scaleY: this.renderer.konva.objectGroup.scaleY(),
width: this.konva.objectGroup.width(), width: this.renderer.konva.objectGroup.width(),
height: this.konva.objectGroup.height(), height: this.renderer.konva.objectGroup.height(),
rotation: this.konva.objectGroup.rotation(), rotation: this.renderer.konva.objectGroup.rotation(),
offsetX: this.konva.objectGroup.offsetX(), offsetX: this.renderer.konva.objectGroup.offsetX(),
offsetY: this.konva.objectGroup.offsetY(), offsetY: this.renderer.konva.objectGroup.offsetY(),
}, },
}; };
this.log.trace(info, msg); this.log.trace(info, msg);

View File

@ -122,7 +122,7 @@ export class CanvasManager {
log: Logger; log: Logger;
workerLog: Logger; workerLog: Logger;
isTransforming: PubSub<boolean>; transformingEntity: PubSub<CanvasEntityIdentifier | null>;
_store: Store<RootState>; _store: Store<RootState>;
_prevState: CanvasV2State; _prevState: CanvasV2State;
@ -208,7 +208,7 @@ export class CanvasManager {
this.log.error('Worker message error'); this.log.error('Worker message error');
}; };
this.isTransforming = new PubSub(false); this.transformingEntity = new PubSub<CanvasEntityIdentifier | null>(null);
this.toolState = new PubSub(this.stateApi.getToolState()); this.toolState = new PubSub(this.stateApi.getToolState());
this.currentFill = new PubSub(this.getCurrentFill()); this.currentFill = new PubSub(this.getCurrentFill());
this.selectedEntityIdentifier = new PubSub( this.selectedEntityIdentifier = new PubSub(
@ -377,11 +377,24 @@ export class CanvasManager {
}; };
getTransformingLayer() { getTransformingLayer() {
return Array.from(this.layers.values()).find((layer) => layer.transformer.isTransforming); const transformingEntity = this.transformingEntity.getValue();
if (!transformingEntity) {
return null;
}
const { id, type } = transformingEntity;
if (type === 'layer') {
return this.layers.get(id) ?? null;
} else if (type === 'inpaint_mask') {
return this.inpaintMask;
}
return null;
} }
getIsTransforming() { getIsTransforming() {
return Boolean(this.getTransformingLayer()); return Boolean(this.transformingEntity.getValue());
} }
startTransform() { startTransform() {
@ -395,7 +408,7 @@ export class CanvasManager {
'No selected layer' 'No selected layer'
); );
layer.adapter.transformer.startTransform(); layer.adapter.transformer.startTransform();
this.isTransforming.publish(true); this.transformingEntity.publish({ id: layer.state.id, type: layer.state.type });
} }
async applyTransform() { async applyTransform() {
@ -403,7 +416,7 @@ export class CanvasManager {
if (layer) { if (layer) {
await layer.transformer.applyTransform(); await layer.transformer.applyTransform();
} }
this.isTransforming.publish(false); this.transformingEntity.publish(null);
} }
cancelTransform() { cancelTransform() {
@ -411,7 +424,7 @@ export class CanvasManager {
if (layer) { if (layer) {
layer.transformer.stopTransform(); layer.transformer.stopTransform();
} }
this.isTransforming.publish(false); this.transformingEntity.publish(null);
} }
render = async () => { render = async () => {

View File

@ -8,16 +8,18 @@ import type { CanvasInpaintMask } from 'features/controlLayers/konva/CanvasInpai
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer'; import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect'; import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId, konvaNodeToBlob, previewBlob } from 'features/controlLayers/konva/util';
import type { import {
CanvasBrushLineState, type CanvasBrushLineState,
CanvasEraserLineState, type CanvasEraserLineState,
CanvasImageState, type CanvasImageState,
CanvasRectState, type CanvasRectState,
RgbColor, imageDTOToImageObject,
type RgbColor,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { uploadImage } from 'services/api/endpoints/images';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
/** /**
@ -34,6 +36,7 @@ type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImage
*/ */
export class CanvasObjectRenderer { export class CanvasObjectRenderer {
static TYPE = 'object_renderer'; static TYPE = 'object_renderer';
static KONVA_OBJECT_GROUP_NAME = 'object-group';
static KONVA_COMPOSITING_RECT_NAME = 'compositing-rect'; static KONVA_COMPOSITING_RECT_NAME = 'compositing-rect';
id: string; id: string;
@ -63,6 +66,10 @@ export class CanvasObjectRenderer {
* A object containing singleton Konva nodes. * A object containing singleton Konva nodes.
*/ */
konva: { konva: {
/**
* A Konva Group that holds all the object renderers.
*/
objectGroup: Konva.Group;
/** /**
* The compositing rect is used to draw the inpaint mask as a single shape with a given opacity. * The compositing rect is used to draw the inpaint mask as a single shape with a given opacity.
* *
@ -74,6 +81,8 @@ export class CanvasObjectRenderer {
* of 'source-in'. The shapes effectively become a mask for the "compositing rect". * of 'source-in'. The shapes effectively become a mask for the "compositing rect".
* *
* This node is only added when the parent of the renderer is an inpaint mask or region, which require this behavior. * This node is only added when the parent of the renderer is an inpaint mask or region, which require this behavior.
*
* The compositing rect is not added to the object group.
*/ */
compositingRect: Konva.Rect | null; compositingRect: Konva.Rect | null;
}; };
@ -87,16 +96,19 @@ export class CanvasObjectRenderer {
this.log.trace('Creating object renderer'); this.log.trace('Creating object renderer');
this.konva = { this.konva = {
objectGroup: new Konva.Group({ name: CanvasObjectRenderer.KONVA_OBJECT_GROUP_NAME, listening: false }),
compositingRect: null, compositingRect: null,
}; };
this.parent.konva.layer.add(this.konva.objectGroup);
if (this.parent.type === 'inpaint_mask') { if (this.parent.type === 'inpaint_mask') {
this.konva.compositingRect = new Konva.Rect({ this.konva.compositingRect = new Konva.Rect({
name: CanvasObjectRenderer.KONVA_COMPOSITING_RECT_NAME, name: CanvasObjectRenderer.KONVA_COMPOSITING_RECT_NAME,
listening: false, listening: false,
globalCompositeOperation: 'source-in', globalCompositeOperation: 'source-in',
}); });
this.parent.konva.objectGroup.add(this.konva.compositingRect); this.parent.konva.layer.add(this.konva.compositingRect);
} }
this.subscriptions.add( this.subscriptions.add(
@ -184,7 +196,7 @@ export class CanvasObjectRenderer {
if (!renderer) { if (!renderer) {
renderer = new CanvasBrushLineRenderer(objectState, this); renderer = new CanvasBrushLineRenderer(objectState, this);
this.renderers.set(renderer.id, renderer); this.renderers.set(renderer.id, renderer);
this.parent.konva.objectGroup.add(renderer.konva.group); this.konva.objectGroup.add(renderer.konva.group);
} }
didRender = renderer.update(objectState, force || isFirstRender); didRender = renderer.update(objectState, force || isFirstRender);
@ -194,7 +206,7 @@ export class CanvasObjectRenderer {
if (!renderer) { if (!renderer) {
renderer = new CanvasEraserLineRenderer(objectState, this); renderer = new CanvasEraserLineRenderer(objectState, this);
this.renderers.set(renderer.id, renderer); this.renderers.set(renderer.id, renderer);
this.parent.konva.objectGroup.add(renderer.konva.group); this.konva.objectGroup.add(renderer.konva.group);
} }
didRender = renderer.update(objectState, force || isFirstRender); didRender = renderer.update(objectState, force || isFirstRender);
@ -204,7 +216,7 @@ export class CanvasObjectRenderer {
if (!renderer) { if (!renderer) {
renderer = new CanvasRectRenderer(objectState, this); renderer = new CanvasRectRenderer(objectState, this);
this.renderers.set(renderer.id, renderer); this.renderers.set(renderer.id, renderer);
this.parent.konva.objectGroup.add(renderer.konva.group); this.konva.objectGroup.add(renderer.konva.group);
} }
didRender = renderer.update(objectState, force || isFirstRender); didRender = renderer.update(objectState, force || isFirstRender);
@ -214,7 +226,7 @@ export class CanvasObjectRenderer {
if (!renderer) { if (!renderer) {
renderer = new CanvasImageRenderer(objectState, this); renderer = new CanvasImageRenderer(objectState, this);
this.renderers.set(renderer.id, renderer); this.renderers.set(renderer.id, renderer);
this.parent.konva.objectGroup.add(renderer.konva.group); this.konva.objectGroup.add(renderer.konva.group);
} }
didRender = await renderer.update(objectState, force || isFirstRender); didRender = await renderer.update(objectState, force || isFirstRender);
} }
@ -311,6 +323,25 @@ export class CanvasObjectRenderer {
return this.renderers.size > 0 || this.buffer !== null; return this.renderers.size > 0 || this.buffer !== null;
}; };
rasterize = async () => {
this.log.debug('Rasterizing entity');
const objectGroupClone = this.konva.objectGroup.clone();
const interactionRectClone = this.parent.transformer.konva.proxyRect.clone();
const rect = interactionRectClone.getClientRect();
const blob = await konvaNodeToBlob(objectGroupClone, rect);
if (this.manager._isDebugging) {
previewBlob(blob, 'Rasterized layer');
}
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
const imageObject = imageDTOToImageObject(imageDTO);
await this.renderObject(imageObject, true);
this.manager.stateApi.rasterizeEntity(
{ id: this.id, imageObject, position: { x: Math.round(rect.x), y: Math.round(rect.y) } },
this.parent.type
);
};
/** /**
* Destroys this renderer and all of its object renderers. * Destroys this renderer and all of its object renderers.
*/ */

View File

@ -23,6 +23,7 @@ import {
imImageCacheChanged, imImageCacheChanged,
imRectAdded, imRectAdded,
imTranslated, imTranslated,
inpaintMaskRasterized,
layerBrushLineAdded, layerBrushLineAdded,
layerEraserLineAdded, layerEraserLineAdded,
layerImageCacheChanged, layerImageCacheChanged,
@ -119,6 +120,8 @@ export class CanvasStateApi {
log.trace({ arg, entityType }, 'Rasterizing entity'); log.trace({ arg, entityType }, 'Rasterizing entity');
if (entityType === 'layer') { if (entityType === 'layer') {
this._store.dispatch(layerRasterized(arg)); this._store.dispatch(layerRasterized(arg));
} else if (entityType === 'inpaint_mask') {
this._store.dispatch(inpaintMaskRasterized(arg));
} else { } else {
assert(false, 'Rasterizing not supported for this entity type'); assert(false, 'Rasterizing not supported for this entity type');
} }

View File

@ -149,7 +149,7 @@ export class CanvasTool {
} else if (!isDrawableEntity) { } else if (!isDrawableEntity) {
// Non-drawable layers don't have tools // Non-drawable layers don't have tools
stage.container().style.cursor = 'not-allowed'; stage.container().style.cursor = 'not-allowed';
} else if (tool === 'move' || this.manager.isTransforming.getValue()) { } else if (tool === 'move' || Boolean(this.manager.transformingEntity.getValue())) {
// Move tool gets a pointer // Move tool gets a pointer
stage.container().style.cursor = 'default'; stage.container().style.cursor = 'default';
} else if (tool === 'rect') { } else if (tool === 'rect') {

View File

@ -251,7 +251,7 @@ export class CanvasTransformer {
// This is called when a transform anchor is dragged. By this time, the transform constraints in the above // This is called when a transform anchor is dragged. By this time, the transform constraints in the above
// callbacks have been enforced, and the transformer has updated its nodes' attributes. We need to pass the // callbacks have been enforced, and the transformer has updated its nodes' attributes. We need to pass the
// updated attributes to the object group, propagating the transformation on down. // updated attributes to the object group, propagating the transformation on down.
this.parent.konva.objectGroup.setAttrs({ this.parent.renderer.konva.objectGroup.setAttrs({
x: this.konva.proxyRect.x(), x: this.konva.proxyRect.x(),
y: this.konva.proxyRect.y(), y: this.konva.proxyRect.y(),
scaleX: this.konva.proxyRect.scaleX(), scaleX: this.konva.proxyRect.scaleX(),
@ -293,7 +293,7 @@ export class CanvasTransformer {
scaleX: snappedScaleX, scaleX: snappedScaleX,
scaleY: snappedScaleY, scaleY: snappedScaleY,
}); });
this.parent.konva.objectGroup.setAttrs({ this.parent.renderer.konva.objectGroup.setAttrs({
x: snappedX, x: snappedX,
y: snappedY, y: snappedY,
scaleX: snappedScaleX, scaleX: snappedScaleX,
@ -337,7 +337,7 @@ export class CanvasTransformer {
// The object group is translated by the difference between the interaction rect's new and old positions (which is // The object group is translated by the difference between the interaction rect's new and old positions (which is
// stored as this.pixelRect) // stored as this.pixelRect)
this.parent.konva.objectGroup.setAttrs({ this.parent.renderer.konva.objectGroup.setAttrs({
x: this.konva.proxyRect.x(), x: this.konva.proxyRect.x(),
y: this.konva.proxyRect.y(), y: this.konva.proxyRect.y(),
}); });
@ -391,6 +391,10 @@ export class CanvasTransformer {
this.syncInteractionState(); this.syncInteractionState();
}) })
); );
this.parent.konva.layer.add(this.konva.bboxOutline);
this.parent.konva.layer.add(this.konva.proxyRect);
this.parent.konva.layer.add(this.konva.transformer);
} }
/** /**
@ -499,7 +503,7 @@ export class CanvasTransformer {
*/ */
applyTransform = async () => { applyTransform = async () => {
this.log.debug('Applying transform'); this.log.debug('Applying transform');
await this.parent.rasterize(); await this.parent.renderer.rasterize();
this.requestRectCalculation(); this.requestRectCalculation();
this.stopTransform(); this.stopTransform();
}; };
@ -534,7 +538,7 @@ export class CanvasTransformer {
scaleY: 1, scaleY: 1,
rotation: 0, rotation: 0,
}; };
this.parent.konva.objectGroup.setAttrs(attrs); this.parent.renderer.konva.objectGroup.setAttrs(attrs);
this.konva.bboxOutline.setAttrs(attrs); this.konva.bboxOutline.setAttrs(attrs);
this.konva.proxyRect.setAttrs(attrs); this.konva.proxyRect.setAttrs(attrs);
}; };
@ -547,7 +551,7 @@ export class CanvasTransformer {
this.log.trace('Updating position'); this.log.trace('Updating position');
const position = get(arg, 'position', this.parent.state.position); const position = get(arg, 'position', this.parent.state.position);
this.parent.konva.objectGroup.setAttrs({ this.parent.renderer.konva.objectGroup.setAttrs({
x: position.x + this.pixelRect.x, x: position.x + this.pixelRect.x,
y: position.y + this.pixelRect.y, y: position.y + this.pixelRect.y,
offsetX: this.pixelRect.x, offsetX: this.pixelRect.x,
@ -603,7 +607,7 @@ export class CanvasTransformer {
this.syncInteractionState(); this.syncInteractionState();
this.update(this.parent.state.position, this.pixelRect); this.update(this.parent.state.position, this.pixelRect);
this.parent.konva.objectGroup.setAttrs({ this.parent.renderer.konva.objectGroup.setAttrs({
x: this.parent.state.position.x + this.pixelRect.x, x: this.parent.state.position.x + this.pixelRect.x,
y: this.parent.state.position.y + this.pixelRect.y, y: this.parent.state.position.y + this.pixelRect.y,
offsetX: this.pixelRect.x, offsetX: this.pixelRect.x,
@ -625,7 +629,7 @@ export class CanvasTransformer {
return; return;
} }
const rect = this.parent.konva.objectGroup.getClientRect({ skipTransform: true }); const rect = this.parent.renderer.konva.objectGroup.getClientRect({ skipTransform: true });
if (!this.parent.renderer.needsPixelBbox()) { if (!this.parent.renderer.needsPixelBbox()) {
this.nodeRect = { ...rect }; this.nodeRect = { ...rect };
@ -638,7 +642,7 @@ export class CanvasTransformer {
// We have eraser strokes - we must calculate the bbox using pixel data // We have eraser strokes - we must calculate the bbox using pixel data
const clone = this.parent.konva.objectGroup.clone(); const clone = this.parent.renderer.konva.objectGroup.clone();
const canvas = clone.toCanvas(); const canvas = clone.toCanvas();
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
if (!ctx) { if (!ctx) {
@ -709,12 +713,6 @@ export class CanvasTransformer {
this.konva.bboxOutline.visible(false); this.konva.bboxOutline.visible(false);
}; };
/**
* Gets the nodes that make up the transformer, in the order they should be added to the layer.
* @returns The nodes that make up the transformer.
*/
getNodes = () => [this.konva.bboxOutline, this.konva.proxyRect, this.konva.transformer];
/** /**
* Gets a JSON-serializable object that describes the transformer. * Gets a JSON-serializable object that describes the transformer.
*/ */

View File

@ -345,6 +345,7 @@ export const {
imBrushLineAdded, imBrushLineAdded,
imEraserLineAdded, imEraserLineAdded,
imRectAdded, imRectAdded,
inpaintMaskRasterized,
// Staging // Staging
sessionStarted, sessionStarted,
sessionStartedStaging, sessionStartedStaging,

View File

@ -6,6 +6,7 @@ import type {
CanvasRectState, CanvasRectState,
CanvasV2State, CanvasV2State,
Coordinate, Coordinate,
EntityRasterizedArg,
ScaleChangedArg, ScaleChangedArg,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types'; import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
@ -84,4 +85,10 @@ export const inpaintMaskReducers = {
state.inpaintMask.bboxNeedsUpdate = true; state.inpaintMask.bboxNeedsUpdate = true;
state.layers.imageCache = null; state.layers.imageCache = null;
}, },
inpaintMaskRasterized: (state, action: PayloadAction<EntityRasterizedArg>) => {
const { imageObject, position } = action.payload;
state.inpaintMask.objects = [imageObject];
state.inpaintMask.position = position;
state.inpaintMask.imageCache = null;
},
} satisfies SliceCaseReducers<CanvasV2State>; } satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -683,7 +683,7 @@ const zCanvasInpaintMaskState = z.object({
position: zCoordinate, position: zCoordinate,
bbox: zRect.nullable(), bbox: zRect.nullable(),
bboxNeedsUpdate: z.boolean(), bboxNeedsUpdate: z.boolean(),
objects: z.array(zMaskObject), objects: z.array(zCanvasObjectState),
fill: zRgbColor, fill: zRgbColor,
imageCache: zImageWithDims.nullable(), imageCache: zImageWithDims.nullable(),
}); });