mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
157 lines
5.0 KiB
TypeScript
157 lines
5.0 KiB
TypeScript
import { deepClone } from 'common/util/deepClone';
|
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
|
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
|
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
|
import type { CanvasLayerState, CanvasV2State, GetLoggingContext } from 'features/controlLayers/store/types';
|
|
import Konva from 'konva';
|
|
import { get } from 'lodash-es';
|
|
import type { Logger } from 'roarr';
|
|
|
|
export class CanvasLayer {
|
|
static TYPE = 'layer' as const;
|
|
static KONVA_LAYER_NAME = `${CanvasLayer.TYPE}_layer`;
|
|
static KONVA_OBJECT_GROUP_NAME = `${CanvasLayer.TYPE}_object-group`;
|
|
|
|
id: string;
|
|
type = CanvasLayer.TYPE;
|
|
manager: CanvasManager;
|
|
log: Logger;
|
|
getLoggingContext: GetLoggingContext;
|
|
|
|
state: CanvasLayerState;
|
|
|
|
konva: {
|
|
layer: Konva.Layer;
|
|
};
|
|
transformer: CanvasTransformer;
|
|
renderer: CanvasObjectRenderer;
|
|
|
|
isFirstRender: boolean = true;
|
|
bboxNeedsUpdate: boolean = true;
|
|
|
|
constructor(state: CanvasLayerState, manager: CanvasManager) {
|
|
this.id = state.id;
|
|
this.manager = manager;
|
|
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
|
|
this.log = this.manager.buildLogger(this.getLoggingContext);
|
|
this.log.debug({ state }, 'Creating layer');
|
|
|
|
this.konva = {
|
|
layer: new Konva.Layer({
|
|
id: this.id,
|
|
name: CanvasLayer.KONVA_LAYER_NAME,
|
|
listening: false,
|
|
imageSmoothingEnabled: false,
|
|
}),
|
|
};
|
|
|
|
this.transformer = new CanvasTransformer(this);
|
|
this.renderer = new CanvasObjectRenderer(this);
|
|
|
|
this.state = state;
|
|
}
|
|
|
|
destroy = (): void => {
|
|
this.log.debug('Destroying layer');
|
|
// 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();
|
|
};
|
|
|
|
update = async (arg?: { state: CanvasLayerState; toolState: CanvasV2State['tool']; isSelected: boolean }) => {
|
|
const state = get(arg, 'state', this.state);
|
|
|
|
if (!this.isFirstRender && state === this.state) {
|
|
this.log.trace('State unchanged, skipping update');
|
|
return;
|
|
}
|
|
|
|
this.log.debug('Updating');
|
|
const { position, objects, opacity, isEnabled } = state;
|
|
|
|
if (this.isFirstRender || isEnabled !== this.state.isEnabled) {
|
|
this.updateVisibility({ isEnabled });
|
|
}
|
|
if (this.isFirstRender || objects !== this.state.objects) {
|
|
await this.updateObjects({ objects });
|
|
}
|
|
if (this.isFirstRender || position !== this.state.position) {
|
|
this.transformer.updatePosition({ position });
|
|
}
|
|
if (this.isFirstRender || opacity !== this.state.opacity) {
|
|
this.updateOpacity({ opacity });
|
|
}
|
|
// this.transformer.syncInteractionState();
|
|
|
|
if (this.isFirstRender) {
|
|
this.transformer.updateBbox();
|
|
}
|
|
|
|
this.state = state;
|
|
this.isFirstRender = false;
|
|
};
|
|
|
|
updateVisibility = (arg?: { isEnabled: boolean }) => {
|
|
this.log.trace('Updating visibility');
|
|
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);
|
|
this.konva.layer.visible(isEnabled);
|
|
};
|
|
|
|
updateObjects = async (arg?: { objects: CanvasLayerState['objects'] }) => {
|
|
this.log.trace('Updating objects');
|
|
|
|
const objects = get(arg, 'objects', this.state.objects);
|
|
|
|
const didUpdate = await this.renderer.render(objects);
|
|
|
|
if (didUpdate) {
|
|
this.transformer.requestRectCalculation();
|
|
}
|
|
};
|
|
|
|
updateOpacity = (arg?: { opacity: number }) => {
|
|
this.log.trace('Updating opacity');
|
|
const opacity = get(arg, 'opacity', this.state.opacity);
|
|
this.renderer.konva.objectGroup.opacity(opacity);
|
|
};
|
|
|
|
repr = () => {
|
|
return {
|
|
id: this.id,
|
|
type: CanvasLayer.TYPE,
|
|
state: deepClone(this.state),
|
|
bboxNeedsUpdate: this.bboxNeedsUpdate,
|
|
transformer: this.transformer.repr(),
|
|
renderer: this.renderer.repr(),
|
|
};
|
|
};
|
|
|
|
logDebugInfo(msg = 'Debug info') {
|
|
const info = {
|
|
repr: this.repr(),
|
|
interactionRectAttrs: {
|
|
x: this.transformer.konva.proxyRect.x(),
|
|
y: this.transformer.konva.proxyRect.y(),
|
|
scaleX: this.transformer.konva.proxyRect.scaleX(),
|
|
scaleY: this.transformer.konva.proxyRect.scaleY(),
|
|
width: this.transformer.konva.proxyRect.width(),
|
|
height: this.transformer.konva.proxyRect.height(),
|
|
rotation: this.transformer.konva.proxyRect.rotation(),
|
|
},
|
|
objectGroupAttrs: {
|
|
x: this.renderer.konva.objectGroup.x(),
|
|
y: this.renderer.konva.objectGroup.y(),
|
|
scaleX: this.renderer.konva.objectGroup.scaleX(),
|
|
scaleY: this.renderer.konva.objectGroup.scaleY(),
|
|
width: this.renderer.konva.objectGroup.width(),
|
|
height: this.renderer.konva.objectGroup.height(),
|
|
rotation: this.renderer.konva.objectGroup.rotation(),
|
|
offsetX: this.renderer.konva.objectGroup.offsetX(),
|
|
offsetY: this.renderer.konva.objectGroup.offsetY(),
|
|
},
|
|
};
|
|
this.log.trace(info, msg);
|
|
}
|
|
}
|