mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): split out object renderer
This commit is contained in:
parent
136ffd97ca
commit
1095b7c37f
@ -1,19 +1,19 @@
|
||||
import type { JSONObject } from 'common/types';
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import type { CanvasBrushLineState } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
export class CanvasBrushLine {
|
||||
export class CanvasBrushLineRenderer {
|
||||
static TYPE = 'brush_line';
|
||||
static GROUP_NAME = `${CanvasBrushLine.TYPE}_group`;
|
||||
static LINE_NAME = `${CanvasBrushLine.TYPE}_line`;
|
||||
static GROUP_NAME = `${CanvasBrushLineRenderer.TYPE}_group`;
|
||||
static LINE_NAME = `${CanvasBrushLineRenderer.TYPE}_line`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasLayer;
|
||||
parent: CanvasObjectRenderer;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: (extra?: JSONObject) => JSONObject;
|
||||
@ -23,8 +23,9 @@ export class CanvasBrushLine {
|
||||
group: Konva.Group;
|
||||
line: Konva.Line;
|
||||
};
|
||||
isFirstRender: boolean = false;
|
||||
|
||||
constructor(state: CanvasBrushLineState, parent: CanvasLayer) {
|
||||
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
|
||||
const { id, strokeWidth, clip, color, points } = state;
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
@ -37,12 +38,12 @@ export class CanvasBrushLine {
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({
|
||||
name: CanvasBrushLine.GROUP_NAME,
|
||||
name: CanvasBrushLineRenderer.GROUP_NAME,
|
||||
clip,
|
||||
listening: false,
|
||||
}),
|
||||
line: new Konva.Line({
|
||||
name: CanvasBrushLine.LINE_NAME,
|
||||
name: CanvasBrushLineRenderer.LINE_NAME,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
@ -59,8 +60,10 @@ export class CanvasBrushLine {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
update(state: CanvasBrushLineState, force?: boolean): boolean {
|
||||
update(state: CanvasBrushLineState, force = this.isFirstRender): 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({
|
||||
@ -72,9 +75,9 @@ export class CanvasBrushLine {
|
||||
});
|
||||
this.state = state;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -90,8 +93,9 @@ export class CanvasBrushLine {
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: CanvasBrushLine.TYPE,
|
||||
type: CanvasBrushLineRenderer.TYPE,
|
||||
parent: this.parent.id,
|
||||
isFirstRender: this.isFirstRender,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CanvasEntity } from 'features/controlLayers/konva/CanvasEntity';
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||
import { type CanvasControlAdapterState, isDrawingTool } from 'features/controlLayers/store/types';
|
||||
@ -21,7 +21,7 @@ export class CanvasControlAdapter extends CanvasEntity {
|
||||
objectGroup: Konva.Group;
|
||||
};
|
||||
|
||||
image: CanvasImage | null;
|
||||
image: CanvasImageRenderer | null;
|
||||
transformer: CanvasTransformer;
|
||||
|
||||
constructor(state: CanvasControlAdapterState, manager: CanvasManager) {
|
||||
@ -68,7 +68,7 @@ export class CanvasControlAdapter extends CanvasEntity {
|
||||
didDraw = true;
|
||||
}
|
||||
} else if (!this.image) {
|
||||
this.image = new CanvasImage(imageObject, this);
|
||||
this.image = new CanvasImageRenderer(imageObject, this);
|
||||
this.updateGroup(true);
|
||||
this.konva.objectGroup.add(this.image.konva.group);
|
||||
await this.image.updateImageSource(imageObject.image.name);
|
||||
|
@ -1,30 +1,31 @@
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import type { CanvasEraserLineState, GetLoggingContext } from 'features/controlLayers/store/types';
|
||||
import { RGBA_RED } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
export class CanvasEraserLine {
|
||||
export class CanvasEraserLineRenderer {
|
||||
static TYPE = 'eraser_line';
|
||||
static GROUP_NAME = `${CanvasEraserLine.TYPE}_group`;
|
||||
static LINE_NAME = `${CanvasEraserLine.TYPE}_line`;
|
||||
static GROUP_NAME = `${CanvasEraserLineRenderer.TYPE}_group`;
|
||||
static LINE_NAME = `${CanvasEraserLineRenderer.TYPE}_line`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasLayer;
|
||||
parent: CanvasObjectRenderer;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: GetLoggingContext;
|
||||
|
||||
isFirstRender: boolean = false;
|
||||
state: CanvasEraserLineState;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
line: Konva.Line;
|
||||
};
|
||||
|
||||
constructor(state: CanvasEraserLineState, parent: CanvasLayer) {
|
||||
constructor(state: CanvasEraserLineState, parent: CanvasObjectRenderer) {
|
||||
const { id, strokeWidth, clip, points } = state;
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
@ -36,12 +37,12 @@ export class CanvasEraserLine {
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({
|
||||
name: CanvasEraserLine.GROUP_NAME,
|
||||
name: CanvasEraserLineRenderer.GROUP_NAME,
|
||||
clip,
|
||||
listening: false,
|
||||
}),
|
||||
line: new Konva.Line({
|
||||
name: CanvasEraserLine.LINE_NAME,
|
||||
name: CanvasEraserLineRenderer.LINE_NAME,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
@ -58,8 +59,10 @@ export class CanvasEraserLine {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
update(state: CanvasEraserLineState, force?: boolean): boolean {
|
||||
update(state: CanvasEraserLineState, force = this.isFirstRender): 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({
|
||||
@ -70,9 +73,9 @@ export class CanvasEraserLine {
|
||||
});
|
||||
this.state = state;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -88,8 +91,9 @@ export class CanvasEraserLine {
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: CanvasEraserLine.TYPE,
|
||||
type: CanvasEraserLineRenderer.TYPE,
|
||||
parent: this.parent.id,
|
||||
isFirstRender: this.isFirstRender,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -1,25 +1,24 @@
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasStagingArea } from 'features/controlLayers/konva/CanvasStagingArea';
|
||||
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import { FILTER_MAP } from 'features/controlLayers/konva/filters';
|
||||
import { loadImage } from 'features/controlLayers/konva/util';
|
||||
import type { GetLoggingContext, CanvasImageState } from 'features/controlLayers/store/types';
|
||||
import type { CanvasImageState, GetLoggingContext } from 'features/controlLayers/store/types';
|
||||
import { t } from 'i18next';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
import { getImageDTO } from 'services/api/endpoints/images';
|
||||
|
||||
export class CanvasImage {
|
||||
export class CanvasImageRenderer {
|
||||
static TYPE = 'image';
|
||||
static GROUP_NAME = `${CanvasImage.TYPE}_group`;
|
||||
static IMAGE_NAME = `${CanvasImage.TYPE}_image`;
|
||||
static PLACEHOLDER_GROUP_NAME = `${CanvasImage.TYPE}_placeholder-group`;
|
||||
static PLACEHOLDER_RECT_NAME = `${CanvasImage.TYPE}_placeholder-rect`;
|
||||
static PLACEHOLDER_TEXT_NAME = `${CanvasImage.TYPE}_placeholder-text`;
|
||||
static GROUP_NAME = `${CanvasImageRenderer.TYPE}_group`;
|
||||
static IMAGE_NAME = `${CanvasImageRenderer.TYPE}_image`;
|
||||
static PLACEHOLDER_GROUP_NAME = `${CanvasImageRenderer.TYPE}_placeholder-group`;
|
||||
static PLACEHOLDER_RECT_NAME = `${CanvasImageRenderer.TYPE}_placeholder-rect`;
|
||||
static PLACEHOLDER_TEXT_NAME = `${CanvasImageRenderer.TYPE}_placeholder-text`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasLayer | CanvasStagingArea;
|
||||
parent: CanvasObjectRenderer;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: GetLoggingContext;
|
||||
@ -33,8 +32,9 @@ export class CanvasImage {
|
||||
imageName: string | null;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
isFirstRender: boolean = true;
|
||||
|
||||
constructor(state: CanvasImageState, parent: CanvasLayer | CanvasStagingArea) {
|
||||
constructor(state: CanvasImageState, parent: CanvasObjectRenderer) {
|
||||
const { id, width, height, x, y } = state;
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
@ -45,18 +45,18 @@ export class CanvasImage {
|
||||
this.log.trace({ state }, 'Creating image');
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y }),
|
||||
group: new Konva.Group({ name: CanvasImageRenderer.GROUP_NAME, listening: false, x, y }),
|
||||
placeholder: {
|
||||
group: new Konva.Group({ name: CanvasImage.PLACEHOLDER_GROUP_NAME, listening: false }),
|
||||
group: new Konva.Group({ name: CanvasImageRenderer.PLACEHOLDER_GROUP_NAME, listening: false }),
|
||||
rect: new Konva.Rect({
|
||||
name: CanvasImage.PLACEHOLDER_RECT_NAME,
|
||||
name: CanvasImageRenderer.PLACEHOLDER_RECT_NAME,
|
||||
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
|
||||
width,
|
||||
height,
|
||||
listening: false,
|
||||
}),
|
||||
text: new Konva.Text({
|
||||
name: CanvasImage.PLACEHOLDER_TEXT_NAME,
|
||||
name: CanvasImageRenderer.PLACEHOLDER_TEXT_NAME,
|
||||
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
|
||||
width,
|
||||
height,
|
||||
@ -81,7 +81,7 @@ export class CanvasImage {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
async updateImageSource(imageName: string) {
|
||||
updateImageSource = async (imageName: string) => {
|
||||
try {
|
||||
this.log.trace({ imageName }, 'Updating image source');
|
||||
|
||||
@ -106,7 +106,7 @@ export class CanvasImage {
|
||||
});
|
||||
} else {
|
||||
this.konva.image = new Konva.Image({
|
||||
name: CanvasImage.IMAGE_NAME,
|
||||
name: CanvasImageRenderer.IMAGE_NAME,
|
||||
listening: false,
|
||||
image: imageEl,
|
||||
width: this.state.width,
|
||||
@ -136,14 +136,16 @@ export class CanvasImage {
|
||||
this.konva.placeholder.text.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
|
||||
this.konva.placeholder.group.visible(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
update = async (state: CanvasImageState, force = this.isFirstRender): Promise<boolean> => {
|
||||
if (force || this.state !== state) {
|
||||
this.isFirstRender = false;
|
||||
|
||||
async update(state: CanvasImageState, force?: boolean): Promise<boolean> {
|
||||
if (this.state !== state || force) {
|
||||
this.log.trace({ state }, 'Updating image');
|
||||
|
||||
const { width, height, x, y, image, filters } = state;
|
||||
if (this.state.image.name !== image.name || force) {
|
||||
if (force || (this.state.image.name !== image.name && !this.isLoading)) {
|
||||
await this.updateImageSource(image.name);
|
||||
}
|
||||
this.konva.image?.setAttrs({ x, y, width, height });
|
||||
@ -158,30 +160,31 @@ export class CanvasImage {
|
||||
this.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
|
||||
this.state = state;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
return false;
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.trace('Destroying image');
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
setVisibility(isVisible: boolean): void {
|
||||
setVisibility = (isVisible: boolean): void => {
|
||||
this.log.trace({ isVisible }, 'Setting image visibility');
|
||||
this.konva.group.visible(isVisible);
|
||||
}
|
||||
};
|
||||
|
||||
repr() {
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: CanvasImage.TYPE,
|
||||
type: CanvasImageRenderer.TYPE,
|
||||
parent: this.parent.id,
|
||||
imageName: this.imageName,
|
||||
isLoading: this.isLoading,
|
||||
isError: this.isError,
|
||||
isFirstRender: this.isFirstRender,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
@ -20,7 +20,7 @@ export class CanvasInitialImage {
|
||||
objectGroup: Konva.Group;
|
||||
};
|
||||
|
||||
image: CanvasImage | null;
|
||||
image: CanvasImageRenderer | null;
|
||||
|
||||
constructor(state: InitialImageEntity, manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
@ -45,7 +45,7 @@ export class CanvasInitialImage {
|
||||
}
|
||||
|
||||
if (!this.image) {
|
||||
this.image = new CanvasImage(this.state.imageObject);
|
||||
this.image = new CanvasImageRenderer(this.state.imageObject);
|
||||
this.konva.objectGroup.add(this.image.konva.group);
|
||||
await this.image.update(this.state.imageObject, true);
|
||||
} else if (!this.image.isLoading && !this.image.isError) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox';
|
||||
import { mapId } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasBrushLineState, CanvasEraserLineState, CanvasInpaintMaskState, CanvasRectState } from 'features/controlLayers/store/types';
|
||||
@ -31,7 +31,7 @@ export class CanvasInpaintMask {
|
||||
transformer: Konva.Transformer;
|
||||
compositingRect: Konva.Rect;
|
||||
};
|
||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
||||
objects: Map<string, CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer>;
|
||||
|
||||
constructor(state: CanvasInpaintMaskState, manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
@ -156,10 +156,10 @@ export class CanvasInpaintMask {
|
||||
private async renderObject(obj: CanvasInpaintMaskState['objects'][number], force = false): Promise<boolean> {
|
||||
if (obj.type === 'brush_line') {
|
||||
let brushLine = this.objects.get(obj.id);
|
||||
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
|
||||
assert(brushLine instanceof CanvasBrushLineRenderer || brushLine === undefined);
|
||||
|
||||
if (!brushLine) {
|
||||
brushLine = new CanvasBrushLine(obj);
|
||||
brushLine = new CanvasBrushLineRenderer(obj);
|
||||
this.objects.set(brushLine.id, brushLine);
|
||||
this.konva.objectGroup.add(brushLine.konva.group);
|
||||
return true;
|
||||
@ -170,10 +170,10 @@ export class CanvasInpaintMask {
|
||||
}
|
||||
} else if (obj.type === 'eraser_line') {
|
||||
let eraserLine = this.objects.get(obj.id);
|
||||
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
|
||||
assert(eraserLine instanceof CanvasEraserLineRenderer || eraserLine === undefined);
|
||||
|
||||
if (!eraserLine) {
|
||||
eraserLine = new CanvasEraserLine(obj);
|
||||
eraserLine = new CanvasEraserLineRenderer(obj);
|
||||
this.objects.set(eraserLine.id, eraserLine);
|
||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||
return true;
|
||||
@ -184,10 +184,10 @@ export class CanvasInpaintMask {
|
||||
}
|
||||
} else if (obj.type === 'rect') {
|
||||
let rect = this.objects.get(obj.id);
|
||||
assert(rect instanceof CanvasRect || rect === undefined);
|
||||
assert(rect instanceof CanvasRectRenderer || rect === undefined);
|
||||
|
||||
if (!rect) {
|
||||
rect = new CanvasRect(obj);
|
||||
rect = new CanvasRectRenderer(obj);
|
||||
this.objects.set(rect.id, rect);
|
||||
this.konva.objectGroup.add(rect.konva.group);
|
||||
return true;
|
||||
|
@ -1,18 +1,12 @@
|
||||
import { getStore } from 'app/store/nanostores/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||
import { getPrefixedId, konvaNodeToBlob, mapId, previewBlob } from 'features/controlLayers/konva/util';
|
||||
import { konvaNodeToBlob, previewBlob } from 'features/controlLayers/konva/util';
|
||||
import { layerRasterized } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEraserLineState,
|
||||
CanvasLayerState,
|
||||
CanvasRectState,
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
GetLoggingContext,
|
||||
@ -23,34 +17,28 @@ import Konva from 'konva';
|
||||
import { debounce, get } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import { uploadImage } from 'services/api/endpoints/images';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasLayer {
|
||||
static TYPE = 'layer';
|
||||
static LAYER_NAME = `${CanvasLayer.TYPE}_layer`;
|
||||
static TRANSFORMER_NAME = `${CanvasLayer.TYPE}_transformer`;
|
||||
static INTERACTION_RECT_NAME = `${CanvasLayer.TYPE}_interaction-rect`;
|
||||
static GROUP_NAME = `${CanvasLayer.TYPE}_group`;
|
||||
static OBJECT_GROUP_NAME = `${CanvasLayer.TYPE}_object-group`;
|
||||
static BBOX_NAME = `${CanvasLayer.TYPE}_bbox`;
|
||||
static KONVA_LAYER_NAME = `${CanvasLayer.TYPE}_layer`;
|
||||
static KONVA_OBJECT_GROUP_NAME = `${CanvasLayer.TYPE}_object-group`;
|
||||
|
||||
id: string;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: GetLoggingContext;
|
||||
|
||||
drawingBuffer: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null;
|
||||
state: CanvasLayerState;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
objectGroup: Konva.Group;
|
||||
};
|
||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
||||
transformer: CanvasTransformer;
|
||||
renderer: CanvasObjectRenderer;
|
||||
|
||||
isFirstRender: boolean = true;
|
||||
bboxNeedsUpdate: boolean;
|
||||
isFirstRender: boolean;
|
||||
isTransforming: boolean;
|
||||
isPendingBboxCalculation: boolean;
|
||||
|
||||
@ -67,26 +55,24 @@ export class CanvasLayer {
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
id: this.id,
|
||||
name: CanvasLayer.LAYER_NAME,
|
||||
name: CanvasLayer.KONVA_LAYER_NAME,
|
||||
listening: false,
|
||||
imageSmoothingEnabled: false,
|
||||
}),
|
||||
objectGroup: new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false }),
|
||||
objectGroup: new Konva.Group({ name: CanvasLayer.KONVA_OBJECT_GROUP_NAME, listening: false }),
|
||||
};
|
||||
|
||||
this.transformer = new CanvasTransformer(this, this.konva.objectGroup);
|
||||
this.transformer = new CanvasTransformer(this);
|
||||
this.renderer = new CanvasObjectRenderer(this);
|
||||
|
||||
this.konva.layer.add(this.konva.objectGroup);
|
||||
this.konva.layer.add(...this.transformer.getNodes());
|
||||
|
||||
this.objects = new Map();
|
||||
this.drawingBuffer = null;
|
||||
this.state = state;
|
||||
this.rect = this.getDefaultRect();
|
||||
this.bbox = this.getDefaultRect();
|
||||
this.bboxNeedsUpdate = true;
|
||||
this.isTransforming = false;
|
||||
this.isFirstRender = true;
|
||||
this.isPendingBboxCalculation = false;
|
||||
}
|
||||
|
||||
@ -94,47 +80,10 @@ export class CanvasLayer {
|
||||
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();
|
||||
for (const obj of this.objects.values()) {
|
||||
obj.destroy();
|
||||
}
|
||||
this.renderer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
};
|
||||
|
||||
getDrawingBuffer = () => {
|
||||
return this.drawingBuffer;
|
||||
};
|
||||
|
||||
setDrawingBuffer = async (obj: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null) => {
|
||||
if (obj) {
|
||||
this.drawingBuffer = obj;
|
||||
await this._renderObject(this.drawingBuffer, true);
|
||||
} else {
|
||||
this.drawingBuffer = null;
|
||||
}
|
||||
};
|
||||
|
||||
finalizeDrawingBuffer = async () => {
|
||||
if (!this.drawingBuffer) {
|
||||
return;
|
||||
}
|
||||
const drawingBuffer = this.drawingBuffer;
|
||||
await 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
|
||||
// a non-buffer object, and we won't trigger things like bbox calculation
|
||||
|
||||
if (drawingBuffer.type === 'brush_line') {
|
||||
drawingBuffer.id = getPrefixedId('brush_line');
|
||||
this.manager.stateApi.onBrushLineAdded({ id: this.id, brushLine: drawingBuffer }, 'layer');
|
||||
} else if (drawingBuffer.type === 'eraser_line') {
|
||||
drawingBuffer.id = getPrefixedId('brush_line');
|
||||
this.manager.stateApi.onEraserLineAdded({ id: this.id, eraserLine: drawingBuffer }, 'layer');
|
||||
} else if (drawingBuffer.type === 'rect') {
|
||||
drawingBuffer.id = getPrefixedId('brush_line');
|
||||
this.manager.stateApi.onRectShapeAdded({ id: this.id, rectShape: drawingBuffer }, 'layer');
|
||||
}
|
||||
};
|
||||
|
||||
update = async (arg?: { state: CanvasLayerState; toolState: CanvasV2State['tool']; isSelected: boolean }) => {
|
||||
const state = get(arg, 'state', this.state);
|
||||
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
||||
@ -173,8 +122,7 @@ export class CanvasLayer {
|
||||
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.konva.layer.visible(isEnabled && hasObjects);
|
||||
this.konva.layer.visible(isEnabled && this.renderer.hasObjects());
|
||||
};
|
||||
|
||||
updatePosition = (arg?: { position: Coordinate }) => {
|
||||
@ -196,30 +144,7 @@ export class CanvasLayer {
|
||||
|
||||
const objects = get(arg, 'objects', this.state.objects);
|
||||
|
||||
const objectIds = objects.map(mapId);
|
||||
|
||||
let didUpdate = false;
|
||||
|
||||
// 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) {
|
||||
this.objects.delete(object.id);
|
||||
object.destroy();
|
||||
didUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const obj of objects) {
|
||||
if (await this._renderObject(obj)) {
|
||||
didUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.drawingBuffer) {
|
||||
if (await this._renderObject(this.drawingBuffer)) {
|
||||
didUpdate = true;
|
||||
}
|
||||
}
|
||||
const didUpdate = await this.renderer.render(objects);
|
||||
|
||||
if (didUpdate) {
|
||||
this.calculateBbox();
|
||||
@ -240,7 +165,7 @@ export class CanvasLayer {
|
||||
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
||||
const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id));
|
||||
|
||||
if (this.objects.size === 0) {
|
||||
if (!this.renderer.hasObjects()) {
|
||||
// The layer is totally empty, we can just disable the layer
|
||||
this.konva.layer.listening(false);
|
||||
this.transformer.setMode('off');
|
||||
@ -279,7 +204,7 @@ export class CanvasLayer {
|
||||
// eraser lines, fully clipped brush lines or if it has been fully erased.
|
||||
if (this.bbox.width === 0 || this.bbox.height === 0) {
|
||||
// We shouldn't reset on the first render - the bbox will be calculated on the next render
|
||||
if (!this.isFirstRender && this.objects.size > 0) {
|
||||
if (!this.isFirstRender && !this.renderer.hasObjects()) {
|
||||
// The layer is fully transparent but has objects - reset it
|
||||
this.manager.stateApi.onEntityReset({ id: this.id }, 'layer');
|
||||
}
|
||||
@ -297,67 +222,6 @@ export class CanvasLayer {
|
||||
});
|
||||
};
|
||||
|
||||
_renderObject = async (obj: CanvasLayerState['objects'][number], force = false): Promise<boolean> => {
|
||||
if (obj.type === 'brush_line') {
|
||||
let brushLine = this.objects.get(obj.id);
|
||||
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
|
||||
|
||||
if (!brushLine) {
|
||||
brushLine = new CanvasBrushLine(obj, this);
|
||||
this.objects.set(brushLine.id, brushLine);
|
||||
this.konva.objectGroup.add(brushLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
return await brushLine.update(obj, force);
|
||||
}
|
||||
} else if (obj.type === 'eraser_line') {
|
||||
let eraserLine = this.objects.get(obj.id);
|
||||
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
|
||||
|
||||
if (!eraserLine) {
|
||||
eraserLine = new CanvasEraserLine(obj, this);
|
||||
this.objects.set(eraserLine.id, eraserLine);
|
||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (await eraserLine.update(obj, force)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (obj.type === 'rect') {
|
||||
let rect = this.objects.get(obj.id);
|
||||
assert(rect instanceof CanvasRect || rect === undefined);
|
||||
|
||||
if (!rect) {
|
||||
rect = new CanvasRect(obj, this);
|
||||
this.objects.set(rect.id, rect);
|
||||
this.konva.objectGroup.add(rect.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (await rect.update(obj, force)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (obj.type === 'image') {
|
||||
let image = this.objects.get(obj.id);
|
||||
assert(image instanceof CanvasImage || image === undefined);
|
||||
|
||||
if (!image) {
|
||||
image = new CanvasImage(obj, this);
|
||||
this.objects.set(image.id, image);
|
||||
this.konva.objectGroup.add(image.konva.group);
|
||||
await image.updateImageSource(obj.image.name);
|
||||
return true;
|
||||
} else {
|
||||
if (await image.update(obj, force)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
startTransform = () => {
|
||||
this.log.debug('Starting transform');
|
||||
this.isTransforming = true;
|
||||
@ -365,9 +229,8 @@ export class CanvasLayer {
|
||||
// When transforming, we want the stage to still be movable if the view tool is selected. If the transformer or
|
||||
// interaction rect are listening, it will interrupt the stage's drag events. So we should disable listening
|
||||
// when the view tool is selected
|
||||
const listening = this.manager.stateApi.getToolState().selected !== 'view';
|
||||
|
||||
this.konva.layer.listening(listening);
|
||||
const shouldListen = this.manager.stateApi.getToolState().selected !== 'view';
|
||||
this.konva.layer.listening(shouldListen);
|
||||
this.transformer.setMode('transform');
|
||||
};
|
||||
|
||||
@ -395,12 +258,8 @@ export class CanvasLayer {
|
||||
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
|
||||
const { dispatch } = getStore();
|
||||
const imageObject = imageDTOToImageObject(imageDTO);
|
||||
await this._renderObject(imageObject, true);
|
||||
for (const obj of this.objects.values()) {
|
||||
if (obj.id !== imageObject.id) {
|
||||
obj.konva.group.visible(false);
|
||||
}
|
||||
}
|
||||
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) } }));
|
||||
};
|
||||
@ -424,7 +283,7 @@ export class CanvasLayer {
|
||||
|
||||
this.isPendingBboxCalculation = true;
|
||||
|
||||
if (this.objects.size === 0) {
|
||||
if (!this.renderer.hasObjects()) {
|
||||
this.log.trace('No objects, resetting bbox');
|
||||
this.rect = this.getDefaultRect();
|
||||
this.bbox = this.getDefaultRect();
|
||||
@ -435,30 +294,7 @@ export class CanvasLayer {
|
||||
|
||||
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
|
||||
|
||||
/**
|
||||
* In some cases, we can use konva's getClientRect as the bbox, but there are some cases where we need to calculate
|
||||
* the bbox using pixel data:
|
||||
*
|
||||
* - Eraser lines are normal lines, except they composite as transparency. Konva's getClientRect includes them when
|
||||
* calculating the bbox.
|
||||
* - Clipped portions of lines will be included in the client rect.
|
||||
* - Images have transparency, so they will be included in the client rect.
|
||||
*
|
||||
* 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;
|
||||
const hasClip = obj instanceof CanvasBrushLine && obj.state.clip;
|
||||
if (isEraserLine || hasClip || isImage) {
|
||||
needsPixelBbox = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsPixelBbox) {
|
||||
if (!this.renderer.needsPixelBbox()) {
|
||||
this.rect = deepClone(rect);
|
||||
this.bbox = deepClone(rect);
|
||||
this.isPendingBboxCalculation = false;
|
||||
@ -508,10 +344,10 @@ export class CanvasLayer {
|
||||
rect: deepClone(this.rect),
|
||||
bbox: deepClone(this.bbox),
|
||||
bboxNeedsUpdate: this.bboxNeedsUpdate,
|
||||
isFirstRender: this.isFirstRender,
|
||||
isTransforming: this.isTransforming,
|
||||
isPendingBboxCalculation: this.isPendingBboxCalculation,
|
||||
objects: Array.from(this.objects.values()).map((obj) => obj.repr()),
|
||||
transformer: this.transformer.repr(),
|
||||
renderer: this.renderer.repr(),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -2,12 +2,13 @@ import type { Store } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { JSONObject } from 'common/types';
|
||||
import type { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import type { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import type { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import type { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import type { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasInitialImage } from 'features/controlLayers/konva/CanvasInitialImage';
|
||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
|
||||
import type { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
|
||||
import type { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
import type { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||
import {
|
||||
getCompositeLayerImage,
|
||||
@ -593,11 +594,12 @@ export class CanvasManager {
|
||||
|
||||
buildGetLoggingContext = (
|
||||
instance:
|
||||
| CanvasBrushLine
|
||||
| CanvasEraserLine
|
||||
| CanvasRect
|
||||
| CanvasImage
|
||||
| CanvasBrushLineRenderer
|
||||
| CanvasEraserLineRenderer
|
||||
| CanvasRectRenderer
|
||||
| CanvasImageRenderer
|
||||
| CanvasTransformer
|
||||
| CanvasObjectRenderer
|
||||
| CanvasLayer
|
||||
| CanvasStagingArea
|
||||
): GetLoggingContext => {
|
||||
@ -609,6 +611,14 @@ export class CanvasManager {
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
} else if (instance instanceof CanvasObjectRenderer) {
|
||||
return (extra?: JSONObject): JSONObject => {
|
||||
return {
|
||||
...instance.parent.getLoggingContext(),
|
||||
rendererId: instance.id,
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
} else {
|
||||
return (extra?: JSONObject): JSONObject => {
|
||||
return {
|
||||
|
@ -0,0 +1,215 @@
|
||||
import type { JSONObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEraserLineState,
|
||||
CanvasImageState,
|
||||
CanvasRectState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type { Logger } from 'roarr';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type AnyObjectRenderer = CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer | CanvasImageRenderer;
|
||||
type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImageState | CanvasRectState;
|
||||
|
||||
export class CanvasObjectRenderer {
|
||||
static TYPE = 'object_renderer';
|
||||
static OBJECT_GROUP_NAME = `${CanvasObjectRenderer.TYPE}_group`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasLayer;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: (extra?: JSONObject) => JSONObject;
|
||||
|
||||
isFirstRender: boolean = true;
|
||||
isRendering: boolean = false;
|
||||
buffer: AnyObjectState | null = null;
|
||||
renderers: Map<string, AnyObjectRenderer> = new Map();
|
||||
|
||||
constructor(parent: CanvasLayer) {
|
||||
this.id = getPrefixedId(CanvasObjectRenderer.TYPE);
|
||||
this.parent = parent;
|
||||
this.manager = parent.manager;
|
||||
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
this.log.trace('Creating object renderer');
|
||||
}
|
||||
|
||||
render = async (objectStates: AnyObjectState[]): Promise<boolean> => {
|
||||
this.isRendering = true;
|
||||
let didRender = false;
|
||||
const objectIds = objectStates.map((objectState) => objectState.id);
|
||||
|
||||
for (const renderer of this.renderers.values()) {
|
||||
if (!objectIds.includes(renderer.id) && renderer.id !== this.buffer?.id) {
|
||||
this.renderers.delete(renderer.id);
|
||||
renderer.destroy();
|
||||
didRender = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const objectState of objectStates) {
|
||||
didRender = (await this.renderObject(objectState)) || didRender;
|
||||
}
|
||||
|
||||
if (this.buffer) {
|
||||
didRender = (await this.renderObject(this.buffer)) || didRender;
|
||||
}
|
||||
|
||||
this.isRendering = false;
|
||||
this.isFirstRender = false;
|
||||
|
||||
return didRender;
|
||||
};
|
||||
|
||||
renderObject = async (objectState: AnyObjectState, force?: boolean): Promise<boolean> => {
|
||||
let didRender = false;
|
||||
|
||||
if (objectState.type === 'brush_line') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasBrushLineRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
renderer = new CanvasBrushLineRenderer(objectState, this);
|
||||
this.renderers.set(renderer.id, renderer);
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
|
||||
didRender = renderer.update(objectState, force);
|
||||
} else if (objectState.type === 'eraser_line') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasEraserLineRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
renderer = new CanvasEraserLineRenderer(objectState, this);
|
||||
this.renderers.set(renderer.id, renderer);
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
|
||||
didRender = renderer.update(objectState, force);
|
||||
} else if (objectState.type === 'rect') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasRectRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
renderer = new CanvasRectRenderer(objectState, this);
|
||||
this.renderers.set(renderer.id, renderer);
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
|
||||
didRender = renderer.update(objectState, force);
|
||||
} else if (objectState.type === 'image') {
|
||||
let renderer = this.renderers.get(objectState.id);
|
||||
assert(renderer instanceof CanvasImageRenderer || renderer === undefined);
|
||||
|
||||
if (!renderer) {
|
||||
renderer = new CanvasImageRenderer(objectState, this);
|
||||
this.renderers.set(renderer.id, renderer);
|
||||
this.parent.konva.objectGroup.add(renderer.konva.group);
|
||||
}
|
||||
didRender = await renderer.update(objectState, force);
|
||||
}
|
||||
|
||||
this.isFirstRender = false;
|
||||
return didRender;
|
||||
};
|
||||
|
||||
hasBuffer = (): boolean => {
|
||||
return this.buffer !== null;
|
||||
};
|
||||
|
||||
setBuffer = async (objectState: AnyObjectState): Promise<boolean> => {
|
||||
this.buffer = objectState;
|
||||
return await this.renderObject(this.buffer, true);
|
||||
};
|
||||
|
||||
clearBuffer = () => {
|
||||
this.buffer = null;
|
||||
};
|
||||
|
||||
commitBuffer = () => {
|
||||
if (!this.buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to give the objects a fresh ID else they will be considered the same object when they are re-rendered as
|
||||
// a non-buffer object, and we won't trigger things like bbox calculation
|
||||
this.buffer.id = getPrefixedId(this.buffer.type);
|
||||
|
||||
if (this.buffer.type === 'brush_line') {
|
||||
this.manager.stateApi.onBrushLineAdded({ id: this.parent.id, brushLine: this.buffer }, 'layer');
|
||||
} else if (this.buffer.type === 'eraser_line') {
|
||||
this.manager.stateApi.onEraserLineAdded({ id: this.parent.id, eraserLine: this.buffer }, 'layer');
|
||||
} else if (this.buffer.type === 'rect') {
|
||||
this.manager.stateApi.onRectShapeAdded({ id: this.parent.id, rectShape: this.buffer }, 'layer');
|
||||
} else {
|
||||
this.log.warn({ buffer: this.buffer }, 'Invalid buffer object type');
|
||||
}
|
||||
|
||||
this.buffer = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the objects in the renderer require a pixel bbox calculation.
|
||||
*
|
||||
* In some cases, we can use Konva's getClientRect as the bbox, but it is not always accurate. It includes
|
||||
* these visually transparent shapes in its calculation:
|
||||
*
|
||||
* - Eraser lines, which are normal lines with a globalCompositeOperation of 'destination-out'.
|
||||
* - Clipped portions of any shape.
|
||||
* - Images, which may have transparent areas.
|
||||
*/
|
||||
needsPixelBbox = (): boolean => {
|
||||
let needsPixelBbox = false;
|
||||
for (const renderer of this.renderers.values()) {
|
||||
const isEraserLine = renderer instanceof CanvasEraserLineRenderer;
|
||||
const isImage = renderer instanceof CanvasImageRenderer;
|
||||
const hasClip = renderer instanceof CanvasBrushLineRenderer && renderer.state.clip;
|
||||
if (isEraserLine || hasClip || isImage) {
|
||||
needsPixelBbox = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return needsPixelBbox;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.trace('Destroying object renderer');
|
||||
for (const renderer of this.renderers.values()) {
|
||||
renderer.destroy();
|
||||
}
|
||||
this.renderers.clear();
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: CanvasObjectRenderer.TYPE,
|
||||
parent: this.parent.id,
|
||||
renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()),
|
||||
buffer: deepClone(this.buffer),
|
||||
isFirstRender: this.isFirstRender,
|
||||
isRendering: this.isRendering,
|
||||
};
|
||||
};
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { GetLoggingContext, CanvasRectState } from 'features/controlLayers/store/types';
|
||||
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import type { CanvasRectState, GetLoggingContext } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
export class CanvasRect {
|
||||
export class CanvasRectRenderer {
|
||||
static TYPE = 'rect';
|
||||
static GROUP_NAME = `${CanvasRect.TYPE}_group`;
|
||||
static RECT_NAME = `${CanvasRect.TYPE}_rect`;
|
||||
static GROUP_NAME = `${CanvasRectRenderer.TYPE}_group`;
|
||||
static RECT_NAME = `${CanvasRectRenderer.TYPE}_rect`;
|
||||
|
||||
id: string;
|
||||
parent: CanvasLayer;
|
||||
parent: CanvasObjectRenderer;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
getLoggingContext: GetLoggingContext;
|
||||
@ -22,8 +22,9 @@ export class CanvasRect {
|
||||
group: Konva.Group;
|
||||
rect: Konva.Rect;
|
||||
};
|
||||
isFirstRender: boolean = false;
|
||||
|
||||
constructor(state: CanvasRectState, parent: CanvasLayer) {
|
||||
constructor(state: CanvasRectState, parent: CanvasObjectRenderer) {
|
||||
const { id, x, y, width, height, color } = state;
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
@ -33,9 +34,9 @@ export class CanvasRect {
|
||||
this.log.trace({ state }, 'Creating rect');
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasRect.GROUP_NAME, listening: false }),
|
||||
group: new Konva.Group({ name: CanvasRectRenderer.GROUP_NAME, listening: false }),
|
||||
rect: new Konva.Rect({
|
||||
name: CanvasRect.RECT_NAME,
|
||||
name: CanvasRectRenderer.RECT_NAME,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
@ -48,8 +49,10 @@ export class CanvasRect {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
update(state: CanvasRectState, force?: boolean): boolean {
|
||||
update(state: CanvasRectState, force = this.isFirstRender): boolean {
|
||||
if (this.state !== state || force) {
|
||||
this.isFirstRender = false;
|
||||
|
||||
this.log.trace({ state }, 'Updating rect');
|
||||
const { x, y, width, height, color } = state;
|
||||
this.konva.rect.setAttrs({
|
||||
@ -61,9 +64,9 @@ export class CanvasRect {
|
||||
});
|
||||
this.state = state;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -79,8 +82,9 @@ export class CanvasRect {
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: CanvasRect.TYPE,
|
||||
type: CanvasRectRenderer.TYPE,
|
||||
parent: this.parent.id,
|
||||
isFirstRender: this.isFirstRender,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox';
|
||||
import { mapId } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasBrushLineState, CanvasEraserLineState, CanvasRectState, CanvasRegionalGuidanceState } from 'features/controlLayers/store/types';
|
||||
@ -32,7 +32,7 @@ export class CanvasRegion {
|
||||
transformer: Konva.Transformer;
|
||||
};
|
||||
|
||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
||||
objects: Map<string, CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer>;
|
||||
|
||||
constructor(state: CanvasRegionalGuidanceState, manager: CanvasManager) {
|
||||
this.id = state.id;
|
||||
@ -155,10 +155,10 @@ export class CanvasRegion {
|
||||
private async renderObject(obj: CanvasRegionalGuidanceState['objects'][number], force = false): Promise<boolean> {
|
||||
if (obj.type === 'brush_line') {
|
||||
let brushLine = this.objects.get(obj.id);
|
||||
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
|
||||
assert(brushLine instanceof CanvasBrushLineRenderer || brushLine === undefined);
|
||||
|
||||
if (!brushLine) {
|
||||
brushLine = new CanvasBrushLine(obj);
|
||||
brushLine = new CanvasBrushLineRenderer(obj);
|
||||
this.objects.set(brushLine.id, brushLine);
|
||||
this.konva.objectGroup.add(brushLine.konva.group);
|
||||
return true;
|
||||
@ -169,10 +169,10 @@ export class CanvasRegion {
|
||||
}
|
||||
} else if (obj.type === 'eraser_line') {
|
||||
let eraserLine = this.objects.get(obj.id);
|
||||
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
|
||||
assert(eraserLine instanceof CanvasEraserLineRenderer || eraserLine === undefined);
|
||||
|
||||
if (!eraserLine) {
|
||||
eraserLine = new CanvasEraserLine(obj);
|
||||
eraserLine = new CanvasEraserLineRenderer(obj);
|
||||
this.objects.set(eraserLine.id, eraserLine);
|
||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||
return true;
|
||||
@ -183,10 +183,10 @@ export class CanvasRegion {
|
||||
}
|
||||
} else if (obj.type === 'rect') {
|
||||
let rect = this.objects.get(obj.id);
|
||||
assert(rect instanceof CanvasRect || rect === undefined);
|
||||
assert(rect instanceof CanvasRectRenderer || rect === undefined);
|
||||
|
||||
if (!rect) {
|
||||
rect = new CanvasRect(obj);
|
||||
rect = new CanvasRectRenderer(obj);
|
||||
this.objects.set(rect.id, rect);
|
||||
this.konva.objectGroup.add(rect.konva.group);
|
||||
return true;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { GetLoggingContext, StagingAreaImage } from 'features/controlLayers/store/types';
|
||||
@ -16,7 +16,7 @@ export class CanvasStagingArea {
|
||||
|
||||
konva: { group: Konva.Group };
|
||||
|
||||
image: CanvasImage | null;
|
||||
image: CanvasImageRenderer | null;
|
||||
selectedImage: StagingAreaImage | null;
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
@ -43,7 +43,7 @@ export class CanvasStagingArea {
|
||||
|
||||
if (!this.image) {
|
||||
const { image_name, width, height } = imageDTO;
|
||||
this.image = new CanvasImage(
|
||||
this.image = new CanvasImageRenderer(
|
||||
{
|
||||
id: 'staging-area-image',
|
||||
type: 'image',
|
||||
|
@ -46,22 +46,16 @@ export class CanvasTransformer {
|
||||
*/
|
||||
isTransformEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The konva group that the transformer will manipulate.
|
||||
*/
|
||||
transformTarget: Konva.Group;
|
||||
|
||||
konva: {
|
||||
transformer: Konva.Transformer;
|
||||
proxyRect: Konva.Rect;
|
||||
bboxOutline: Konva.Rect;
|
||||
};
|
||||
|
||||
constructor(parent: CanvasLayer, transformTarget: Konva.Group) {
|
||||
constructor(parent: CanvasLayer) {
|
||||
this.id = getPrefixedId(CanvasTransformer.TYPE);
|
||||
this.parent = parent;
|
||||
this.manager = parent.manager;
|
||||
this.transformTarget = transformTarget;
|
||||
|
||||
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
@ -192,7 +186,7 @@ export class CanvasTransformer {
|
||||
// This is called when a transform anchor is dragged. By this time, the transform constraints in the above
|
||||
// callbacks have been enforced, and the transformer has updated its nodes' attributes. We need to pass the
|
||||
// updated attributes to the object group, propagating the transformation on down.
|
||||
this.transformTarget.setAttrs({
|
||||
this.parent.konva.objectGroup.setAttrs({
|
||||
x: this.konva.proxyRect.x(),
|
||||
y: this.konva.proxyRect.y(),
|
||||
scaleX: this.konva.proxyRect.scaleX(),
|
||||
@ -234,7 +228,7 @@ export class CanvasTransformer {
|
||||
scaleX: snappedScaleX,
|
||||
scaleY: snappedScaleY,
|
||||
});
|
||||
this.transformTarget.setAttrs({
|
||||
this.parent.konva.objectGroup.setAttrs({
|
||||
x: snappedX,
|
||||
y: snappedY,
|
||||
scaleX: snappedScaleX,
|
||||
@ -278,7 +272,7 @@ export class CanvasTransformer {
|
||||
|
||||
// The object group is translated by the difference between the interaction rect's new and old positions (which is
|
||||
// stored as this.bbox)
|
||||
this.transformTarget.setAttrs({
|
||||
this.parent.konva.objectGroup.setAttrs({
|
||||
x: this.konva.proxyRect.x(),
|
||||
y: this.konva.proxyRect.y(),
|
||||
});
|
||||
|
@ -6,11 +6,11 @@ import {
|
||||
offsetCoord,
|
||||
} from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasLayerState,
|
||||
CanvasRegionalGuidanceState,
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
Tool,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { isDrawableEntity, isDrawableEntityAdapter } from 'features/controlLayers/store/types';
|
||||
@ -189,11 +189,11 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||
if (e.evt.shiftKey && lastLinePoint) {
|
||||
// Create a straight line from the last line point
|
||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
if (selectedEntityAdapter.renderer.buffer) {
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
await selectedEntityAdapter.renderer.setBuffer({
|
||||
id: getObjectId('brush_line', true),
|
||||
type: 'brush_line',
|
||||
points: [
|
||||
@ -208,10 +208,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
clip: getClip(selectedEntity),
|
||||
});
|
||||
} else {
|
||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
if (selectedEntityAdapter.renderer.buffer) {
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
await selectedEntityAdapter.renderer.setBuffer({
|
||||
id: getObjectId('brush_line', true),
|
||||
type: 'brush_line',
|
||||
points: [alignedPoint.x, alignedPoint.y],
|
||||
@ -228,10 +228,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||
if (e.evt.shiftKey && lastLinePoint) {
|
||||
// Create a straight line from the last line point
|
||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
if (selectedEntityAdapter.renderer.buffer) {
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
await selectedEntityAdapter.renderer.setBuffer({
|
||||
id: getObjectId('eraser_line', true),
|
||||
type: 'eraser_line',
|
||||
points: [
|
||||
@ -245,10 +245,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
clip: getClip(selectedEntity),
|
||||
});
|
||||
} else {
|
||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
if (selectedEntityAdapter.renderer.buffer) {
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
await selectedEntityAdapter.renderer.setBuffer({
|
||||
id: getObjectId('eraser_line', true),
|
||||
type: 'eraser_line',
|
||||
points: [alignedPoint.x, alignedPoint.y],
|
||||
@ -260,10 +260,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
}
|
||||
|
||||
if (toolState.selected === 'rect') {
|
||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
if (selectedEntityAdapter.renderer.buffer) {
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
await selectedEntityAdapter.renderer.setBuffer({
|
||||
id: getObjectId('rect', true),
|
||||
type: 'rect',
|
||||
x: Math.round(normalizedPoint.x),
|
||||
@ -295,29 +295,29 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
const toolState = getToolState();
|
||||
|
||||
if (toolState.selected === 'brush') {
|
||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
||||
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||
if (drawingBuffer?.type === 'brush_line') {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
} else {
|
||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
||||
await selectedEntityAdapter.renderer.clearBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
if (toolState.selected === 'eraser') {
|
||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
||||
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||
if (drawingBuffer?.type === 'eraser_line') {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
} else {
|
||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
||||
await selectedEntityAdapter.renderer.clearBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
if (toolState.selected === 'rect') {
|
||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
||||
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||
if (drawingBuffer?.type === 'rect') {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
} else {
|
||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
||||
await selectedEntityAdapter.renderer.clearBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,7 +344,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
getIsPrimaryMouseDown(e)
|
||||
) {
|
||||
if (toolState.selected === 'brush') {
|
||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
||||
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||
if (drawingBuffer) {
|
||||
if (drawingBuffer?.type === 'brush_line') {
|
||||
const nextPoint = getNextPoint(pos, toolState, getLastPointOfLine(drawingBuffer.points));
|
||||
@ -352,19 +352,19 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
const normalizedPoint = offsetCoord(nextPoint, selectedEntity.position);
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||
setLastAddedPoint(alignedPoint);
|
||||
}
|
||||
} else {
|
||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
||||
await selectedEntityAdapter.renderer.clearBuffer();
|
||||
}
|
||||
} else {
|
||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
if (selectedEntityAdapter.renderer.buffer) {
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
await selectedEntityAdapter.renderer.setBuffer({
|
||||
id: getObjectId('brush_line', true),
|
||||
type: 'brush_line',
|
||||
points: [alignedPoint.x, alignedPoint.y],
|
||||
@ -377,7 +377,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
}
|
||||
|
||||
if (toolState.selected === 'eraser') {
|
||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
||||
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||
if (drawingBuffer) {
|
||||
if (drawingBuffer.type === 'eraser_line') {
|
||||
const nextPoint = getNextPoint(pos, toolState, getLastPointOfLine(drawingBuffer.points));
|
||||
@ -385,19 +385,19 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
const normalizedPoint = offsetCoord(nextPoint, selectedEntity.position);
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||
setLastAddedPoint(alignedPoint);
|
||||
}
|
||||
} else {
|
||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
||||
await selectedEntityAdapter.renderer.clearBuffer();
|
||||
}
|
||||
} else {
|
||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
if (selectedEntityAdapter.renderer.buffer) {
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
await selectedEntityAdapter.renderer.setBuffer({
|
||||
id: getObjectId('eraser_line', true),
|
||||
type: 'eraser_line',
|
||||
points: [alignedPoint.x, alignedPoint.y],
|
||||
@ -409,15 +409,15 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
}
|
||||
|
||||
if (toolState.selected === 'rect') {
|
||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
||||
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||
if (drawingBuffer) {
|
||||
if (drawingBuffer.type === 'rect') {
|
||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||
drawingBuffer.width = Math.round(normalizedPoint.x - drawingBuffer.x);
|
||||
drawingBuffer.height = Math.round(normalizedPoint.y - drawingBuffer.y);
|
||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||
} else {
|
||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
||||
await selectedEntityAdapter.renderer.clearBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -443,23 +443,23 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
!getSpaceKey() &&
|
||||
getIsPrimaryMouseDown(e)
|
||||
) {
|
||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
||||
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||
if (toolState.selected === 'brush' && drawingBuffer?.type === 'brush_line') {
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
} else if (toolState.selected === 'eraser' && drawingBuffer?.type === 'eraser_line') {
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
} else if (toolState.selected === 'rect' && drawingBuffer?.type === 'rect') {
|
||||
drawingBuffer.width = Math.round(normalizedPoint.x - drawingBuffer.x);
|
||||
drawingBuffer.height = Math.round(normalizedPoint.y - drawingBuffer.y);
|
||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||
await selectedEntityAdapter.renderer.commitBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { getImageDataTransparency } from 'common/util/arrayBuffer';
|
||||
import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { Coordinate, GenerationMode, Rect, CanvasObjectState, RgbaColor } from 'features/controlLayers/store/types';
|
||||
import type { CanvasObjectState, Coordinate, GenerationMode, Rect, RgbaColor } from 'features/controlLayers/store/types';
|
||||
import { isValidLayer } from 'features/nodes/util/graph/generation/addLayers';
|
||||
import Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
@ -414,8 +413,6 @@ export function getCompositeLayerStageClone(arg: { manager: CanvasManager }): Ko
|
||||
if (!layer) {
|
||||
console.log('deleting', konvaLayer);
|
||||
toDelete.push(konvaLayer);
|
||||
} else {
|
||||
konvaLayer.findOne<Konva.Group>(`.${CanvasLayer.GROUP_NAME}`)?.findOne(`.${CanvasLayer.BBOX_NAME}`)?.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,8 +572,16 @@ const zCanvasImageState = z.object({
|
||||
});
|
||||
export type CanvasImageState = z.infer<typeof zCanvasImageState>;
|
||||
|
||||
const zCanvasObjectState = z.discriminatedUnion('type', [zCanvasImageState, zCanvasBrushLineState, zCanvasEraserLineState, zCanvasRectState]);
|
||||
const zCanvasObjectState = z.discriminatedUnion('type', [
|
||||
zCanvasImageState,
|
||||
zCanvasBrushLineState,
|
||||
zCanvasEraserLineState,
|
||||
zCanvasRectState,
|
||||
]);
|
||||
export type CanvasObjectState = z.infer<typeof zCanvasObjectState>;
|
||||
export function isCanvasBrushLineState(obj: CanvasObjectState): obj is CanvasBrushLineState {
|
||||
return obj.type === 'brush_line';
|
||||
}
|
||||
|
||||
export const zCanvasLayerState = z.object({
|
||||
id: zId,
|
||||
@ -603,7 +611,13 @@ export type IPAdapterConfig = Pick<
|
||||
>;
|
||||
|
||||
const zMaskObject = z
|
||||
.discriminatedUnion('type', [zOLD_VectorMaskLine, zOLD_VectorMaskRect, zCanvasBrushLineState, zCanvasEraserLineState, zCanvasRectState])
|
||||
.discriminatedUnion('type', [
|
||||
zOLD_VectorMaskLine,
|
||||
zOLD_VectorMaskRect,
|
||||
zCanvasBrushLineState,
|
||||
zCanvasEraserLineState,
|
||||
zCanvasRectState,
|
||||
])
|
||||
.transform((val) => {
|
||||
// Migrate old vector mask objects to new format
|
||||
if (val.type === 'vector_mask_line') {
|
||||
@ -713,7 +727,10 @@ const zCanvasT2IAdapteState = zCanvasControlAdapterStateBase.extend({
|
||||
});
|
||||
export type CanvasT2IAdapterState = z.infer<typeof zCanvasT2IAdapteState>;
|
||||
|
||||
export const zCanvasControlAdapterState = z.discriminatedUnion('adapterType', [zCanvasControlNetState, zCanvasT2IAdapteState]);
|
||||
export const zCanvasControlAdapterState = z.discriminatedUnion('adapterType', [
|
||||
zCanvasControlNetState,
|
||||
zCanvasT2IAdapteState,
|
||||
]);
|
||||
export type CanvasControlAdapterState = z.infer<typeof zCanvasControlAdapterState>;
|
||||
export type ControlNetConfig = Pick<
|
||||
CanvasControlNetState,
|
||||
@ -949,7 +966,9 @@ export type RemoveIndexString<T> = {
|
||||
|
||||
export type GenerationMode = 'txt2img' | 'img2img' | 'inpaint' | 'outpaint';
|
||||
|
||||
export function isDrawableEntity(entity: CanvasEntity): entity is CanvasLayerState | CanvasRegionalGuidanceState | CanvasInpaintMaskState {
|
||||
export function isDrawableEntity(
|
||||
entity: CanvasEntity
|
||||
): entity is CanvasLayerState | CanvasRegionalGuidanceState | CanvasInpaintMaskState {
|
||||
return entity.type === 'layer' || entity.type === 'regional_guidance' || entity.type === 'inpaint_mask';
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user