feat(ui): wip inpaint mask uses new API

This commit is contained in:
psychedelicious 2024-08-05 22:01:06 +10:00
parent 31ac02cd93
commit 5dcef6fa0d
5 changed files with 177 additions and 277 deletions

View File

@ -1,294 +1,128 @@
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect'; import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox'; import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { mapId } from 'features/controlLayers/konva/util'; import type { CanvasInpaintMaskState, CanvasV2State, GetLoggingContext } from 'features/controlLayers/store/types';
import type {
CanvasBrushLineState,
CanvasEraserLineState,
CanvasInpaintMaskState,
CanvasRectState,
} from 'features/controlLayers/store/types';
import { isDrawingTool, RGBA_RED } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import { get } from 'lodash-es';
import type { Logger } from 'roarr';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export class CanvasInpaintMask { export class CanvasInpaintMask {
static NAME_PREFIX = 'inpaint-mask';
static LAYER_NAME = `${CanvasInpaintMask.NAME_PREFIX}_layer`;
static TRANSFORMER_NAME = `${CanvasInpaintMask.NAME_PREFIX}_transformer`;
static GROUP_NAME = `${CanvasInpaintMask.NAME_PREFIX}_group`;
static OBJECT_GROUP_NAME = `${CanvasInpaintMask.NAME_PREFIX}_object-group`;
static COMPOSITING_RECT_NAME = `${CanvasInpaintMask.NAME_PREFIX}_compositing-rect`;
static TYPE = 'inpaint_mask' as const; static TYPE = 'inpaint_mask' as const;
private drawingBuffer: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null; static NAME_PREFIX = 'inpaint-mask';
private state: CanvasInpaintMaskState; 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;
manager: CanvasManager; manager: CanvasManager;
log: Logger;
getLoggingContext: GetLoggingContext;
state: CanvasInpaintMaskState;
transformer: CanvasTransformer;
renderer: CanvasObjectRenderer;
isFirstRender: boolean = true;
konva: { konva: {
layer: Konva.Layer; layer: Konva.Layer;
group: Konva.Group;
objectGroup: Konva.Group; objectGroup: Konva.Group;
transformer: Konva.Transformer;
compositingRect: Konva.Rect;
}; };
objects: Map<string, CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer>;
constructor(state: CanvasInpaintMaskState, manager: CanvasManager) { constructor(state: CanvasInpaintMaskState, manager: CanvasManager) {
this.manager = manager; this.manager = manager;
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug({ state }, 'Creating inpaint mask');
this.konva = { this.konva = {
layer: new Konva.Layer({ name: CanvasInpaintMask.LAYER_NAME }), layer: new Konva.Layer({
group: new Konva.Group({ name: CanvasInpaintMask.GROUP_NAME, listening: false }), name: CanvasInpaintMask.KONVA_LAYER_NAME,
objectGroup: new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false }), listening: false,
transformer: new Konva.Transformer({ imageSmoothingEnabled: false,
name: CanvasInpaintMask.TRANSFORMER_NAME,
shouldOverdrawWholeArea: true,
draggable: true,
dragDistance: 0,
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
rotateEnabled: false,
flipEnabled: false,
}), }),
compositingRect: new Konva.Rect({ name: CanvasInpaintMask.COMPOSITING_RECT_NAME, listening: false }), objectGroup: new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false }),
}; };
this.konva.group.add(this.konva.objectGroup); this.transformer = new CanvasTransformer(this);
this.konva.layer.add(this.konva.group); this.renderer = new CanvasObjectRenderer(this, true);
assert(this.renderer.konva.compositingRect, 'Compositing rect must be set');
this.konva.transformer.on('transformend', () => { this.konva.layer.add(this.konva.objectGroup);
this.manager.stateApi.onScaleChanged( this.konva.layer.add(this.renderer.konva.compositingRect);
{ this.konva.layer.add(...this.transformer.getNodes());
id: this.id,
scale: this.konva.group.scaleX(),
position: { x: this.konva.group.x(), y: this.konva.group.y() },
},
'inpaint_mask'
);
});
this.konva.transformer.on('dragend', () => {
this.manager.stateApi.setEntityPosition(
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
'inpaint_mask'
);
});
this.konva.layer.add(this.konva.transformer);
this.konva.group.add(this.konva.compositingRect);
this.objects = new Map();
this.drawingBuffer = null;
this.state = state; this.state = state;
} }
destroy(): void { destroy = (): void => {
this.log.debug('Destroying inpaint mask');
// We need to call the destroy method on all children so they can do their own cleanup.
this.transformer.destroy();
this.renderer.destroy();
this.konva.layer.destroy(); this.konva.layer.destroy();
} };
getDrawingBuffer() { update = async (arg?: { state: CanvasInpaintMaskState; toolState: CanvasV2State['tool']; isSelected: boolean }) => {
return this.drawingBuffer; const state = get(arg, 'state', this.state);
}
async setDrawingBuffer(obj: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null) { if (!this.isFirstRender && state === this.state) {
this.drawingBuffer = obj; this.log.trace('State unchanged, skipping update');
if (this.drawingBuffer) {
if (this.drawingBuffer.type === 'brush_line') {
this.drawingBuffer.color = RGBA_RED;
} else if (this.drawingBuffer.type === 'rect') {
this.drawingBuffer.color = RGBA_RED;
}
await this.renderObject(this.drawingBuffer, true);
this.updateGroup(true);
}
}
finalizeDrawingBuffer() {
if (!this.drawingBuffer) {
return; return;
} }
if (this.drawingBuffer.type === 'brush_line') {
this.manager.stateApi.addBrushLine({ id: this.id, brushLine: this.drawingBuffer }, 'inpaint_mask');
} else if (this.drawingBuffer.type === 'eraser_line') {
this.manager.stateApi.addEraserLine({ id: this.id, eraserLine: this.drawingBuffer }, 'inpaint_mask');
} else if (this.drawingBuffer.type === 'rect') {
this.manager.stateApi.addRect({ id: this.id, rect: this.drawingBuffer }, 'inpaint_mask');
}
this.setDrawingBuffer(null);
}
async render(state: CanvasInpaintMaskState) { // const maskOpacity = this.manager.stateApi.getMaskOpacity()
this.log.debug('Updating');
const { position, objects, isEnabled } = state;
if (this.isFirstRender || objects !== this.state.objects) {
await this.updateObjects({ objects });
}
if (this.isFirstRender || position !== this.state.position) {
await this.transformer.updatePosition({ position });
}
// if (this.isFirstRender || opacity !== this.state.opacity) {
// await this.updateOpacity({ opacity });
// }
if (this.isFirstRender || isEnabled !== this.state.isEnabled) {
await this.updateVisibility({ isEnabled });
}
// this.transformer.syncInteractionState();
if (this.isFirstRender) {
await this.transformer.updateBbox();
}
this.state = state; this.state = state;
this.isFirstRender = false;
};
// Update the layer's position and listening state updateObjects = async (arg?: { objects: CanvasInpaintMaskState['objects'] }) => {
this.konva.group.setAttrs({ this.log.trace('Updating objects');
x: state.position.x,
y: state.position.y,
scaleX: 1,
scaleY: 1,
});
let didDraw = false; const objects = get(arg, 'objects', this.state.objects);
const objectIds = state.objects.map(mapId); const didUpdate = await this.renderer.render(objects);
// Destroy any objects that are no longer in state
for (const object of this.objects.values()) { if (didUpdate) {
if (!objectIds.includes(object.id)) { this.transformer.requestRectCalculation();
this.objects.delete(object.id);
object.destroy();
didDraw = true;
}
} }
for (const obj of state.objects) { this.isFirstRender = false;
if (await this.renderObject(obj)) { };
didDraw = true;
}
}
if (this.drawingBuffer) { // updateOpacity = (arg?: { opacity: number }) => {
if (await this.renderObject(this.drawingBuffer)) { // this.log.trace('Updating opacity');
didDraw = true; // const opacity = get(arg, 'opacity', this.state.opacity);
} // this.konva.objectGroup.opacity(opacity);
} // };
this.updateGroup(didDraw); updateVisibility = (arg?: { isEnabled: boolean }) => {
} this.log.trace('Updating visibility');
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);
private async renderObject(obj: CanvasInpaintMaskState['objects'][number], force = false): Promise<boolean> { this.konva.layer.visible(isEnabled && this.renderer.hasObjects());
if (obj.type === 'brush_line') { };
let brushLine = this.objects.get(obj.id);
assert(brushLine instanceof CanvasBrushLineRenderer || brushLine === undefined);
if (!brushLine) {
brushLine = new CanvasBrushLineRenderer(obj);
this.objects.set(brushLine.id, brushLine);
this.konva.objectGroup.add(brushLine.konva.group);
return true;
} else {
if (brushLine.update(obj, force)) {
return true;
}
}
} else if (obj.type === 'eraser_line') {
let eraserLine = this.objects.get(obj.id);
assert(eraserLine instanceof CanvasEraserLineRenderer || eraserLine === undefined);
if (!eraserLine) {
eraserLine = new CanvasEraserLineRenderer(obj);
this.objects.set(eraserLine.id, eraserLine);
this.konva.objectGroup.add(eraserLine.konva.group);
return true;
} else {
if (eraserLine.update(obj, force)) {
return true;
}
}
} else if (obj.type === 'rect') {
let rect = this.objects.get(obj.id);
assert(rect instanceof CanvasRectRenderer || rect === undefined);
if (!rect) {
rect = new CanvasRectRenderer(obj);
this.objects.set(rect.id, rect);
this.konva.objectGroup.add(rect.konva.group);
return true;
} else {
if (rect.update(obj, force)) {
return true;
}
}
}
return false;
}
updateGroup(didDraw: boolean) {
this.konva.layer.visible(this.state.isEnabled);
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
this.konva.group.opacity(1);
if (didDraw) {
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
const rgbColor = rgbColorToString(this.state.fill);
const maskOpacity = this.manager.stateApi.getMaskOpacity();
this.konva.compositingRect.setAttrs({
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
...getNodeBboxFast(this.konva.objectGroup),
fill: rgbColor,
opacity: maskOpacity,
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
globalCompositeOperation: 'source-in',
visible: true,
// This rect must always be on top of all other shapes
zIndex: this.objects.size + 1,
});
}
const isSelected = this.manager.stateApi.getIsSelected(this.id);
const selectedTool = this.manager.stateApi.getToolState().selected;
if (this.objects.size === 0) {
// If the layer is totally empty, reset the cache and bail out.
this.konva.layer.listening(false);
this.konva.transformer.nodes([]);
if (this.konva.group.isCached()) {
this.konva.group.clearCache();
}
return;
}
if (isSelected && selectedTool === 'move') {
// When the layer is selected and being moved, we should always cache it.
// We should update the cache if we drew to the layer.
if (!this.konva.group.isCached() || didDraw) {
// this.konva.group.cache();
}
// Activate the transformer
this.konva.layer.listening(true);
this.konva.transformer.nodes([this.konva.group]);
this.konva.transformer.forceUpdate();
return;
}
if (isSelected && selectedTool !== 'move') {
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
this.konva.layer.listening(false);
// The transformer also does not need to be active.
this.konva.transformer.nodes([]);
if (isDrawingTool(selectedTool)) {
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
// should never be cached.
if (this.konva.group.isCached()) {
this.konva.group.clearCache();
}
} else {
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
// We should update the cache if we drew to the layer.
if (!this.konva.group.isCached() || didDraw) {
// this.konva.group.cache();
}
}
return;
}
if (!isSelected) {
// Unselected layers should not be listening
this.konva.layer.listening(false);
// The transformer also does not need to be active.
this.konva.transformer.nodes([]);
// Update the layer's cache if it's not already cached or we drew to it.
if (!this.konva.group.isCached() || didDraw) {
// this.konva.group.cache();
}
return;
}
}
} }

View File

@ -177,9 +177,6 @@ export class CanvasManager {
this.background = new CanvasBackground(this); this.background = new CanvasBackground(this);
this.stage.add(this.background.konva.layer); this.stage.add(this.background.konva.layer);
this.inpaintMask = new CanvasInpaintMask(this.stateApi.getInpaintMaskState(), this);
this.stage.add(this.inpaintMask.konva.layer);
this.layers = new Map(); this.layers = new Map();
this.regions = new Map(); this.regions = new Map();
this.controlAdapters = new Map(); this.controlAdapters = new Map();
@ -222,6 +219,9 @@ export class CanvasManager {
this.getSelectedEntity(), this.getSelectedEntity(),
(a, b) => a?.state === b?.state && a?.adapter === b?.adapter (a, b) => a?.state === b?.state && a?.adapter === b?.adapter
); );
this.inpaintMask = new CanvasInpaintMask(this.stateApi.getInpaintMaskState(), this);
this.stage.add(this.inpaintMask.konva.layer);
} }
enableDebugging() { enableDebugging() {
@ -273,11 +273,6 @@ export class CanvasManager {
await this.preview.progressPreview.render(this.stateApi.$lastProgressEvent.get()); await this.preview.progressPreview.render(this.stateApi.$lastProgressEvent.get());
} }
async renderInpaintMask() {
const inpaintMaskState = this.stateApi.getInpaintMaskState();
await this.inpaintMask.render(inpaintMaskState);
}
async renderControlAdapters() { async renderControlAdapters() {
const { entities } = this.stateApi.getControlAdaptersState(); const { entities } = this.stateApi.getControlAdaptersState();
@ -372,9 +367,9 @@ export class CanvasManager {
const selectedEntity = this.getSelectedEntity(); const selectedEntity = this.getSelectedEntity();
if (selectedEntity) { if (selectedEntity) {
if (selectedEntity.state.type === 'regional_guidance') { if (selectedEntity.state.type === 'regional_guidance') {
currentFill = { ...selectedEntity.state.fill, a: state.settings.maskOpacity }; currentFill = { ...selectedEntity.state.fill, a: 1 };
} else if (selectedEntity.state.type === 'inpaint_mask') { } else if (selectedEntity.state.type === 'inpaint_mask') {
currentFill = { ...state.inpaintMask.fill, a: state.settings.maskOpacity }; currentFill = { ...state.inpaintMask.fill, a: 1 };
} }
} }
return currentFill; return currentFill;
@ -394,7 +389,10 @@ export class CanvasManager {
} }
const layer = this.getSelectedEntity(); const layer = this.getSelectedEntity();
// TODO(psyche): Support other entity types // TODO(psyche): Support other entity types
assert(layer?.adapter instanceof CanvasLayer, 'No selected layer'); assert(
layer && (layer.adapter instanceof CanvasLayer || layer.adapter instanceof CanvasInpaintMask),
'No selected layer'
);
layer.adapter.transformer.startTransform(); layer.adapter.transformer.startTransform();
this.isTransforming.publish(true); this.isTransforming.publish(true);
} }
@ -472,13 +470,16 @@ export class CanvasManager {
if ( if (
this._isFirstRender || this._isFirstRender ||
state.inpaintMask !== this._prevState.inpaintMask ||
state.settings.maskOpacity !== this._prevState.settings.maskOpacity || state.settings.maskOpacity !== this._prevState.settings.maskOpacity ||
state.tool.selected !== this._prevState.tool.selected || state.tool.selected !== this._prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
) { ) {
this.log.debug('Rendering inpaint mask'); this.log.debug('Rendering inpaint mask');
await this.renderInpaintMask(); await this.inpaintMask.update({
state: state.inpaintMask,
toolState: state.tool,
isSelected: state.selectedEntityIdentifier?.id === state.inpaintMask.id,
});
} }
if ( if (
@ -670,9 +671,14 @@ export class CanvasManager {
| CanvasTransformer | CanvasTransformer
| CanvasObjectRenderer | CanvasObjectRenderer
| CanvasLayer | CanvasLayer
| CanvasInpaintMask
| CanvasStagingArea | CanvasStagingArea
): GetLoggingContext => { ): GetLoggingContext => {
if (instance instanceof CanvasLayer || instance instanceof CanvasStagingArea) { if (
instance instanceof CanvasLayer ||
instance instanceof CanvasStagingArea ||
instance instanceof CanvasInpaintMask
) {
return (extra?: JSONObject): JSONObject => { return (extra?: JSONObject): JSONObject => {
return { return {
...instance.manager.getLoggingContext(), ...instance.manager.getLoggingContext(),

View File

@ -1,8 +1,10 @@
import type { JSONObject } from 'common/types'; import type { JSONObject } from 'common/types';
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine'; import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine'; import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage'; import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasInpaintMask } from 'features/controlLayers/konva/CanvasInpaintMask';
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';
@ -13,6 +15,7 @@ import type {
CanvasImageState, CanvasImageState,
CanvasRectState, CanvasRectState,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -30,9 +33,10 @@ type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImage
*/ */
export class CanvasObjectRenderer { export class CanvasObjectRenderer {
static TYPE = 'object_renderer'; static TYPE = 'object_renderer';
static KONVA_COMPOSITING_RECT_NAME = 'compositing-rect';
id: string; id: string;
parent: CanvasLayer; parent: CanvasLayer | CanvasInpaintMask;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
getLoggingContext: (extra?: JSONObject) => JSONObject; getLoggingContext: (extra?: JSONObject) => JSONObject;
@ -54,7 +58,11 @@ export class CanvasObjectRenderer {
*/ */
renderers: Map<string, AnyObjectRenderer> = new Map(); renderers: Map<string, AnyObjectRenderer> = new Map();
constructor(parent: CanvasLayer) { konva: {
compositingRect: Konva.Rect | null;
};
constructor(parent: CanvasLayer | CanvasInpaintMask, withCompositingRect: boolean = false) {
this.id = getPrefixedId(CanvasObjectRenderer.TYPE); this.id = getPrefixedId(CanvasObjectRenderer.TYPE);
this.parent = parent; this.parent = parent;
this.manager = parent.manager; this.manager = parent.manager;
@ -62,6 +70,18 @@ export class CanvasObjectRenderer {
this.log = this.manager.buildLogger(this.getLoggingContext); this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.trace('Creating object renderer'); this.log.trace('Creating object renderer');
this.konva = {
compositingRect: null,
};
if (withCompositingRect) {
this.konva.compositingRect = new Konva.Rect({
name: CanvasObjectRenderer.KONVA_COMPOSITING_RECT_NAME,
listening: false,
});
this.parent.konva.objectGroup.add(this.konva.compositingRect);
}
this.subscriptions.add( this.subscriptions.add(
this.manager.toolState.subscribe((newVal, oldVal) => { this.manager.toolState.subscribe((newVal, oldVal) => {
if (newVal.selected !== oldVal.selected) { if (newVal.selected !== oldVal.selected) {
@ -69,6 +89,21 @@ export class CanvasObjectRenderer {
} }
}) })
); );
// The compositing rect must cover the whole stage at all times. When the stage is scaled, moved or resized, we
// need to update the compositing rect to match the stage.
this.subscriptions.add(
this.manager.stateApi.$stageAttrs.listen((attrs) => {
if (this.konva.compositingRect) {
this.konva.compositingRect.setAttrs({
x: -attrs.position.x / attrs.scale,
y: -attrs.position.y / attrs.scale,
width: attrs.dimensions.width / attrs.scale,
height: attrs.dimensions.height / attrs.scale,
});
}
})
);
} }
/** /**
@ -96,6 +131,24 @@ export class CanvasObjectRenderer {
didRender = (await this.renderObject(this.buffer)) || didRender; didRender = (await this.renderObject(this.buffer)) || didRender;
} }
if (didRender && this.parent.type === 'inpaint_mask') {
assert(this.konva.compositingRect, 'Compositing rect must exist for inpaint mask');
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
const rgbColor = rgbColorToString(this.parent.state.fill);
const maskOpacity = this.manager.stateApi.getMaskOpacity();
this.konva.compositingRect.setAttrs({
fill: rgbColor,
opacity: maskOpacity,
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
globalCompositeOperation: 'source-in',
visible: true,
// This rect must always be on top of all other shapes
// zIndex: this.renderers.size + 1,
});
}
return didRender; return didRender;
}; };
@ -177,6 +230,12 @@ export class CanvasObjectRenderer {
this.buffer = objectState; this.buffer = objectState;
return await this.renderObject(this.buffer, true); return await this.renderObject(this.buffer, true);
// const didDraw = await this.renderObject(this.buffer, true);
// if (didDraw && this.konva.compositingRect) {
// this.konva.compositingRect.zIndex(this.renderers.size + 1);
// }
// return didDraw;
}; };
/** /**
@ -204,11 +263,11 @@ export class CanvasObjectRenderer {
this.buffer.id = getPrefixedId(this.buffer.type); this.buffer.id = getPrefixedId(this.buffer.type);
if (this.buffer.type === 'brush_line') { if (this.buffer.type === 'brush_line') {
this.manager.stateApi.addBrushLine({ id: this.parent.id, brushLine: this.buffer }, 'layer'); this.manager.stateApi.addBrushLine({ id: this.parent.id, brushLine: this.buffer }, this.parent.type);
} else if (this.buffer.type === 'eraser_line') { } else if (this.buffer.type === 'eraser_line') {
this.manager.stateApi.addEraserLine({ id: this.parent.id, eraserLine: this.buffer }, 'layer'); this.manager.stateApi.addEraserLine({ id: this.parent.id, eraserLine: this.buffer }, this.parent.type);
} else if (this.buffer.type === 'rect') { } else if (this.buffer.type === 'rect') {
this.manager.stateApi.addRect({ id: this.parent.id, rect: this.buffer }, 'layer'); this.manager.stateApi.addRect({ id: this.parent.id, rect: this.buffer }, this.parent.type);
} else { } else {
this.log.warn({ buffer: this.buffer }, 'Invalid buffer object type'); this.log.warn({ buffer: this.buffer }, 'Invalid buffer object type');
} }

View File

@ -1,3 +1,4 @@
import type { CanvasInpaintMask } from 'features/controlLayers/konva/CanvasInpaintMask';
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer'; import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
import { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getEmptyRect, getPrefixedId } from 'features/controlLayers/konva/util'; import { getEmptyRect, getPrefixedId } from 'features/controlLayers/konva/util';
@ -31,7 +32,7 @@ export class CanvasTransformer {
static ANCHOR_HIT_PADDING = 10; static ANCHOR_HIT_PADDING = 10;
id: string; id: string;
parent: CanvasLayer; parent: CanvasLayer | CanvasInpaintMask;
manager: CanvasManager; manager: CanvasManager;
log: Logger; log: Logger;
getLoggingContext: GetLoggingContext; getLoggingContext: GetLoggingContext;
@ -89,7 +90,7 @@ export class CanvasTransformer {
bboxOutline: Konva.Rect; bboxOutline: Konva.Rect;
}; };
constructor(parent: CanvasLayer) { constructor(parent: CanvasLayer | CanvasInpaintMask) {
this.id = getPrefixedId(CanvasTransformer.TYPE); this.id = getPrefixedId(CanvasTransformer.TYPE);
this.parent = parent; this.parent = parent;
this.manager = parent.manager; this.manager = parent.manager;
@ -354,7 +355,7 @@ export class CanvasTransformer {
}; };
this.log.trace({ position }, 'Position changed'); this.log.trace({ position }, 'Position changed');
this.manager.stateApi.setEntityPosition({ id: this.parent.id, position }, 'layer'); this.manager.stateApi.setEntityPosition({ id: this.parent.id, position }, this.parent.type);
}); });
this.subscriptions.add( this.subscriptions.add(

View File

@ -128,8 +128,8 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
$spaceKey, $spaceKey,
getBbox, getBbox,
getSettings, getSettings,
setBrushWidth: onBrushWidthChanged, setBrushWidth,
setEraserWidth: onEraserWidthChanged, setEraserWidth,
} = stateApi; } = stateApi;
function getIsPrimaryMouseDown(e: KonvaEventObject<MouseEvent>) { function getIsPrimaryMouseDown(e: KonvaEventObject<MouseEvent>) {
@ -461,9 +461,9 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
} }
// Holding ctrl or meta while scrolling changes the brush size // Holding ctrl or meta while scrolling changes the brush size
if (toolState.selected === 'brush') { if (toolState.selected === 'brush') {
onBrushWidthChanged(calculateNewBrushSize(toolState.brush.width, delta)); setBrushWidth(calculateNewBrushSize(toolState.brush.width, delta));
} else if (toolState.selected === 'eraser') { } else if (toolState.selected === 'eraser') {
onEraserWidthChanged(calculateNewBrushSize(toolState.eraser.width, delta)); setEraserWidth(calculateNewBrushSize(toolState.eraser.width, delta));
} }
} else { } else {
// We need the absolute cursor position - not the scaled position // We need the absolute cursor position - not the scaled position