feat(ui): transform cleanup

This commit is contained in:
psychedelicious 2024-07-30 20:03:15 +10:00
parent 49733091c7
commit ea02323095
7 changed files with 212 additions and 215 deletions

View File

@ -1,6 +1,6 @@
/* eslint-disable i18next/no-literal-string */
import { Button } from '@chakra-ui/react';
import { Flex } from '@invoke-ai/ui-library';
import { Flex, Switch } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { BrushWidth } from 'features/controlLayers/components/BrushWidth';
@ -14,6 +14,7 @@ import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoB
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
export const ControlLayersToolbar = memo(() => {
@ -27,12 +28,19 @@ export const ControlLayersToolbar = memo(() => {
l.calculateBbox();
}
}, [canvasManager]);
const debug = useCallback(() => {
if (!canvasManager) {
return;
}
canvasManager.logDebugInfo();
}, [canvasManager]);
const onChangeDebugging = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
if (!canvasManager) {
return;
}
if (e.target.checked) {
canvasManager.enableDebugging();
} else {
canvasManager.disableDebugging();
}
},
[canvasManager]
);
return (
<Flex w="full" gap={2}>
<Flex flex={1} justifyContent="center">
@ -46,7 +54,7 @@ export const ControlLayersToolbar = memo(() => {
{tool === 'eraser' && <EraserWidth />}
</Flex>
<Button onClick={bbox}>bbox</Button>
<Button onClick={debug}>debug</Button>
<Switch onChange={onChangeDebugging}>debug</Switch>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto" alignItems="center">
<FillColorPicker />

View File

@ -25,7 +25,7 @@ export class CanvasBrushLine {
this.id = id;
this.parent = parent;
this.parent.log.trace(`Creating brush line ${this.id}`);
this.parent._log.trace(`Creating brush line ${this.id}`);
this.konva = {
group: new Konva.Group({
@ -54,7 +54,7 @@ export class CanvasBrushLine {
async update(state: BrushLine, force?: boolean): Promise<boolean> {
if (force || this.state !== state) {
this.parent.log.trace(`Updating brush line ${this.id}`);
this.parent._log.trace(`Updating brush line ${this.id}`);
const { points, color, clip, strokeWidth } = state;
this.konva.line.setAttrs({
// A line with only one point will not be rendered, so we duplicate the points to make it visible
@ -71,7 +71,7 @@ export class CanvasBrushLine {
}
destroy() {
this.parent.log.trace(`Destroying brush line ${this.id}`);
this.parent._log.trace(`Destroying brush line ${this.id}`);
this.konva.group.destroy();
}
}

View File

@ -26,7 +26,7 @@ export class CanvasEraserLine {
this.id = id;
this.parent = parent;
this.parent.log.trace(`Creating eraser line ${this.id}`);
this.parent._log.trace(`Creating eraser line ${this.id}`);
this.konva = {
group: new Konva.Group({
@ -55,7 +55,7 @@ export class CanvasEraserLine {
async update(state: EraserLine, force?: boolean): Promise<boolean> {
if (force || this.state !== state) {
this.parent.log.trace(`Updating eraser line ${this.id}`);
this.parent._log.trace(`Updating eraser line ${this.id}`);
const { points, clip, strokeWidth } = state;
this.konva.line.setAttrs({
// A line with only one point will not be rendered, so we duplicate the points to make it visible
@ -71,7 +71,7 @@ export class CanvasEraserLine {
}
destroy() {
this.parent.log.trace(`Destroying eraser line ${this.id}`);
this.parent._log.trace(`Destroying eraser line ${this.id}`);
this.konva.group.destroy();
}
}

View File

@ -36,7 +36,7 @@ export class CanvasImage {
this.id = id;
this.parent = parent;
this.parent.log.trace(`Creating image ${this.id}`);
this.parent._log.trace(`Creating image ${this.id}`);
this.konva = {
group: new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y }),
@ -77,13 +77,13 @@ export class CanvasImage {
async updateImageSource(imageName: string) {
try {
this.parent.log.trace(`Updating image source ${this.id}`);
this.parent._log.trace(`Updating image source ${this.id}`);
this.isLoading = true;
this.konva.group.visible(true);
if (!this.image) {
this.konva.placeholder.group.visible(true);
this.konva.placeholder.group.visible(false);
this.konva.placeholder.text.text(t('common.loadingImage', 'Loading Image'));
}
@ -130,7 +130,7 @@ export class CanvasImage {
async update(state: ImageObject, force?: boolean): Promise<boolean> {
if (this.state !== state || force) {
this.parent.log.trace(`Updating image ${this.id}`);
this.parent._log.trace(`Updating image ${this.id}`);
const { width, height, x, y, image, filters } = state;
if (this.state.image.name !== image.name || force) {
@ -154,7 +154,7 @@ export class CanvasImage {
}
destroy() {
this.parent.log.trace(`Destroying image ${this.id}`);
this.parent._log.trace(`Destroying image ${this.id}`);
this.konva.group.destroy();
}
}

View File

@ -24,24 +24,6 @@ import { uploadImage } from 'services/api/endpoints/images';
import { assert } from 'tsafe';
import { v4 as uuidv4 } from 'uuid';
const getCenter = (rect: Rect): Coordinate => {
return {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2,
};
};
window.getCenter = getCenter;
function rotatePoint(point: Coordinate, origin: Coordinate, deg: number): Coordinate {
const angle = deg * (Math.PI / 180); // Convert to radians
const rotatedX = Math.cos(angle) * (point.x - origin.x) - Math.sin(angle) * (point.y - origin.y) + origin.x;
const rotatedY = Math.sin(angle) * (point.x - origin.x) + Math.cos(angle) * (point.y - origin.y) + origin.y;
return { x: rotatedX, y: rotatedY };
}
window.rotatePoint = rotatePoint;
export class CanvasLayer {
static NAME_PREFIX = 'layer';
static LAYER_NAME = `${CanvasLayer.NAME_PREFIX}_layer`;
@ -51,8 +33,8 @@ export class CanvasLayer {
static OBJECT_GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_object-group`;
static BBOX_NAME = `${CanvasLayer.NAME_PREFIX}_bbox`;
private drawingBuffer: BrushLine | EraserLine | RectShape | null;
private state: LayerEntity;
_drawingBuffer: BrushLine | EraserLine | RectShape | null;
_state: LayerEntity;
id: string;
manager: CanvasManager;
@ -66,10 +48,11 @@ export class CanvasLayer {
};
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
log: Logger;
bboxNeedsUpdate: boolean;
_log: Logger;
_bboxNeedsUpdate: boolean;
_isFirstRender: boolean;
isTransforming: boolean;
isFirstRender: boolean;
rect: Rect;
bbox: Rect;
@ -113,17 +96,7 @@ export class CanvasLayer {
this.konva.layer.add(this.konva.bbox);
this.konva.transformer.on('transformstart', () => {
console.log('>>> transformstart');
console.log('interactionRect', {
x: this.konva.interactionRect.x(),
y: this.konva.interactionRect.y(),
scaleX: this.konva.interactionRect.scaleX(),
scaleY: this.konva.interactionRect.scaleY(),
width: this.konva.interactionRect.width(),
height: this.konva.interactionRect.height(),
});
this.logBbox('transformstart bbox');
console.log('this.state.position', this.state.position);
this.logDebugInfo("'transformstart' fired");
});
this.konva.transformer.on('transform', () => {
@ -152,17 +125,7 @@ export class CanvasLayer {
// this.konva.interactionRect.scaleY(scaleY);
// this.konva.interactionRect.rotation(0);
console.log('>>> transform');
console.log('activeAnchor', this.konva.transformer.getActiveAnchor());
console.log('interactionRect', {
x: this.konva.interactionRect.x(),
y: this.konva.interactionRect.y(),
scaleX: this.konva.interactionRect.scaleX(),
scaleY: this.konva.interactionRect.scaleY(),
width: this.konva.interactionRect.width(),
height: this.konva.interactionRect.height(),
rotation: this.konva.interactionRect.rotation(),
});
this.logDebugInfo("'transform' fired");
this.konva.objectGroup.setAttrs({
x: this.konva.interactionRect.x(),
@ -171,33 +134,10 @@ export class CanvasLayer {
scaleY: this.konva.interactionRect.scaleY(),
rotation: this.konva.interactionRect.rotation(),
});
console.log('objectGroup', {
x: this.konva.objectGroup.x(),
y: this.konva.objectGroup.y(),
scaleX: this.konva.objectGroup.scaleX(),
scaleY: this.konva.objectGroup.scaleY(),
offsetX: this.konva.objectGroup.offsetX(),
offsetY: this.konva.objectGroup.offsetY(),
width: this.konva.objectGroup.width(),
height: this.konva.objectGroup.height(),
rotation: this.konva.objectGroup.rotation(),
});
});
this.konva.transformer.on('transformend', () => {
// this.offsetX = this.konva.interactionRect.x() - this.state.position.x;
// this.offsetY = this.konva.interactionRect.y() - this.state.position.y;
// this.width = Math.round(this.konva.interactionRect.width() * this.konva.interactionRect.scaleX());
// this.height = Math.round(this.konva.interactionRect.height() * this.konva.interactionRect.scaleY());
// this.manager.stateApi.onPosChanged(
// {
// id: this.id,
// position: { x: this.konva.objectGroup.x(), y: this.konva.objectGroup.y() },
// },
// 'layer'
// );
this.logBbox('transformend bbox');
this.logDebugInfo("'transformend' fired");
});
this.konva.interactionRect.on('dragmove', () => {
@ -220,7 +160,7 @@ export class CanvasLayer {
});
});
this.konva.interactionRect.on('dragend', () => {
this.logBbox('dragend bbox');
this.logDebugInfo("'dragend' fired");
if (this.isTransforming) {
// When the user cancels the transformation, we need to reset the layer, so we should not update the layer's
@ -241,38 +181,38 @@ export class CanvasLayer {
});
this.objects = new Map();
this.drawingBuffer = null;
this.state = state;
this._drawingBuffer = null;
this._state = state;
this.rect = this.getDefaultRect();
this.bbox = this.getDefaultRect();
this.bboxNeedsUpdate = true;
this._bboxNeedsUpdate = true;
this.isTransforming = false;
this.isFirstRender = true;
this.log = this.manager.getLogger(`layer_${this.id}`);
this._isFirstRender = true;
this._log = this.manager.getLogger(`layer_${this.id}`);
}
destroy(): void {
this.log.debug(`Layer ${this.id} - destroying`);
this._log.debug(`Layer ${this.id} - destroying`);
this.konva.layer.destroy();
}
getDrawingBuffer() {
return this.drawingBuffer;
return this._drawingBuffer;
}
async setDrawingBuffer(obj: BrushLine | EraserLine | RectShape | null) {
if (obj) {
this.drawingBuffer = obj;
await this._renderObject(this.drawingBuffer, true);
this._drawingBuffer = obj;
await this._renderObject(this._drawingBuffer, true);
} else {
this.drawingBuffer = null;
this._drawingBuffer = null;
}
}
async finalizeDrawingBuffer() {
if (!this.drawingBuffer) {
if (!this._drawingBuffer) {
return;
}
const drawingBuffer = this.drawingBuffer;
const drawingBuffer = this._drawingBuffer;
this.setDrawingBuffer(null);
// We need to give the objects a fresh ID else they will be considered the same object when they are re-rendered as
@ -291,44 +231,50 @@ export class CanvasLayer {
}
async update(arg?: { state: LayerEntity; toolState: CanvasV2State['tool']; isSelected: boolean }) {
const state = get(arg, 'state', this.state);
const state = get(arg, 'state', this._state);
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id));
if (!this.isFirstRender && state === this.state) {
this.log.trace('State unchanged, skipping update');
if (!this._isFirstRender && state === this._state) {
this._log.trace('State unchanged, skipping update');
return;
}
this.log.debug('Updating');
this._log.debug('Updating');
const { position, objects, opacity, isEnabled } = state;
if (this.isFirstRender || position !== this.state.position) {
if (this._isFirstRender || position !== this._state.position) {
await this.updatePosition({ position });
}
if (this.isFirstRender || objects !== this.state.objects) {
if (this._isFirstRender || objects !== this._state.objects) {
await this.updateObjects({ objects });
}
if (this.isFirstRender || opacity !== this.state.opacity) {
if (this._isFirstRender || opacity !== this._state.opacity) {
await this.updateOpacity({ opacity });
}
if (this.isFirstRender || isEnabled !== this.state.isEnabled) {
if (this._isFirstRender || isEnabled !== this._state.isEnabled) {
await this.updateVisibility({ isEnabled });
}
await this.updateInteraction({ toolState, isSelected });
this.state = state;
if (this._isFirstRender) {
await this.updateBbox();
}
this._state = state;
this._isFirstRender = false;
}
async updateVisibility(arg?: { isEnabled: boolean }) {
this.log.trace('Updating visibility');
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);
const hasObjects = this.objects.size > 0 || this.drawingBuffer !== null;
this._log.trace('Updating visibility');
const isEnabled = get(arg, 'isEnabled', this._state.isEnabled);
const hasObjects = this.objects.size > 0 || this._drawingBuffer !== null;
this.konva.layer.visible(isEnabled || hasObjects);
}
async updatePosition(arg?: { position: Coordinate }) {
this.log.trace('Updating position');
const position = get(arg, 'position', this.state.position);
this._log.trace('Updating position');
const position = get(arg, 'position', this._state.position);
const bboxPadding = this.manager.getScaledBboxPadding();
this.konva.objectGroup.setAttrs({
@ -348,9 +294,9 @@ export class CanvasLayer {
}
async updateObjects(arg?: { objects: LayerEntity['objects'] }) {
this.log.trace('Updating objects');
this._log.trace('Updating objects');
const objects = get(arg, 'objects', this.state.objects);
const objects = get(arg, 'objects', this._state.objects);
const objectIds = objects.map(mapId);
@ -358,7 +304,7 @@ export class CanvasLayer {
// Destroy any objects that are no longer in state
for (const object of this.objects.values()) {
if (!objectIds.includes(object.id) && object.id !== this.drawingBuffer?.id) {
if (!objectIds.includes(object.id) && object.id !== this._drawingBuffer?.id) {
this.objects.delete(object.id);
object.destroy();
didUpdate = true;
@ -371,8 +317,8 @@ export class CanvasLayer {
}
}
if (this.drawingBuffer) {
if (await this._renderObject(this.drawingBuffer)) {
if (this._drawingBuffer) {
if (await this._renderObject(this._drawingBuffer)) {
didUpdate = true;
}
}
@ -383,15 +329,15 @@ export class CanvasLayer {
}
async 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);
}
async updateInteraction(arg?: { toolState: CanvasV2State['tool']; isSelected: boolean }) {
this.log.trace('Updating interaction');
this._log.trace('Updating interaction');
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id));
@ -440,42 +386,49 @@ export class CanvasLayer {
}
async updateBbox() {
this.log.trace('Updating bbox');
this._log.trace('Updating bbox');
// If the bbox has no width or height, that means the layer is fully transparent. This can happen if it is only
// eraser lines, fully clipped brush lines or if it has been fully erased. In this case, we should reset the layer
// so we aren't drawing shapes that do not render anything.
// eraser lines, fully clipped brush lines or if it has been fully erased.
if (this.bbox.width === 0 || this.bbox.height === 0) {
this.manager.stateApi.onEntityReset({ id: this.id }, 'layer');
if (this.objects.size > 0) {
// The layer is fully transparent but has objects - reset it
this.manager.stateApi.onEntityReset({ id: this.id }, 'layer');
}
this.konva.bbox.visible(false);
this.konva.interactionRect.visible(false);
return;
}
this.konva.bbox.visible(true);
this.konva.interactionRect.visible(true);
const onePixel = this.manager.getScaledPixel();
const bboxPadding = this.manager.getScaledBboxPadding();
this.konva.bbox.setAttrs({
x: this.state.position.x + this.bbox.x - bboxPadding,
y: this.state.position.y + this.bbox.y - bboxPadding,
x: this._state.position.x + this.bbox.x - bboxPadding,
y: this._state.position.y + this.bbox.y - bboxPadding,
width: this.bbox.width + bboxPadding * 2,
height: this.bbox.height + bboxPadding * 2,
strokeWidth: onePixel,
});
this.konva.interactionRect.setAttrs({
x: this.state.position.x + this.bbox.x,
y: this.state.position.y + this.bbox.y,
x: this._state.position.x + this.bbox.x,
y: this._state.position.y + this.bbox.y,
width: this.bbox.width,
height: this.bbox.height,
});
this.konva.objectGroup.setAttrs({
x: this.state.position.x + this.bbox.x,
y: this.state.position.y + this.bbox.y,
x: this._state.position.x + this.bbox.x,
y: this._state.position.y + this.bbox.y,
offsetX: this.bbox.x,
offsetY: this.bbox.y,
});
}
async syncStageScale() {
this.log.trace('Syncing scale to stage');
this._log.trace('Syncing scale to stage');
const onePixel = this.manager.getScaledPixel();
const bboxPadding = this.manager.getScaledBboxPadding();
@ -552,7 +505,7 @@ export class CanvasLayer {
}
async startTransform() {
this.log.debug('Starting transform');
this._log.debug('Starting transform');
this.isTransforming = true;
// When transforming, we want the stage to still be movable if the view tool is selected. If the transformer or
@ -583,14 +536,15 @@ export class CanvasLayer {
}
async applyTransform() {
this.log.debug('Applying transform');
this._log.debug('Applying transform');
this.isTransforming = false;
const objectGroupClone = this.konva.objectGroup.clone();
const interactionRectClone = this.konva.interactionRect.clone();
const rect = interactionRectClone.getClientRect();
const blob = await konvaNodeToBlob(objectGroupClone, rect);
previewBlob(blob, 'transformed layer');
if (this.manager._isDebugging) {
previewBlob(blob, 'transformed layer');
}
const imageDTO = await uploadImage(blob, `${this.id}_transform.png`, 'other', true);
const { dispatch } = getStore();
dispatch(layerRasterized({ id: this.id, imageDTO, position: { x: rect.x, y: rect.y } }));
@ -599,11 +553,11 @@ export class CanvasLayer {
}
async cancelTransform() {
this.log.debug('Canceling transform');
this._log.debug('Canceling transform');
this.isTransforming = false;
this.resetScale();
await this.updatePosition({ position: this.state.position });
await this.updatePosition({ position: this._state.position });
await this.updateBbox();
await this.updateInteraction({
toolState: this.manager.stateApi.getToolState(),
@ -616,7 +570,7 @@ export class CanvasLayer {
}
calculateBbox = debounce(() => {
this.log.debug('Calculating bbox');
this._log.debug('Calculating bbox');
if (this.objects.size === 0) {
this.rect = this.getDefaultRect();
@ -625,7 +579,6 @@ export class CanvasLayer {
return;
}
let needsPixelBbox = false;
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
/**
@ -640,6 +593,7 @@ export class CanvasLayer {
* TODO(psyche): Using pixel data is slow. Is it possible to be clever and somehow subtract the eraser lines and
* clipped areas from the client rect?
*/
let needsPixelBbox = false;
for (const obj of this.objects.values()) {
const isEraserLine = obj instanceof CanvasEraserLine;
const isImage = obj instanceof CanvasImage;
@ -653,7 +607,7 @@ export class CanvasLayer {
if (!needsPixelBbox) {
this.rect = deepClone(rect);
this.bbox = deepClone(rect);
this.log.trace({ bbox: this.bbox, rect: this.rect }, 'Got bbox from client rect');
this._log.trace({ bbox: this.bbox, rect: this.rect }, 'Got bbox from client rect');
this.updateBbox();
return;
}
@ -682,19 +636,42 @@ export class CanvasLayer {
} else {
this.bbox = deepClone(rect);
}
this.log.trace({ bbox: this.bbox, rect: this.rect, extents }, `Got bbox from worker`);
this._log.trace({ bbox: this.bbox, rect: this.rect, extents }, `Got bbox from worker`);
this.updateBbox();
clone.destroy();
}
);
}, CanvasManager.BBOX_DEBOUNCE_MS);
logBbox(msg: string = 'bbox') {
console.log(msg, {
x: this.state.position.x,
y: this.state.position.y,
rect: deepClone(this.rect),
bbox: deepClone(this.bbox),
});
logDebugInfo(msg = 'Debug info') {
const debugInfo = {
id: this.id,
state: this._state,
rect: this.rect,
bbox: this.bbox,
objects: Array.from(this.objects.values()).map((obj) => obj.id),
isTransforming: this.isTransforming,
interactionRectAttrs: {
x: this.konva.interactionRect.x(),
y: this.konva.interactionRect.y(),
scaleX: this.konva.interactionRect.scaleX(),
scaleY: this.konva.interactionRect.scaleY(),
width: this.konva.interactionRect.width(),
height: this.konva.interactionRect.height(),
rotation: this.konva.interactionRect.rotation(),
},
objectGroupAttrs: {
x: this.konva.objectGroup.x(),
y: this.konva.objectGroup.y(),
scaleX: this.konva.objectGroup.scaleX(),
scaleY: this.konva.objectGroup.scaleY(),
width: this.konva.objectGroup.width(),
height: this.konva.objectGroup.height(),
rotation: this.konva.objectGroup.rotation(),
offsetX: this.konva.objectGroup.offsetX(),
offsetY: this.konva.objectGroup.offsetY(),
},
};
this._log.debug(debugInfo, msg);
}
}

View File

@ -64,7 +64,7 @@ type Util = {
export const $canvasManager = atom<CanvasManager | null>(null);
export class CanvasManager {
private static BBOX_PADDING_PX = 5;
static BBOX_PADDING_PX = 5;
static BBOX_DEBOUNCE_MS = 300;
stage: Konva.Stage;
@ -82,13 +82,15 @@ export class CanvasManager {
log: Logger;
workerLog: Logger;
_isDebugging: boolean;
onTransform: ((isTransforming: boolean) => void) | null;
private store: Store<RootState>;
private isFirstRender: boolean;
private prevState: CanvasV2State;
private worker: Worker;
private tasks: Map<string, { task: GetBboxTask; onComplete: (extents: Extents | null) => void }>;
_store: Store<RootState>;
_isFirstRender: boolean;
_prevState: CanvasV2State;
_worker: Worker;
_tasks: Map<string, { task: GetBboxTask; onComplete: (extents: Extents | null) => void }>;
constructor(
stage: Konva.Stage,
@ -99,10 +101,10 @@ export class CanvasManager {
) {
this.stage = stage;
this.container = container;
this.store = store;
this.stateApi = new CanvasStateApi(this.store);
this.prevState = this.stateApi.getState();
this.isFirstRender = true;
this._store = store;
this.stateApi = new CanvasStateApi(this._store);
this._prevState = this.stateApi.getState();
this._isFirstRender = true;
this.log = logger('canvas');
this.workerLog = logger('worker');
@ -133,9 +135,9 @@ export class CanvasManager {
this.initialImage = new CanvasInitialImage(this.stateApi.getInitialImageState(), this);
this.stage.add(this.initialImage.konva.layer);
this.worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module', name: 'worker' });
this.tasks = new Map();
this.worker.onmessage = (event: MessageEvent<ExtentsResult | WorkerLogMessage>) => {
this._worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module', name: 'worker' });
this._tasks = new Map();
this._worker.onmessage = (event: MessageEvent<ExtentsResult | WorkerLogMessage>) => {
const { type, data } = event.data;
if (type === 'log') {
if (data.ctx) {
@ -144,20 +146,30 @@ export class CanvasManager {
this.workerLog[data.level](data.message);
}
} else if (type === 'extents') {
const task = this.tasks.get(data.id);
const task = this._tasks.get(data.id);
if (!task) {
return;
}
task.onComplete(data.extents);
}
};
this.worker.onerror = (event) => {
this._worker.onerror = (event) => {
this.log.error({ message: event.message }, 'Worker error');
};
this.worker.onmessageerror = () => {
this._worker.onmessageerror = () => {
this.log.error('Worker message error');
};
this.onTransform = null;
this._isDebugging = false;
}
enableDebugging() {
this._isDebugging = true;
this.logDebugInfo();
}
disableDebugging() {
this._isDebugging = false;
}
getLogger(namespace: string) {
@ -171,8 +183,8 @@ export class CanvasManager {
type: 'get_bbox',
data: { ...data, id },
};
this.tasks.set(id, { task, onComplete });
this.worker.postMessage(task, [data.buffer]);
this._tasks.set(id, { task, onComplete });
this._worker.postMessage(task, [data.buffer]);
}
async renderInitialImage() {
@ -306,12 +318,12 @@ export class CanvasManager {
render = async () => {
const state = this.stateApi.getState();
if (this.prevState === state && !this.isFirstRender) {
if (this._prevState === state && !this._isFirstRender) {
this.log.trace('No changes detected, skipping render');
return;
}
if (this.isFirstRender || state.layers.entities !== this.prevState.layers.entities) {
if (this._isFirstRender || state.layers.entities !== this._prevState.layers.entities) {
this.log.debug('Rendering layers');
for (const canvasLayer of this.layers.values()) {
@ -339,9 +351,9 @@ export class CanvasManager {
}
if (
this.isFirstRender ||
state.tool.selected !== this.prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
this._isFirstRender ||
state.tool.selected !== this._prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
) {
this.log.debug('Updating interaction');
for (const layer of this.layers.values()) {
@ -350,89 +362,89 @@ export class CanvasManager {
}
if (
this.isFirstRender ||
state.initialImage !== this.prevState.initialImage ||
state.bbox.rect !== this.prevState.bbox.rect ||
state.tool.selected !== this.prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
this._isFirstRender ||
state.initialImage !== this._prevState.initialImage ||
state.bbox.rect !== this._prevState.bbox.rect ||
state.tool.selected !== this._prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
) {
this.log.debug('Rendering initial image');
await this.renderInitialImage();
}
if (
this.isFirstRender ||
state.regions.entities !== this.prevState.regions.entities ||
state.settings.maskOpacity !== this.prevState.settings.maskOpacity ||
state.tool.selected !== this.prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
this._isFirstRender ||
state.regions.entities !== this._prevState.regions.entities ||
state.settings.maskOpacity !== this._prevState.settings.maskOpacity ||
state.tool.selected !== this._prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
) {
this.log.debug('Rendering regions');
await this.renderRegions();
}
if (
this.isFirstRender ||
state.inpaintMask !== this.prevState.inpaintMask ||
state.settings.maskOpacity !== this.prevState.settings.maskOpacity ||
state.tool.selected !== this.prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
this._isFirstRender ||
state.inpaintMask !== this._prevState.inpaintMask ||
state.settings.maskOpacity !== this._prevState.settings.maskOpacity ||
state.tool.selected !== this._prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
) {
this.log.debug('Rendering inpaint mask');
await this.renderInpaintMask();
}
if (
this.isFirstRender ||
state.controlAdapters.entities !== this.prevState.controlAdapters.entities ||
state.tool.selected !== this.prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
this._isFirstRender ||
state.controlAdapters.entities !== this._prevState.controlAdapters.entities ||
state.tool.selected !== this._prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
) {
this.log.debug('Rendering control adapters');
await this.renderControlAdapters();
}
if (
this.isFirstRender ||
state.bbox !== this.prevState.bbox ||
state.tool.selected !== this.prevState.tool.selected ||
state.session.isActive !== this.prevState.session.isActive
this._isFirstRender ||
state.bbox !== this._prevState.bbox ||
state.tool.selected !== this._prevState.tool.selected ||
state.session.isActive !== this._prevState.session.isActive
) {
this.log.debug('Rendering generation bbox');
await this.preview.bbox.render();
}
if (
this.isFirstRender ||
state.layers !== this.prevState.layers ||
state.controlAdapters !== this.prevState.controlAdapters ||
state.regions !== this.prevState.regions
this._isFirstRender ||
state.layers !== this._prevState.layers ||
state.controlAdapters !== this._prevState.controlAdapters ||
state.regions !== this._prevState.regions
) {
// this.log.debug('Updating entity bboxes');
// debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged);
}
if (this.isFirstRender || state.session !== this.prevState.session) {
if (this._isFirstRender || state.session !== this._prevState.session) {
this.log.debug('Rendering staging area');
await this.preview.stagingArea.render();
}
if (
this.isFirstRender ||
state.layers.entities !== this.prevState.layers.entities ||
state.controlAdapters.entities !== this.prevState.controlAdapters.entities ||
state.regions.entities !== this.prevState.regions.entities ||
state.inpaintMask !== this.prevState.inpaintMask ||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
this._isFirstRender ||
state.layers.entities !== this._prevState.layers.entities ||
state.controlAdapters.entities !== this._prevState.controlAdapters.entities ||
state.regions.entities !== this._prevState.regions.entities ||
state.inpaintMask !== this._prevState.inpaintMask ||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
) {
this.log.debug('Arranging entities');
await this.arrangeEntities();
}
this.prevState = state;
this._prevState = state;
if (this.isFirstRender) {
this.isFirstRender = false;
if (this._isFirstRender) {
this._isFirstRender = false;
}
};
@ -448,7 +460,7 @@ export class CanvasManager {
resizeObserver.observe(this.container);
this.fitStageToContainer();
const unsubscribeRenderer = this.store.subscribe(this.render);
const unsubscribeRenderer = this._store.subscribe(this.render);
// When we this flag, we need to render the staging area
$shouldShowStagedImage.subscribe(async (shouldShowStagedImage, prevShouldShowStagedImage) => {

View File

@ -26,7 +26,7 @@ export class CanvasRect {
this.id = id;
this.parent = parent;
this.parent.log.trace(`Creating rect ${this.id}`);
this.parent._log.trace(`Creating rect ${this.id}`);
this.konva = {
group: new Konva.Group({ name: CanvasRect.GROUP_NAME, listening: false }),
@ -47,7 +47,7 @@ export class CanvasRect {
async update(state: RectShape, force?: boolean): Promise<boolean> {
if (this.state !== state || force) {
this.parent.log.trace(`Updating rect ${this.id}`);
this.parent._log.trace(`Updating rect ${this.id}`);
const { x, y, width, height, color } = state;
this.konva.rect.setAttrs({
x,
@ -64,7 +64,7 @@ export class CanvasRect {
}
destroy() {
this.parent.log.trace(`Destroying rect ${this.id}`);
this.parent._log.trace(`Destroying rect ${this.id}`);
this.konva.group.destroy();
}
}