mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): document & clean up object renderer
This commit is contained in:
parent
1095b7c37f
commit
b6d845a4d0
@ -23,7 +23,6 @@ export class CanvasBrushLineRenderer {
|
||||
group: Konva.Group;
|
||||
line: Konva.Line;
|
||||
};
|
||||
isFirstRender: boolean = false;
|
||||
|
||||
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
|
||||
const { id, strokeWidth, clip, color, points } = state;
|
||||
@ -60,10 +59,8 @@ export class CanvasBrushLineRenderer {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
update(state: CanvasBrushLineState, force = this.isFirstRender): boolean {
|
||||
update(state: CanvasBrushLineState, force = false): boolean {
|
||||
if (force || this.state !== state) {
|
||||
this.isFirstRender = false;
|
||||
|
||||
this.log.trace({ state }, 'Updating brush line');
|
||||
const { points, color, clip, strokeWidth } = state;
|
||||
this.konva.line.setAttrs({
|
||||
@ -95,7 +92,6 @@ export class CanvasBrushLineRenderer {
|
||||
id: this.id,
|
||||
type: CanvasBrushLineRenderer.TYPE,
|
||||
parent: this.parent.id,
|
||||
isFirstRender: this.isFirstRender,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ export class CanvasEraserLineRenderer {
|
||||
log: Logger;
|
||||
getLoggingContext: GetLoggingContext;
|
||||
|
||||
isFirstRender: boolean = false;
|
||||
state: CanvasEraserLineState;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
@ -59,10 +58,8 @@ export class CanvasEraserLineRenderer {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
update(state: CanvasEraserLineState, force = this.isFirstRender): boolean {
|
||||
update(state: CanvasEraserLineState, force = false): boolean {
|
||||
if (force || this.state !== state) {
|
||||
this.isFirstRender = false;
|
||||
|
||||
this.log.trace({ state }, 'Updating eraser line');
|
||||
const { points, clip, strokeWidth } = state;
|
||||
this.konva.line.setAttrs({
|
||||
@ -93,7 +90,6 @@ export class CanvasEraserLineRenderer {
|
||||
id: this.id,
|
||||
type: CanvasEraserLineRenderer.TYPE,
|
||||
parent: this.parent.id,
|
||||
isFirstRender: this.isFirstRender,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ export class CanvasImageRenderer {
|
||||
imageName: string | null;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
isFirstRender: boolean = true;
|
||||
|
||||
constructor(state: CanvasImageState, parent: CanvasObjectRenderer) {
|
||||
const { id, width, height, x, y } = state;
|
||||
@ -138,10 +137,8 @@ export class CanvasImageRenderer {
|
||||
}
|
||||
};
|
||||
|
||||
update = async (state: CanvasImageState, force = this.isFirstRender): Promise<boolean> => {
|
||||
update = async (state: CanvasImageState, force = false): Promise<boolean> => {
|
||||
if (force || this.state !== state) {
|
||||
this.isFirstRender = false;
|
||||
|
||||
this.log.trace({ state }, 'Updating image');
|
||||
|
||||
const { width, height, x, y, image, filters } = state;
|
||||
@ -183,7 +180,6 @@ export class CanvasImageRenderer {
|
||||
imageName: this.imageName,
|
||||
isLoading: this.isLoading,
|
||||
isError: this.isError,
|
||||
isFirstRender: this.isFirstRender,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
};
|
||||
|
@ -259,7 +259,6 @@ export class CanvasLayer {
|
||||
const { dispatch } = getStore();
|
||||
const imageObject = imageDTOToImageObject(imageDTO);
|
||||
await this.renderer.renderObject(imageObject, true);
|
||||
this.renderer.hideAll([imageObject.id]);
|
||||
this.resetScale();
|
||||
dispatch(layerRasterized({ id: this.id, imageObject, position: { x: Math.round(rect.x), y: Math.round(rect.y) } }));
|
||||
};
|
||||
|
@ -16,12 +16,17 @@ import type {
|
||||
import type { Logger } from 'roarr';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
/**
|
||||
* Union of all object renderers.
|
||||
*/
|
||||
type AnyObjectRenderer = CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer | CanvasImageRenderer;
|
||||
/**
|
||||
* Union of all object states.
|
||||
*/
|
||||
type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImageState | CanvasRectState;
|
||||
|
||||
export class CanvasObjectRenderer {
|
||||
static TYPE = 'object_renderer';
|
||||
static OBJECT_GROUP_NAME = `${CanvasObjectRenderer.TYPE}_group`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasLayer;
|
||||
@ -29,9 +34,16 @@ export class CanvasObjectRenderer {
|
||||
log: Logger;
|
||||
getLoggingContext: (extra?: JSONObject) => JSONObject;
|
||||
|
||||
isFirstRender: boolean = true;
|
||||
isRendering: boolean = false;
|
||||
/**
|
||||
* A buffer object state that is rendered separately from the other objects. This is used for objects that are being
|
||||
* drawn in real-time, such as brush lines. The buffer object state only exists in this renderer and is not part of
|
||||
* the application state until it is committed.
|
||||
*/
|
||||
buffer: AnyObjectState | null = null;
|
||||
|
||||
/**
|
||||
* A map of object renderers, keyed by their ID.
|
||||
*/
|
||||
renderers: Map<string, AnyObjectRenderer> = new Map();
|
||||
|
||||
constructor(parent: CanvasLayer) {
|
||||
@ -43,8 +55,12 @@ export class CanvasObjectRenderer {
|
||||
this.log.trace('Creating object renderer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the given objects.
|
||||
* @param objectStates The objects to render.
|
||||
* @returns A promise that resolves to a boolean, indicating if any of the objects were rendered.
|
||||
*/
|
||||
render = async (objectStates: AnyObjectState[]): Promise<boolean> => {
|
||||
this.isRendering = true;
|
||||
let didRender = false;
|
||||
const objectIds = objectStates.map((objectState) => objectState.id);
|
||||
|
||||
@ -64,17 +80,26 @@ export class CanvasObjectRenderer {
|
||||
didRender = (await this.renderObject(this.buffer)) || didRender;
|
||||
}
|
||||
|
||||
this.isRendering = false;
|
||||
this.isFirstRender = false;
|
||||
|
||||
return didRender;
|
||||
};
|
||||
|
||||
renderObject = async (objectState: AnyObjectState, force?: boolean): Promise<boolean> => {
|
||||
/**
|
||||
* Renders the given object. If the object renderer does not exist, it will be created and its Konva group added to the
|
||||
* parent entity's object group.
|
||||
* @param objectState The object's state.
|
||||
* @param force Whether to force the object to render, even if it has not changed. If omitted, the object renderer
|
||||
* will only render if the object state has changed. The exception is the first render, where the object will always
|
||||
* be rendered.
|
||||
* @returns A promise that resolves to a boolean, indicating if the object was rendered.
|
||||
*/
|
||||
renderObject = async (objectState: AnyObjectState, force = false): Promise<boolean> => {
|
||||
let didRender = false;
|
||||
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
|
||||
const isFirstRender = renderer === undefined;
|
||||
|
||||
if (objectState.type === 'brush_line') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasBrushLineRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
@ -83,9 +108,8 @@ export class CanvasObjectRenderer {
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
|
||||
didRender = renderer.update(objectState, force);
|
||||
didRender = renderer.update(objectState, force || isFirstRender);
|
||||
} else if (objectState.type === 'eraser_line') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasEraserLineRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
@ -94,9 +118,8 @@ export class CanvasObjectRenderer {
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
|
||||
didRender = renderer.update(objectState, force);
|
||||
didRender = renderer.update(objectState, force || isFirstRender);
|
||||
} else if (objectState.type === 'rect') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasRectRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
@ -105,9 +128,8 @@ export class CanvasObjectRenderer {
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
|
||||
didRender = renderer.update(objectState, force);
|
||||
didRender = renderer.update(objectState, force || isFirstRender);
|
||||
} else if (objectState.type === 'image') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasImageRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
@ -115,28 +137,43 @@ export class CanvasObjectRenderer {
|
||||
this.renderers.set(renderer.id, renderer);
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
didRender = await renderer.update(objectState, force);
|
||||
didRender = await renderer.update(objectState, force || isFirstRender);
|
||||
}
|
||||
|
||||
this.isFirstRender = false;
|
||||
return didRender;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the renderer has a buffer object to render.
|
||||
* @returns Whether the renderer has a buffer object to render.
|
||||
*/
|
||||
hasBuffer = (): boolean => {
|
||||
return this.buffer !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the buffer object state to render.
|
||||
* @param objectState The object state to set as the buffer.
|
||||
* @returns A promise that resolves to a boolean, indicating if the object was rendered.
|
||||
*/
|
||||
setBuffer = async (objectState: AnyObjectState): Promise<boolean> => {
|
||||
this.buffer = objectState;
|
||||
return await this.renderObject(this.buffer, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the buffer object state.
|
||||
*/
|
||||
clearBuffer = () => {
|
||||
this.buffer = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Commits the current buffer object, pushing the buffer object state back to the application state.
|
||||
*/
|
||||
commitBuffer = () => {
|
||||
if (!this.buffer) {
|
||||
this.log.warn('No buffer object to commit');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -181,18 +218,17 @@ export class CanvasObjectRenderer {
|
||||
return needsPixelBbox;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the renderer has any objects to render, including its buffer.
|
||||
* @returns Whether the renderer has any objects to render.
|
||||
*/
|
||||
hasObjects = (): boolean => {
|
||||
return this.renderers.size > 0 || this.buffer !== null;
|
||||
};
|
||||
|
||||
hideAll = (except: string[]) => {
|
||||
for (const renderer of this.renderers.values()) {
|
||||
if (!except.includes(renderer.id)) {
|
||||
renderer.setVisibility(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys this renderer and all of its object renderers.
|
||||
*/
|
||||
destroy = () => {
|
||||
this.log.trace('Destroying object renderer');
|
||||
for (const renderer of this.renderers.values()) {
|
||||
@ -201,6 +237,10 @@ export class CanvasObjectRenderer {
|
||||
this.renderers.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a serializable representation of the renderer.
|
||||
* @returns A serializable representation of the renderer.
|
||||
*/
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
@ -208,8 +248,6 @@ export class CanvasObjectRenderer {
|
||||
parent: this.parent.id,
|
||||
renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()),
|
||||
buffer: deepClone(this.buffer),
|
||||
isFirstRender: this.isFirstRender,
|
||||
isRendering: this.isRendering,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -49,8 +49,8 @@ export class CanvasRectRenderer {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
update(state: CanvasRectState, force = this.isFirstRender): boolean {
|
||||
if (this.state !== state || force) {
|
||||
update(state: CanvasRectState, force = false): boolean {
|
||||
if (force || this.state !== state) {
|
||||
this.isFirstRender = false;
|
||||
|
||||
this.log.trace({ state }, 'Updating rect');
|
||||
|
Loading…
Reference in New Issue
Block a user