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
34f4468b20
commit
67a3aa6dff
@ -23,7 +23,6 @@ export class CanvasBrushLineRenderer {
|
|||||||
group: Konva.Group;
|
group: Konva.Group;
|
||||||
line: Konva.Line;
|
line: Konva.Line;
|
||||||
};
|
};
|
||||||
isFirstRender: boolean = false;
|
|
||||||
|
|
||||||
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
|
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
|
||||||
const { id, strokeWidth, clip, color, points } = state;
|
const { id, strokeWidth, clip, color, points } = state;
|
||||||
@ -60,10 +59,8 @@ export class CanvasBrushLineRenderer {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(state: CanvasBrushLineState, force = this.isFirstRender): boolean {
|
update(state: CanvasBrushLineState, force = false): boolean {
|
||||||
if (force || this.state !== state) {
|
if (force || this.state !== state) {
|
||||||
this.isFirstRender = false;
|
|
||||||
|
|
||||||
this.log.trace({ state }, 'Updating brush line');
|
this.log.trace({ state }, 'Updating brush line');
|
||||||
const { points, color, clip, strokeWidth } = state;
|
const { points, color, clip, strokeWidth } = state;
|
||||||
this.konva.line.setAttrs({
|
this.konva.line.setAttrs({
|
||||||
@ -95,7 +92,6 @@ export class CanvasBrushLineRenderer {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
type: CanvasBrushLineRenderer.TYPE,
|
type: CanvasBrushLineRenderer.TYPE,
|
||||||
parent: this.parent.id,
|
parent: this.parent.id,
|
||||||
isFirstRender: this.isFirstRender,
|
|
||||||
state: deepClone(this.state),
|
state: deepClone(this.state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ export class CanvasEraserLineRenderer {
|
|||||||
log: Logger;
|
log: Logger;
|
||||||
getLoggingContext: GetLoggingContext;
|
getLoggingContext: GetLoggingContext;
|
||||||
|
|
||||||
isFirstRender: boolean = false;
|
|
||||||
state: CanvasEraserLineState;
|
state: CanvasEraserLineState;
|
||||||
konva: {
|
konva: {
|
||||||
group: Konva.Group;
|
group: Konva.Group;
|
||||||
@ -59,10 +58,8 @@ export class CanvasEraserLineRenderer {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(state: CanvasEraserLineState, force = this.isFirstRender): boolean {
|
update(state: CanvasEraserLineState, force = false): boolean {
|
||||||
if (force || this.state !== state) {
|
if (force || this.state !== state) {
|
||||||
this.isFirstRender = false;
|
|
||||||
|
|
||||||
this.log.trace({ state }, 'Updating eraser line');
|
this.log.trace({ state }, 'Updating eraser line');
|
||||||
const { points, clip, strokeWidth } = state;
|
const { points, clip, strokeWidth } = state;
|
||||||
this.konva.line.setAttrs({
|
this.konva.line.setAttrs({
|
||||||
@ -93,7 +90,6 @@ export class CanvasEraserLineRenderer {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
type: CanvasEraserLineRenderer.TYPE,
|
type: CanvasEraserLineRenderer.TYPE,
|
||||||
parent: this.parent.id,
|
parent: this.parent.id,
|
||||||
isFirstRender: this.isFirstRender,
|
|
||||||
state: deepClone(this.state),
|
state: deepClone(this.state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ export class CanvasImageRenderer {
|
|||||||
imageName: string | null;
|
imageName: string | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
isFirstRender: boolean = true;
|
|
||||||
|
|
||||||
constructor(state: CanvasImageState, parent: CanvasObjectRenderer) {
|
constructor(state: CanvasImageState, parent: CanvasObjectRenderer) {
|
||||||
const { id, width, height, x, y } = state;
|
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) {
|
if (force || this.state !== state) {
|
||||||
this.isFirstRender = false;
|
|
||||||
|
|
||||||
this.log.trace({ state }, 'Updating image');
|
this.log.trace({ state }, 'Updating image');
|
||||||
|
|
||||||
const { width, height, x, y, image, filters } = state;
|
const { width, height, x, y, image, filters } = state;
|
||||||
@ -183,7 +180,6 @@ export class CanvasImageRenderer {
|
|||||||
imageName: this.imageName,
|
imageName: this.imageName,
|
||||||
isLoading: this.isLoading,
|
isLoading: this.isLoading,
|
||||||
isError: this.isError,
|
isError: this.isError,
|
||||||
isFirstRender: this.isFirstRender,
|
|
||||||
state: deepClone(this.state),
|
state: deepClone(this.state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -259,7 +259,6 @@ export class CanvasLayer {
|
|||||||
const { dispatch } = getStore();
|
const { dispatch } = getStore();
|
||||||
const imageObject = imageDTOToImageObject(imageDTO);
|
const imageObject = imageDTOToImageObject(imageDTO);
|
||||||
await this.renderer.renderObject(imageObject, true);
|
await this.renderer.renderObject(imageObject, true);
|
||||||
this.renderer.hideAll([imageObject.id]);
|
|
||||||
this.resetScale();
|
this.resetScale();
|
||||||
dispatch(layerRasterized({ id: this.id, imageObject, position: { x: Math.round(rect.x), y: Math.round(rect.y) } }));
|
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 type { Logger } from 'roarr';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union of all object renderers.
|
||||||
|
*/
|
||||||
type AnyObjectRenderer = CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer | CanvasImageRenderer;
|
type AnyObjectRenderer = CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer | CanvasImageRenderer;
|
||||||
|
/**
|
||||||
|
* Union of all object states.
|
||||||
|
*/
|
||||||
type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImageState | CanvasRectState;
|
type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImageState | CanvasRectState;
|
||||||
|
|
||||||
export class CanvasObjectRenderer {
|
export class CanvasObjectRenderer {
|
||||||
static TYPE = 'object_renderer';
|
static TYPE = 'object_renderer';
|
||||||
static OBJECT_GROUP_NAME = `${CanvasObjectRenderer.TYPE}_group`;
|
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
parent: CanvasLayer;
|
parent: CanvasLayer;
|
||||||
@ -29,9 +34,16 @@ export class CanvasObjectRenderer {
|
|||||||
log: Logger;
|
log: Logger;
|
||||||
getLoggingContext: (extra?: JSONObject) => JSONObject;
|
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;
|
buffer: AnyObjectState | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of object renderers, keyed by their ID.
|
||||||
|
*/
|
||||||
renderers: Map<string, AnyObjectRenderer> = new Map();
|
renderers: Map<string, AnyObjectRenderer> = new Map();
|
||||||
|
|
||||||
constructor(parent: CanvasLayer) {
|
constructor(parent: CanvasLayer) {
|
||||||
@ -43,8 +55,12 @@ export class CanvasObjectRenderer {
|
|||||||
this.log.trace('Creating object renderer');
|
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> => {
|
render = async (objectStates: AnyObjectState[]): Promise<boolean> => {
|
||||||
this.isRendering = true;
|
|
||||||
let didRender = false;
|
let didRender = false;
|
||||||
const objectIds = objectStates.map((objectState) => objectState.id);
|
const objectIds = objectStates.map((objectState) => objectState.id);
|
||||||
|
|
||||||
@ -64,17 +80,26 @@ export class CanvasObjectRenderer {
|
|||||||
didRender = (await this.renderObject(this.buffer)) || didRender;
|
didRender = (await this.renderObject(this.buffer)) || didRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isRendering = false;
|
|
||||||
this.isFirstRender = false;
|
|
||||||
|
|
||||||
return didRender;
|
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 didRender = false;
|
||||||
|
|
||||||
if (objectState.type === 'brush_line') {
|
|
||||||
let renderer = this.renderers.get(objectState.id);
|
let renderer = this.renderers.get(objectState.id);
|
||||||
|
|
||||||
|
const isFirstRender = renderer === undefined;
|
||||||
|
|
||||||
|
if (objectState.type === 'brush_line') {
|
||||||
assert(renderer instanceof CanvasBrushLineRenderer || renderer === undefined);
|
assert(renderer instanceof CanvasBrushLineRenderer || renderer === undefined);
|
||||||
|
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
@ -83,9 +108,8 @@ export class CanvasObjectRenderer {
|
|||||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
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') {
|
} else if (objectState.type === 'eraser_line') {
|
||||||
let renderer = this.renderers.get(objectState.id);
|
|
||||||
assert(renderer instanceof CanvasEraserLineRenderer || renderer === undefined);
|
assert(renderer instanceof CanvasEraserLineRenderer || renderer === undefined);
|
||||||
|
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
@ -94,9 +118,8 @@ export class CanvasObjectRenderer {
|
|||||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||||
}
|
}
|
||||||
|
|
||||||
didRender = renderer.update(objectState, force);
|
didRender = renderer.update(objectState, force || isFirstRender);
|
||||||
} else if (objectState.type === 'rect') {
|
} else if (objectState.type === 'rect') {
|
||||||
let renderer = this.renderers.get(objectState.id);
|
|
||||||
assert(renderer instanceof CanvasRectRenderer || renderer === undefined);
|
assert(renderer instanceof CanvasRectRenderer || renderer === undefined);
|
||||||
|
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
@ -105,9 +128,8 @@ export class CanvasObjectRenderer {
|
|||||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||||
}
|
}
|
||||||
|
|
||||||
didRender = renderer.update(objectState, force);
|
didRender = renderer.update(objectState, force || isFirstRender);
|
||||||
} else if (objectState.type === 'image') {
|
} else if (objectState.type === 'image') {
|
||||||
let renderer = this.renderers.get(objectState.id);
|
|
||||||
assert(renderer instanceof CanvasImageRenderer || renderer === undefined);
|
assert(renderer instanceof CanvasImageRenderer || renderer === undefined);
|
||||||
|
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
@ -115,28 +137,43 @@ export class CanvasObjectRenderer {
|
|||||||
this.renderers.set(renderer.id, renderer);
|
this.renderers.set(renderer.id, renderer);
|
||||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
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;
|
return didRender;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the renderer has a buffer object to render.
|
||||||
|
* @returns Whether the renderer has a buffer object to render.
|
||||||
|
*/
|
||||||
hasBuffer = (): boolean => {
|
hasBuffer = (): boolean => {
|
||||||
return this.buffer !== null;
|
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> => {
|
setBuffer = async (objectState: AnyObjectState): Promise<boolean> => {
|
||||||
this.buffer = objectState;
|
this.buffer = objectState;
|
||||||
return await this.renderObject(this.buffer, true);
|
return await this.renderObject(this.buffer, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the buffer object state.
|
||||||
|
*/
|
||||||
clearBuffer = () => {
|
clearBuffer = () => {
|
||||||
this.buffer = null;
|
this.buffer = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commits the current buffer object, pushing the buffer object state back to the application state.
|
||||||
|
*/
|
||||||
commitBuffer = () => {
|
commitBuffer = () => {
|
||||||
if (!this.buffer) {
|
if (!this.buffer) {
|
||||||
|
this.log.warn('No buffer object to commit');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,18 +218,17 @@ export class CanvasObjectRenderer {
|
|||||||
return needsPixelBbox;
|
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 => {
|
hasObjects = (): boolean => {
|
||||||
return this.renderers.size > 0 || this.buffer !== null;
|
return this.renderers.size > 0 || this.buffer !== null;
|
||||||
};
|
};
|
||||||
|
|
||||||
hideAll = (except: string[]) => {
|
/**
|
||||||
for (const renderer of this.renderers.values()) {
|
* Destroys this renderer and all of its object renderers.
|
||||||
if (!except.includes(renderer.id)) {
|
*/
|
||||||
renderer.setVisibility(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
destroy = () => {
|
destroy = () => {
|
||||||
this.log.trace('Destroying object renderer');
|
this.log.trace('Destroying object renderer');
|
||||||
for (const renderer of this.renderers.values()) {
|
for (const renderer of this.renderers.values()) {
|
||||||
@ -201,6 +237,10 @@ export class CanvasObjectRenderer {
|
|||||||
this.renderers.clear();
|
this.renderers.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a serializable representation of the renderer.
|
||||||
|
* @returns A serializable representation of the renderer.
|
||||||
|
*/
|
||||||
repr = () => {
|
repr = () => {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@ -208,8 +248,6 @@ export class CanvasObjectRenderer {
|
|||||||
parent: this.parent.id,
|
parent: this.parent.id,
|
||||||
renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()),
|
renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()),
|
||||||
buffer: deepClone(this.buffer),
|
buffer: deepClone(this.buffer),
|
||||||
isFirstRender: this.isFirstRender,
|
|
||||||
isRendering: this.isRendering,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,8 @@ export class CanvasRectRenderer {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(state: CanvasRectState, force = this.isFirstRender): boolean {
|
update(state: CanvasRectState, force = false): boolean {
|
||||||
if (this.state !== state || force) {
|
if (force || this.state !== state) {
|
||||||
this.isFirstRender = false;
|
this.isFirstRender = false;
|
||||||
|
|
||||||
this.log.trace({ state }, 'Updating rect');
|
this.log.trace({ state }, 'Updating rect');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user