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
e5da902fd0
commit
d15be9b57c
@ -1,19 +1,19 @@
|
|||||||
import type { JSONObject } from 'common/types';
|
import type { JSONObject } from 'common/types';
|
||||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
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 type { CanvasBrushLineState } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
|
|
||||||
export class CanvasBrushLine {
|
export class CanvasBrushLineRenderer {
|
||||||
static TYPE = 'brush_line';
|
static TYPE = 'brush_line';
|
||||||
static GROUP_NAME = `${CanvasBrushLine.TYPE}_group`;
|
static GROUP_NAME = `${CanvasBrushLineRenderer.TYPE}_group`;
|
||||||
static LINE_NAME = `${CanvasBrushLine.TYPE}_line`;
|
static LINE_NAME = `${CanvasBrushLineRenderer.TYPE}_line`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
parent: CanvasLayer;
|
parent: CanvasObjectRenderer;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
log: Logger;
|
log: Logger;
|
||||||
getLoggingContext: (extra?: JSONObject) => JSONObject;
|
getLoggingContext: (extra?: JSONObject) => JSONObject;
|
||||||
@ -23,8 +23,9 @@ export class CanvasBrushLine {
|
|||||||
group: Konva.Group;
|
group: Konva.Group;
|
||||||
line: Konva.Line;
|
line: Konva.Line;
|
||||||
};
|
};
|
||||||
|
isFirstRender: boolean = false;
|
||||||
|
|
||||||
constructor(state: CanvasBrushLineState, parent: CanvasLayer) {
|
constructor(state: CanvasBrushLineState, parent: CanvasObjectRenderer) {
|
||||||
const { id, strokeWidth, clip, color, points } = state;
|
const { id, strokeWidth, clip, color, points } = state;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -37,12 +38,12 @@ export class CanvasBrushLine {
|
|||||||
|
|
||||||
this.konva = {
|
this.konva = {
|
||||||
group: new Konva.Group({
|
group: new Konva.Group({
|
||||||
name: CanvasBrushLine.GROUP_NAME,
|
name: CanvasBrushLineRenderer.GROUP_NAME,
|
||||||
clip,
|
clip,
|
||||||
listening: false,
|
listening: false,
|
||||||
}),
|
}),
|
||||||
line: new Konva.Line({
|
line: new Konva.Line({
|
||||||
name: CanvasBrushLine.LINE_NAME,
|
name: CanvasBrushLineRenderer.LINE_NAME,
|
||||||
listening: false,
|
listening: false,
|
||||||
shadowForStrokeEnabled: false,
|
shadowForStrokeEnabled: false,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
@ -59,8 +60,10 @@ export class CanvasBrushLine {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(state: CanvasBrushLineState, force?: boolean): boolean {
|
update(state: CanvasBrushLineState, force = this.isFirstRender): 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({
|
||||||
@ -72,9 +75,9 @@ export class CanvasBrushLine {
|
|||||||
});
|
});
|
||||||
this.state = state;
|
this.state = state;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -90,8 +93,9 @@ export class CanvasBrushLine {
|
|||||||
repr() {
|
repr() {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
type: CanvasBrushLine.TYPE,
|
type: CanvasBrushLineRenderer.TYPE,
|
||||||
parent: this.parent.id,
|
parent: this.parent.id,
|
||||||
|
isFirstRender: this.isFirstRender,
|
||||||
state: deepClone(this.state),
|
state: deepClone(this.state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CanvasEntity } from 'features/controlLayers/konva/CanvasEntity';
|
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 type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||||
import { type CanvasControlAdapterState, isDrawingTool } from 'features/controlLayers/store/types';
|
import { type CanvasControlAdapterState, isDrawingTool } from 'features/controlLayers/store/types';
|
||||||
@ -21,7 +21,7 @@ export class CanvasControlAdapter extends CanvasEntity {
|
|||||||
objectGroup: Konva.Group;
|
objectGroup: Konva.Group;
|
||||||
};
|
};
|
||||||
|
|
||||||
image: CanvasImage | null;
|
image: CanvasImageRenderer | null;
|
||||||
transformer: CanvasTransformer;
|
transformer: CanvasTransformer;
|
||||||
|
|
||||||
constructor(state: CanvasControlAdapterState, manager: CanvasManager) {
|
constructor(state: CanvasControlAdapterState, manager: CanvasManager) {
|
||||||
@ -68,7 +68,7 @@ export class CanvasControlAdapter extends CanvasEntity {
|
|||||||
didDraw = true;
|
didDraw = true;
|
||||||
}
|
}
|
||||||
} else if (!this.image) {
|
} else if (!this.image) {
|
||||||
this.image = new CanvasImage(imageObject, this);
|
this.image = new CanvasImageRenderer(imageObject, this);
|
||||||
this.updateGroup(true);
|
this.updateGroup(true);
|
||||||
this.konva.objectGroup.add(this.image.konva.group);
|
this.konva.objectGroup.add(this.image.konva.group);
|
||||||
await this.image.updateImageSource(imageObject.image.name);
|
await this.image.updateImageSource(imageObject.image.name);
|
||||||
|
@ -1,30 +1,31 @@
|
|||||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
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 type { CanvasEraserLineState, GetLoggingContext } from 'features/controlLayers/store/types';
|
||||||
import { RGBA_RED } from 'features/controlLayers/store/types';
|
import { RGBA_RED } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
|
|
||||||
export class CanvasEraserLine {
|
export class CanvasEraserLineRenderer {
|
||||||
static TYPE = 'eraser_line';
|
static TYPE = 'eraser_line';
|
||||||
static GROUP_NAME = `${CanvasEraserLine.TYPE}_group`;
|
static GROUP_NAME = `${CanvasEraserLineRenderer.TYPE}_group`;
|
||||||
static LINE_NAME = `${CanvasEraserLine.TYPE}_line`;
|
static LINE_NAME = `${CanvasEraserLineRenderer.TYPE}_line`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
parent: CanvasLayer;
|
parent: CanvasObjectRenderer;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
log: Logger;
|
log: Logger;
|
||||||
getLoggingContext: GetLoggingContext;
|
getLoggingContext: GetLoggingContext;
|
||||||
|
|
||||||
|
isFirstRender: boolean = false;
|
||||||
state: CanvasEraserLineState;
|
state: CanvasEraserLineState;
|
||||||
konva: {
|
konva: {
|
||||||
group: Konva.Group;
|
group: Konva.Group;
|
||||||
line: Konva.Line;
|
line: Konva.Line;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(state: CanvasEraserLineState, parent: CanvasLayer) {
|
constructor(state: CanvasEraserLineState, parent: CanvasObjectRenderer) {
|
||||||
const { id, strokeWidth, clip, points } = state;
|
const { id, strokeWidth, clip, points } = state;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -36,12 +37,12 @@ export class CanvasEraserLine {
|
|||||||
|
|
||||||
this.konva = {
|
this.konva = {
|
||||||
group: new Konva.Group({
|
group: new Konva.Group({
|
||||||
name: CanvasEraserLine.GROUP_NAME,
|
name: CanvasEraserLineRenderer.GROUP_NAME,
|
||||||
clip,
|
clip,
|
||||||
listening: false,
|
listening: false,
|
||||||
}),
|
}),
|
||||||
line: new Konva.Line({
|
line: new Konva.Line({
|
||||||
name: CanvasEraserLine.LINE_NAME,
|
name: CanvasEraserLineRenderer.LINE_NAME,
|
||||||
listening: false,
|
listening: false,
|
||||||
shadowForStrokeEnabled: false,
|
shadowForStrokeEnabled: false,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
@ -58,8 +59,10 @@ export class CanvasEraserLine {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(state: CanvasEraserLineState, force?: boolean): boolean {
|
update(state: CanvasEraserLineState, force = this.isFirstRender): 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({
|
||||||
@ -70,9 +73,9 @@ export class CanvasEraserLine {
|
|||||||
});
|
});
|
||||||
this.state = state;
|
this.state = state;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -88,8 +91,9 @@ export class CanvasEraserLine {
|
|||||||
repr() {
|
repr() {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
type: CanvasEraserLine.TYPE,
|
type: CanvasEraserLineRenderer.TYPE,
|
||||||
parent: this.parent.id,
|
parent: this.parent.id,
|
||||||
|
isFirstRender: this.isFirstRender,
|
||||||
state: deepClone(this.state),
|
state: deepClone(this.state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
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 { FILTER_MAP } from 'features/controlLayers/konva/filters';
|
||||||
import { loadImage } from 'features/controlLayers/konva/util';
|
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 { t } from 'i18next';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { getImageDTO } from 'services/api/endpoints/images';
|
import { getImageDTO } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
export class CanvasImage {
|
export class CanvasImageRenderer {
|
||||||
static TYPE = 'image';
|
static TYPE = 'image';
|
||||||
static GROUP_NAME = `${CanvasImage.TYPE}_group`;
|
static GROUP_NAME = `${CanvasImageRenderer.TYPE}_group`;
|
||||||
static IMAGE_NAME = `${CanvasImage.TYPE}_image`;
|
static IMAGE_NAME = `${CanvasImageRenderer.TYPE}_image`;
|
||||||
static PLACEHOLDER_GROUP_NAME = `${CanvasImage.TYPE}_placeholder-group`;
|
static PLACEHOLDER_GROUP_NAME = `${CanvasImageRenderer.TYPE}_placeholder-group`;
|
||||||
static PLACEHOLDER_RECT_NAME = `${CanvasImage.TYPE}_placeholder-rect`;
|
static PLACEHOLDER_RECT_NAME = `${CanvasImageRenderer.TYPE}_placeholder-rect`;
|
||||||
static PLACEHOLDER_TEXT_NAME = `${CanvasImage.TYPE}_placeholder-text`;
|
static PLACEHOLDER_TEXT_NAME = `${CanvasImageRenderer.TYPE}_placeholder-text`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
parent: CanvasLayer | CanvasStagingArea;
|
parent: CanvasObjectRenderer;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
log: Logger;
|
log: Logger;
|
||||||
getLoggingContext: GetLoggingContext;
|
getLoggingContext: GetLoggingContext;
|
||||||
@ -33,8 +32,9 @@ export class CanvasImage {
|
|||||||
imageName: string | null;
|
imageName: string | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
|
isFirstRender: boolean = true;
|
||||||
|
|
||||||
constructor(state: CanvasImageState, parent: CanvasLayer | CanvasStagingArea) {
|
constructor(state: CanvasImageState, parent: CanvasObjectRenderer) {
|
||||||
const { id, width, height, x, y } = state;
|
const { id, width, height, x, y } = state;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -45,18 +45,18 @@ export class CanvasImage {
|
|||||||
this.log.trace({ state }, 'Creating image');
|
this.log.trace({ state }, 'Creating image');
|
||||||
|
|
||||||
this.konva = {
|
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: {
|
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({
|
rect: new Konva.Rect({
|
||||||
name: CanvasImage.PLACEHOLDER_RECT_NAME,
|
name: CanvasImageRenderer.PLACEHOLDER_RECT_NAME,
|
||||||
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
|
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
listening: false,
|
listening: false,
|
||||||
}),
|
}),
|
||||||
text: new Konva.Text({
|
text: new Konva.Text({
|
||||||
name: CanvasImage.PLACEHOLDER_TEXT_NAME,
|
name: CanvasImageRenderer.PLACEHOLDER_TEXT_NAME,
|
||||||
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
|
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -81,7 +81,7 @@ export class CanvasImage {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateImageSource(imageName: string) {
|
updateImageSource = async (imageName: string) => {
|
||||||
try {
|
try {
|
||||||
this.log.trace({ imageName }, 'Updating image source');
|
this.log.trace({ imageName }, 'Updating image source');
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ export class CanvasImage {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.konva.image = new Konva.Image({
|
this.konva.image = new Konva.Image({
|
||||||
name: CanvasImage.IMAGE_NAME,
|
name: CanvasImageRenderer.IMAGE_NAME,
|
||||||
listening: false,
|
listening: false,
|
||||||
image: imageEl,
|
image: imageEl,
|
||||||
width: this.state.width,
|
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.text.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
|
||||||
this.konva.placeholder.group.visible(true);
|
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');
|
this.log.trace({ state }, 'Updating image');
|
||||||
|
|
||||||
const { width, height, x, y, image, filters } = state;
|
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);
|
await this.updateImageSource(image.name);
|
||||||
}
|
}
|
||||||
this.konva.image?.setAttrs({ x, y, width, height });
|
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.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
|
||||||
this.state = state;
|
this.state = state;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
destroy = () => {
|
||||||
this.log.trace('Destroying image');
|
this.log.trace('Destroying image');
|
||||||
this.konva.group.destroy();
|
this.konva.group.destroy();
|
||||||
}
|
};
|
||||||
|
|
||||||
setVisibility(isVisible: boolean): void {
|
setVisibility = (isVisible: boolean): void => {
|
||||||
this.log.trace({ isVisible }, 'Setting image visibility');
|
this.log.trace({ isVisible }, 'Setting image visibility');
|
||||||
this.konva.group.visible(isVisible);
|
this.konva.group.visible(isVisible);
|
||||||
}
|
};
|
||||||
|
|
||||||
repr() {
|
repr = () => {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
type: CanvasImage.TYPE,
|
type: CanvasImageRenderer.TYPE,
|
||||||
parent: this.parent.id,
|
parent: this.parent.id,
|
||||||
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),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -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 { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
@ -20,7 +20,7 @@ export class CanvasInitialImage {
|
|||||||
objectGroup: Konva.Group;
|
objectGroup: Konva.Group;
|
||||||
};
|
};
|
||||||
|
|
||||||
image: CanvasImage | null;
|
image: CanvasImageRenderer | null;
|
||||||
|
|
||||||
constructor(state: InitialImageEntity, manager: CanvasManager) {
|
constructor(state: InitialImageEntity, manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@ -45,7 +45,7 @@ export class CanvasInitialImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.image) {
|
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);
|
this.konva.objectGroup.add(this.image.konva.group);
|
||||||
await this.image.update(this.state.imageObject, true);
|
await this.image.update(this.state.imageObject, true);
|
||||||
} else if (!this.image.isLoading && !this.image.isError) {
|
} else if (!this.image.isLoading && !this.image.isError) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||||
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
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 { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox';
|
||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import type { CanvasBrushLineState, CanvasEraserLineState, CanvasInpaintMaskState, CanvasRectState } from 'features/controlLayers/store/types';
|
import type { CanvasBrushLineState, CanvasEraserLineState, CanvasInpaintMaskState, CanvasRectState } from 'features/controlLayers/store/types';
|
||||||
@ -31,7 +31,7 @@ export class CanvasInpaintMask {
|
|||||||
transformer: Konva.Transformer;
|
transformer: Konva.Transformer;
|
||||||
compositingRect: Konva.Rect;
|
compositingRect: Konva.Rect;
|
||||||
};
|
};
|
||||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
objects: Map<string, CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer>;
|
||||||
|
|
||||||
constructor(state: CanvasInpaintMaskState, manager: CanvasManager) {
|
constructor(state: CanvasInpaintMaskState, manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@ -156,10 +156,10 @@ export class CanvasInpaintMask {
|
|||||||
private async renderObject(obj: CanvasInpaintMaskState['objects'][number], force = false): Promise<boolean> {
|
private async renderObject(obj: CanvasInpaintMaskState['objects'][number], force = false): Promise<boolean> {
|
||||||
if (obj.type === 'brush_line') {
|
if (obj.type === 'brush_line') {
|
||||||
let brushLine = this.objects.get(obj.id);
|
let brushLine = this.objects.get(obj.id);
|
||||||
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
|
assert(brushLine instanceof CanvasBrushLineRenderer || brushLine === undefined);
|
||||||
|
|
||||||
if (!brushLine) {
|
if (!brushLine) {
|
||||||
brushLine = new CanvasBrushLine(obj);
|
brushLine = new CanvasBrushLineRenderer(obj);
|
||||||
this.objects.set(brushLine.id, brushLine);
|
this.objects.set(brushLine.id, brushLine);
|
||||||
this.konva.objectGroup.add(brushLine.konva.group);
|
this.konva.objectGroup.add(brushLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
@ -170,10 +170,10 @@ export class CanvasInpaintMask {
|
|||||||
}
|
}
|
||||||
} else if (obj.type === 'eraser_line') {
|
} else if (obj.type === 'eraser_line') {
|
||||||
let eraserLine = this.objects.get(obj.id);
|
let eraserLine = this.objects.get(obj.id);
|
||||||
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
|
assert(eraserLine instanceof CanvasEraserLineRenderer || eraserLine === undefined);
|
||||||
|
|
||||||
if (!eraserLine) {
|
if (!eraserLine) {
|
||||||
eraserLine = new CanvasEraserLine(obj);
|
eraserLine = new CanvasEraserLineRenderer(obj);
|
||||||
this.objects.set(eraserLine.id, eraserLine);
|
this.objects.set(eraserLine.id, eraserLine);
|
||||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
@ -184,10 +184,10 @@ export class CanvasInpaintMask {
|
|||||||
}
|
}
|
||||||
} else if (obj.type === 'rect') {
|
} else if (obj.type === 'rect') {
|
||||||
let rect = this.objects.get(obj.id);
|
let rect = this.objects.get(obj.id);
|
||||||
assert(rect instanceof CanvasRect || rect === undefined);
|
assert(rect instanceof CanvasRectRenderer || rect === undefined);
|
||||||
|
|
||||||
if (!rect) {
|
if (!rect) {
|
||||||
rect = new CanvasRect(obj);
|
rect = new CanvasRectRenderer(obj);
|
||||||
this.objects.set(rect.id, rect);
|
this.objects.set(rect.id, rect);
|
||||||
this.konva.objectGroup.add(rect.konva.group);
|
this.konva.objectGroup.add(rect.konva.group);
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import { getStore } from 'app/store/nanostores/store';
|
import { getStore } from 'app/store/nanostores/store';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
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 { 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 { 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 { layerRasterized } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import type {
|
import type {
|
||||||
CanvasBrushLineState,
|
|
||||||
CanvasEraserLineState,
|
|
||||||
CanvasLayerState,
|
CanvasLayerState,
|
||||||
CanvasRectState,
|
|
||||||
CanvasV2State,
|
CanvasV2State,
|
||||||
Coordinate,
|
Coordinate,
|
||||||
GetLoggingContext,
|
GetLoggingContext,
|
||||||
@ -23,34 +17,28 @@ import Konva from 'konva';
|
|||||||
import { debounce, get } from 'lodash-es';
|
import { debounce, get } from 'lodash-es';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { uploadImage } from 'services/api/endpoints/images';
|
import { uploadImage } from 'services/api/endpoints/images';
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
export class CanvasLayer {
|
export class CanvasLayer {
|
||||||
static TYPE = 'layer';
|
static TYPE = 'layer';
|
||||||
static LAYER_NAME = `${CanvasLayer.TYPE}_layer`;
|
static KONVA_LAYER_NAME = `${CanvasLayer.TYPE}_layer`;
|
||||||
static TRANSFORMER_NAME = `${CanvasLayer.TYPE}_transformer`;
|
static KONVA_OBJECT_GROUP_NAME = `${CanvasLayer.TYPE}_object-group`;
|
||||||
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`;
|
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
log: Logger;
|
log: Logger;
|
||||||
getLoggingContext: GetLoggingContext;
|
getLoggingContext: GetLoggingContext;
|
||||||
|
|
||||||
drawingBuffer: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null;
|
|
||||||
state: CanvasLayerState;
|
state: CanvasLayerState;
|
||||||
|
|
||||||
konva: {
|
konva: {
|
||||||
layer: Konva.Layer;
|
layer: Konva.Layer;
|
||||||
objectGroup: Konva.Group;
|
objectGroup: Konva.Group;
|
||||||
};
|
};
|
||||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
|
||||||
transformer: CanvasTransformer;
|
transformer: CanvasTransformer;
|
||||||
|
renderer: CanvasObjectRenderer;
|
||||||
|
|
||||||
|
isFirstRender: boolean = true;
|
||||||
bboxNeedsUpdate: boolean;
|
bboxNeedsUpdate: boolean;
|
||||||
isFirstRender: boolean;
|
|
||||||
isTransforming: boolean;
|
isTransforming: boolean;
|
||||||
isPendingBboxCalculation: boolean;
|
isPendingBboxCalculation: boolean;
|
||||||
|
|
||||||
@ -67,26 +55,24 @@ export class CanvasLayer {
|
|||||||
this.konva = {
|
this.konva = {
|
||||||
layer: new Konva.Layer({
|
layer: new Konva.Layer({
|
||||||
id: this.id,
|
id: this.id,
|
||||||
name: CanvasLayer.LAYER_NAME,
|
name: CanvasLayer.KONVA_LAYER_NAME,
|
||||||
listening: false,
|
listening: false,
|
||||||
imageSmoothingEnabled: 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.konva.objectGroup);
|
||||||
this.konva.layer.add(...this.transformer.getNodes());
|
this.konva.layer.add(...this.transformer.getNodes());
|
||||||
|
|
||||||
this.objects = new Map();
|
|
||||||
this.drawingBuffer = null;
|
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.rect = this.getDefaultRect();
|
this.rect = this.getDefaultRect();
|
||||||
this.bbox = this.getDefaultRect();
|
this.bbox = this.getDefaultRect();
|
||||||
this.bboxNeedsUpdate = true;
|
this.bboxNeedsUpdate = true;
|
||||||
this.isTransforming = false;
|
this.isTransforming = false;
|
||||||
this.isFirstRender = true;
|
|
||||||
this.isPendingBboxCalculation = false;
|
this.isPendingBboxCalculation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,47 +80,10 @@ export class CanvasLayer {
|
|||||||
this.log.debug('Destroying layer');
|
this.log.debug('Destroying layer');
|
||||||
// We need to call the destroy method on all children so they can do their own cleanup.
|
// We need to call the destroy method on all children so they can do their own cleanup.
|
||||||
this.transformer.destroy();
|
this.transformer.destroy();
|
||||||
for (const obj of this.objects.values()) {
|
this.renderer.destroy();
|
||||||
obj.destroy();
|
|
||||||
}
|
|
||||||
this.konva.layer.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 }) => {
|
update = async (arg?: { state: CanvasLayerState; toolState: CanvasV2State['tool']; isSelected: boolean }) => {
|
||||||
const state = get(arg, 'state', this.state);
|
const state = get(arg, 'state', this.state);
|
||||||
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
||||||
@ -173,8 +122,7 @@ export class CanvasLayer {
|
|||||||
updateVisibility = (arg?: { isEnabled: boolean }) => {
|
updateVisibility = (arg?: { isEnabled: boolean }) => {
|
||||||
this.log.trace('Updating visibility');
|
this.log.trace('Updating visibility');
|
||||||
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);
|
const isEnabled = get(arg, 'isEnabled', this.state.isEnabled);
|
||||||
const hasObjects = this.objects.size > 0 || this.drawingBuffer !== null;
|
this.konva.layer.visible(isEnabled && this.renderer.hasObjects());
|
||||||
this.konva.layer.visible(isEnabled && hasObjects);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updatePosition = (arg?: { position: Coordinate }) => {
|
updatePosition = (arg?: { position: Coordinate }) => {
|
||||||
@ -196,30 +144,7 @@ export class CanvasLayer {
|
|||||||
|
|
||||||
const objects = get(arg, 'objects', this.state.objects);
|
const objects = get(arg, 'objects', this.state.objects);
|
||||||
|
|
||||||
const objectIds = objects.map(mapId);
|
const didUpdate = await this.renderer.render(objects);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (didUpdate) {
|
if (didUpdate) {
|
||||||
this.calculateBbox();
|
this.calculateBbox();
|
||||||
@ -240,7 +165,7 @@ export class CanvasLayer {
|
|||||||
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
||||||
const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id));
|
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
|
// The layer is totally empty, we can just disable the layer
|
||||||
this.konva.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
this.transformer.setMode('off');
|
this.transformer.setMode('off');
|
||||||
@ -279,7 +204,7 @@ export class CanvasLayer {
|
|||||||
// eraser lines, fully clipped brush lines or if it has been fully erased.
|
// eraser lines, fully clipped brush lines or if it has been fully erased.
|
||||||
if (this.bbox.width === 0 || this.bbox.height === 0) {
|
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
|
// 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
|
// The layer is fully transparent but has objects - reset it
|
||||||
this.manager.stateApi.onEntityReset({ id: this.id }, 'layer');
|
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 = () => {
|
startTransform = () => {
|
||||||
this.log.debug('Starting transform');
|
this.log.debug('Starting transform');
|
||||||
this.isTransforming = true;
|
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
|
// 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
|
// interaction rect are listening, it will interrupt the stage's drag events. So we should disable listening
|
||||||
// when the view tool is selected
|
// when the view tool is selected
|
||||||
const listening = this.manager.stateApi.getToolState().selected !== 'view';
|
const shouldListen = this.manager.stateApi.getToolState().selected !== 'view';
|
||||||
|
this.konva.layer.listening(shouldListen);
|
||||||
this.konva.layer.listening(listening);
|
|
||||||
this.transformer.setMode('transform');
|
this.transformer.setMode('transform');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -395,12 +258,8 @@ export class CanvasLayer {
|
|||||||
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
|
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
|
||||||
const { dispatch } = getStore();
|
const { dispatch } = getStore();
|
||||||
const imageObject = imageDTOToImageObject(imageDTO);
|
const imageObject = imageDTOToImageObject(imageDTO);
|
||||||
await this._renderObject(imageObject, true);
|
await this.renderer.renderObject(imageObject, true);
|
||||||
for (const obj of this.objects.values()) {
|
this.renderer.hideAll([imageObject.id]);
|
||||||
if (obj.id !== imageObject.id) {
|
|
||||||
obj.konva.group.visible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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) } }));
|
||||||
};
|
};
|
||||||
@ -424,7 +283,7 @@ export class CanvasLayer {
|
|||||||
|
|
||||||
this.isPendingBboxCalculation = true;
|
this.isPendingBboxCalculation = true;
|
||||||
|
|
||||||
if (this.objects.size === 0) {
|
if (!this.renderer.hasObjects()) {
|
||||||
this.log.trace('No objects, resetting bbox');
|
this.log.trace('No objects, resetting bbox');
|
||||||
this.rect = this.getDefaultRect();
|
this.rect = this.getDefaultRect();
|
||||||
this.bbox = this.getDefaultRect();
|
this.bbox = this.getDefaultRect();
|
||||||
@ -435,30 +294,7 @@ export class CanvasLayer {
|
|||||||
|
|
||||||
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
|
const rect = this.konva.objectGroup.getClientRect({ skipTransform: true });
|
||||||
|
|
||||||
/**
|
if (!this.renderer.needsPixelBbox()) {
|
||||||
* 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) {
|
|
||||||
this.rect = deepClone(rect);
|
this.rect = deepClone(rect);
|
||||||
this.bbox = deepClone(rect);
|
this.bbox = deepClone(rect);
|
||||||
this.isPendingBboxCalculation = false;
|
this.isPendingBboxCalculation = false;
|
||||||
@ -508,10 +344,10 @@ export class CanvasLayer {
|
|||||||
rect: deepClone(this.rect),
|
rect: deepClone(this.rect),
|
||||||
bbox: deepClone(this.bbox),
|
bbox: deepClone(this.bbox),
|
||||||
bboxNeedsUpdate: this.bboxNeedsUpdate,
|
bboxNeedsUpdate: this.bboxNeedsUpdate,
|
||||||
isFirstRender: this.isFirstRender,
|
|
||||||
isTransforming: this.isTransforming,
|
isTransforming: this.isTransforming,
|
||||||
isPendingBboxCalculation: this.isPendingBboxCalculation,
|
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 { logger } from 'app/logging/logger';
|
||||||
import type { RootState } from 'app/store/store';
|
import type { RootState } from 'app/store/store';
|
||||||
import type { JSONObject } from 'common/types';
|
import type { JSONObject } from 'common/types';
|
||||||
import type { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
import type { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||||
import type { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
import type { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||||
import type { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
import type { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||||
import { CanvasInitialImage } from 'features/controlLayers/konva/CanvasInitialImage';
|
import { CanvasInitialImage } from 'features/controlLayers/konva/CanvasInitialImage';
|
||||||
|
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||||
import { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
|
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 type { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||||
import {
|
import {
|
||||||
getCompositeLayerImage,
|
getCompositeLayerImage,
|
||||||
@ -593,11 +594,12 @@ export class CanvasManager {
|
|||||||
|
|
||||||
buildGetLoggingContext = (
|
buildGetLoggingContext = (
|
||||||
instance:
|
instance:
|
||||||
| CanvasBrushLine
|
| CanvasBrushLineRenderer
|
||||||
| CanvasEraserLine
|
| CanvasEraserLineRenderer
|
||||||
| CanvasRect
|
| CanvasRectRenderer
|
||||||
| CanvasImage
|
| CanvasImageRenderer
|
||||||
| CanvasTransformer
|
| CanvasTransformer
|
||||||
|
| CanvasObjectRenderer
|
||||||
| CanvasLayer
|
| CanvasLayer
|
||||||
| CanvasStagingArea
|
| CanvasStagingArea
|
||||||
): GetLoggingContext => {
|
): GetLoggingContext => {
|
||||||
@ -609,6 +611,14 @@ export class CanvasManager {
|
|||||||
...extra,
|
...extra,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
} else if (instance instanceof CanvasObjectRenderer) {
|
||||||
|
return (extra?: JSONObject): JSONObject => {
|
||||||
|
return {
|
||||||
|
...instance.parent.getLoggingContext(),
|
||||||
|
rendererId: instance.id,
|
||||||
|
...extra,
|
||||||
|
};
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return (extra?: JSONObject): JSONObject => {
|
return (extra?: JSONObject): JSONObject => {
|
||||||
return {
|
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 { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
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 Konva from 'konva';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
|
|
||||||
export class CanvasRect {
|
export class CanvasRectRenderer {
|
||||||
static TYPE = 'rect';
|
static TYPE = 'rect';
|
||||||
static GROUP_NAME = `${CanvasRect.TYPE}_group`;
|
static GROUP_NAME = `${CanvasRectRenderer.TYPE}_group`;
|
||||||
static RECT_NAME = `${CanvasRect.TYPE}_rect`;
|
static RECT_NAME = `${CanvasRectRenderer.TYPE}_rect`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
parent: CanvasLayer;
|
parent: CanvasObjectRenderer;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
log: Logger;
|
log: Logger;
|
||||||
getLoggingContext: GetLoggingContext;
|
getLoggingContext: GetLoggingContext;
|
||||||
@ -22,8 +22,9 @@ export class CanvasRect {
|
|||||||
group: Konva.Group;
|
group: Konva.Group;
|
||||||
rect: Konva.Rect;
|
rect: Konva.Rect;
|
||||||
};
|
};
|
||||||
|
isFirstRender: boolean = false;
|
||||||
|
|
||||||
constructor(state: CanvasRectState, parent: CanvasLayer) {
|
constructor(state: CanvasRectState, parent: CanvasObjectRenderer) {
|
||||||
const { id, x, y, width, height, color } = state;
|
const { id, x, y, width, height, color } = state;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -33,9 +34,9 @@ export class CanvasRect {
|
|||||||
this.log.trace({ state }, 'Creating rect');
|
this.log.trace({ state }, 'Creating rect');
|
||||||
|
|
||||||
this.konva = {
|
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({
|
rect: new Konva.Rect({
|
||||||
name: CanvasRect.RECT_NAME,
|
name: CanvasRectRenderer.RECT_NAME,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
@ -48,8 +49,10 @@ export class CanvasRect {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(state: CanvasRectState, force?: boolean): boolean {
|
update(state: CanvasRectState, force = this.isFirstRender): boolean {
|
||||||
if (this.state !== state || force) {
|
if (this.state !== state || force) {
|
||||||
|
this.isFirstRender = false;
|
||||||
|
|
||||||
this.log.trace({ state }, 'Updating rect');
|
this.log.trace({ state }, 'Updating rect');
|
||||||
const { x, y, width, height, color } = state;
|
const { x, y, width, height, color } = state;
|
||||||
this.konva.rect.setAttrs({
|
this.konva.rect.setAttrs({
|
||||||
@ -61,9 +64,9 @@ export class CanvasRect {
|
|||||||
});
|
});
|
||||||
this.state = state;
|
this.state = state;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -79,8 +82,9 @@ export class CanvasRect {
|
|||||||
repr() {
|
repr() {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
type: CanvasRect.TYPE,
|
type: CanvasRectRenderer.TYPE,
|
||||||
parent: this.parent.id,
|
parent: this.parent.id,
|
||||||
|
isFirstRender: this.isFirstRender,
|
||||||
state: deepClone(this.state),
|
state: deepClone(this.state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||||
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||||
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
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 { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox';
|
||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import type { CanvasBrushLineState, CanvasEraserLineState, CanvasRectState, CanvasRegionalGuidanceState } from 'features/controlLayers/store/types';
|
import type { CanvasBrushLineState, CanvasEraserLineState, CanvasRectState, CanvasRegionalGuidanceState } from 'features/controlLayers/store/types';
|
||||||
@ -32,7 +32,7 @@ export class CanvasRegion {
|
|||||||
transformer: Konva.Transformer;
|
transformer: Konva.Transformer;
|
||||||
};
|
};
|
||||||
|
|
||||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
objects: Map<string, CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer>;
|
||||||
|
|
||||||
constructor(state: CanvasRegionalGuidanceState, manager: CanvasManager) {
|
constructor(state: CanvasRegionalGuidanceState, manager: CanvasManager) {
|
||||||
this.id = state.id;
|
this.id = state.id;
|
||||||
@ -155,10 +155,10 @@ export class CanvasRegion {
|
|||||||
private async renderObject(obj: CanvasRegionalGuidanceState['objects'][number], force = false): Promise<boolean> {
|
private async renderObject(obj: CanvasRegionalGuidanceState['objects'][number], force = false): Promise<boolean> {
|
||||||
if (obj.type === 'brush_line') {
|
if (obj.type === 'brush_line') {
|
||||||
let brushLine = this.objects.get(obj.id);
|
let brushLine = this.objects.get(obj.id);
|
||||||
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
|
assert(brushLine instanceof CanvasBrushLineRenderer || brushLine === undefined);
|
||||||
|
|
||||||
if (!brushLine) {
|
if (!brushLine) {
|
||||||
brushLine = new CanvasBrushLine(obj);
|
brushLine = new CanvasBrushLineRenderer(obj);
|
||||||
this.objects.set(brushLine.id, brushLine);
|
this.objects.set(brushLine.id, brushLine);
|
||||||
this.konva.objectGroup.add(brushLine.konva.group);
|
this.konva.objectGroup.add(brushLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
@ -169,10 +169,10 @@ export class CanvasRegion {
|
|||||||
}
|
}
|
||||||
} else if (obj.type === 'eraser_line') {
|
} else if (obj.type === 'eraser_line') {
|
||||||
let eraserLine = this.objects.get(obj.id);
|
let eraserLine = this.objects.get(obj.id);
|
||||||
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
|
assert(eraserLine instanceof CanvasEraserLineRenderer || eraserLine === undefined);
|
||||||
|
|
||||||
if (!eraserLine) {
|
if (!eraserLine) {
|
||||||
eraserLine = new CanvasEraserLine(obj);
|
eraserLine = new CanvasEraserLineRenderer(obj);
|
||||||
this.objects.set(eraserLine.id, eraserLine);
|
this.objects.set(eraserLine.id, eraserLine);
|
||||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
@ -183,10 +183,10 @@ export class CanvasRegion {
|
|||||||
}
|
}
|
||||||
} else if (obj.type === 'rect') {
|
} else if (obj.type === 'rect') {
|
||||||
let rect = this.objects.get(obj.id);
|
let rect = this.objects.get(obj.id);
|
||||||
assert(rect instanceof CanvasRect || rect === undefined);
|
assert(rect instanceof CanvasRectRenderer || rect === undefined);
|
||||||
|
|
||||||
if (!rect) {
|
if (!rect) {
|
||||||
rect = new CanvasRect(obj);
|
rect = new CanvasRectRenderer(obj);
|
||||||
this.objects.set(rect.id, rect);
|
this.objects.set(rect.id, rect);
|
||||||
this.konva.objectGroup.add(rect.konva.group);
|
this.konva.objectGroup.add(rect.konva.group);
|
||||||
return true;
|
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 type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||||
import type { GetLoggingContext, StagingAreaImage } from 'features/controlLayers/store/types';
|
import type { GetLoggingContext, StagingAreaImage } from 'features/controlLayers/store/types';
|
||||||
@ -16,7 +16,7 @@ export class CanvasStagingArea {
|
|||||||
|
|
||||||
konva: { group: Konva.Group };
|
konva: { group: Konva.Group };
|
||||||
|
|
||||||
image: CanvasImage | null;
|
image: CanvasImageRenderer | null;
|
||||||
selectedImage: StagingAreaImage | null;
|
selectedImage: StagingAreaImage | null;
|
||||||
|
|
||||||
constructor(manager: CanvasManager) {
|
constructor(manager: CanvasManager) {
|
||||||
@ -43,7 +43,7 @@ export class CanvasStagingArea {
|
|||||||
|
|
||||||
if (!this.image) {
|
if (!this.image) {
|
||||||
const { image_name, width, height } = imageDTO;
|
const { image_name, width, height } = imageDTO;
|
||||||
this.image = new CanvasImage(
|
this.image = new CanvasImageRenderer(
|
||||||
{
|
{
|
||||||
id: 'staging-area-image',
|
id: 'staging-area-image',
|
||||||
type: 'image',
|
type: 'image',
|
||||||
|
@ -46,22 +46,16 @@ export class CanvasTransformer {
|
|||||||
*/
|
*/
|
||||||
isTransformEnabled: boolean;
|
isTransformEnabled: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* The konva group that the transformer will manipulate.
|
|
||||||
*/
|
|
||||||
transformTarget: Konva.Group;
|
|
||||||
|
|
||||||
konva: {
|
konva: {
|
||||||
transformer: Konva.Transformer;
|
transformer: Konva.Transformer;
|
||||||
proxyRect: Konva.Rect;
|
proxyRect: Konva.Rect;
|
||||||
bboxOutline: Konva.Rect;
|
bboxOutline: Konva.Rect;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(parent: CanvasLayer, transformTarget: Konva.Group) {
|
constructor(parent: CanvasLayer) {
|
||||||
this.id = getPrefixedId(CanvasTransformer.TYPE);
|
this.id = getPrefixedId(CanvasTransformer.TYPE);
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.manager = parent.manager;
|
this.manager = parent.manager;
|
||||||
this.transformTarget = transformTarget;
|
|
||||||
|
|
||||||
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
|
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
|
||||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
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
|
// 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
|
// 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.
|
// updated attributes to the object group, propagating the transformation on down.
|
||||||
this.transformTarget.setAttrs({
|
this.parent.konva.objectGroup.setAttrs({
|
||||||
x: this.konva.proxyRect.x(),
|
x: this.konva.proxyRect.x(),
|
||||||
y: this.konva.proxyRect.y(),
|
y: this.konva.proxyRect.y(),
|
||||||
scaleX: this.konva.proxyRect.scaleX(),
|
scaleX: this.konva.proxyRect.scaleX(),
|
||||||
@ -234,7 +228,7 @@ export class CanvasTransformer {
|
|||||||
scaleX: snappedScaleX,
|
scaleX: snappedScaleX,
|
||||||
scaleY: snappedScaleY,
|
scaleY: snappedScaleY,
|
||||||
});
|
});
|
||||||
this.transformTarget.setAttrs({
|
this.parent.konva.objectGroup.setAttrs({
|
||||||
x: snappedX,
|
x: snappedX,
|
||||||
y: snappedY,
|
y: snappedY,
|
||||||
scaleX: snappedScaleX,
|
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
|
// The object group is translated by the difference between the interaction rect's new and old positions (which is
|
||||||
// stored as this.bbox)
|
// stored as this.bbox)
|
||||||
this.transformTarget.setAttrs({
|
this.parent.konva.objectGroup.setAttrs({
|
||||||
x: this.konva.proxyRect.x(),
|
x: this.konva.proxyRect.x(),
|
||||||
y: this.konva.proxyRect.y(),
|
y: this.konva.proxyRect.y(),
|
||||||
});
|
});
|
||||||
|
@ -6,11 +6,11 @@ import {
|
|||||||
offsetCoord,
|
offsetCoord,
|
||||||
} from 'features/controlLayers/konva/util';
|
} from 'features/controlLayers/konva/util';
|
||||||
import type {
|
import type {
|
||||||
CanvasV2State,
|
|
||||||
Coordinate,
|
|
||||||
CanvasInpaintMaskState,
|
CanvasInpaintMaskState,
|
||||||
CanvasLayerState,
|
CanvasLayerState,
|
||||||
CanvasRegionalGuidanceState,
|
CanvasRegionalGuidanceState,
|
||||||
|
CanvasV2State,
|
||||||
|
Coordinate,
|
||||||
Tool,
|
Tool,
|
||||||
} from 'features/controlLayers/store/types';
|
} from 'features/controlLayers/store/types';
|
||||||
import { isDrawableEntity, isDrawableEntityAdapter } 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);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||||
if (e.evt.shiftKey && lastLinePoint) {
|
if (e.evt.shiftKey && lastLinePoint) {
|
||||||
// Create a straight line from the last line point
|
// Create a straight line from the last line point
|
||||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
if (selectedEntityAdapter.renderer.buffer) {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
await selectedEntityAdapter.setDrawingBuffer({
|
await selectedEntityAdapter.renderer.setBuffer({
|
||||||
id: getObjectId('brush_line', true),
|
id: getObjectId('brush_line', true),
|
||||||
type: 'brush_line',
|
type: 'brush_line',
|
||||||
points: [
|
points: [
|
||||||
@ -208,10 +208,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
clip: getClip(selectedEntity),
|
clip: getClip(selectedEntity),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
if (selectedEntityAdapter.renderer.buffer) {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
await selectedEntityAdapter.setDrawingBuffer({
|
await selectedEntityAdapter.renderer.setBuffer({
|
||||||
id: getObjectId('brush_line', true),
|
id: getObjectId('brush_line', true),
|
||||||
type: 'brush_line',
|
type: 'brush_line',
|
||||||
points: [alignedPoint.x, alignedPoint.y],
|
points: [alignedPoint.x, alignedPoint.y],
|
||||||
@ -228,10 +228,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||||
if (e.evt.shiftKey && lastLinePoint) {
|
if (e.evt.shiftKey && lastLinePoint) {
|
||||||
// Create a straight line from the last line point
|
// Create a straight line from the last line point
|
||||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
if (selectedEntityAdapter.renderer.buffer) {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
await selectedEntityAdapter.setDrawingBuffer({
|
await selectedEntityAdapter.renderer.setBuffer({
|
||||||
id: getObjectId('eraser_line', true),
|
id: getObjectId('eraser_line', true),
|
||||||
type: 'eraser_line',
|
type: 'eraser_line',
|
||||||
points: [
|
points: [
|
||||||
@ -245,10 +245,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
clip: getClip(selectedEntity),
|
clip: getClip(selectedEntity),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
if (selectedEntityAdapter.renderer.buffer) {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
await selectedEntityAdapter.setDrawingBuffer({
|
await selectedEntityAdapter.renderer.setBuffer({
|
||||||
id: getObjectId('eraser_line', true),
|
id: getObjectId('eraser_line', true),
|
||||||
type: 'eraser_line',
|
type: 'eraser_line',
|
||||||
points: [alignedPoint.x, alignedPoint.y],
|
points: [alignedPoint.x, alignedPoint.y],
|
||||||
@ -260,10 +260,10 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toolState.selected === 'rect') {
|
if (toolState.selected === 'rect') {
|
||||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
if (selectedEntityAdapter.renderer.buffer) {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
await selectedEntityAdapter.setDrawingBuffer({
|
await selectedEntityAdapter.renderer.setBuffer({
|
||||||
id: getObjectId('rect', true),
|
id: getObjectId('rect', true),
|
||||||
type: 'rect',
|
type: 'rect',
|
||||||
x: Math.round(normalizedPoint.x),
|
x: Math.round(normalizedPoint.x),
|
||||||
@ -295,29 +295,29 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
const toolState = getToolState();
|
const toolState = getToolState();
|
||||||
|
|
||||||
if (toolState.selected === 'brush') {
|
if (toolState.selected === 'brush') {
|
||||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||||
if (drawingBuffer?.type === 'brush_line') {
|
if (drawingBuffer?.type === 'brush_line') {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
} else {
|
} else {
|
||||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
await selectedEntityAdapter.renderer.clearBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolState.selected === 'eraser') {
|
if (toolState.selected === 'eraser') {
|
||||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||||
if (drawingBuffer?.type === 'eraser_line') {
|
if (drawingBuffer?.type === 'eraser_line') {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
} else {
|
} else {
|
||||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
await selectedEntityAdapter.renderer.clearBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolState.selected === 'rect') {
|
if (toolState.selected === 'rect') {
|
||||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||||
if (drawingBuffer?.type === 'rect') {
|
if (drawingBuffer?.type === 'rect') {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
} else {
|
} else {
|
||||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
await selectedEntityAdapter.renderer.clearBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +344,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
getIsPrimaryMouseDown(e)
|
getIsPrimaryMouseDown(e)
|
||||||
) {
|
) {
|
||||||
if (toolState.selected === 'brush') {
|
if (toolState.selected === 'brush') {
|
||||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||||
if (drawingBuffer) {
|
if (drawingBuffer) {
|
||||||
if (drawingBuffer?.type === 'brush_line') {
|
if (drawingBuffer?.type === 'brush_line') {
|
||||||
const nextPoint = getNextPoint(pos, toolState, getLastPointOfLine(drawingBuffer.points));
|
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 normalizedPoint = offsetCoord(nextPoint, selectedEntity.position);
|
||||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||||
setLastAddedPoint(alignedPoint);
|
setLastAddedPoint(alignedPoint);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
await selectedEntityAdapter.renderer.clearBuffer();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
if (selectedEntityAdapter.renderer.buffer) {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||||
await selectedEntityAdapter.setDrawingBuffer({
|
await selectedEntityAdapter.renderer.setBuffer({
|
||||||
id: getObjectId('brush_line', true),
|
id: getObjectId('brush_line', true),
|
||||||
type: 'brush_line',
|
type: 'brush_line',
|
||||||
points: [alignedPoint.x, alignedPoint.y],
|
points: [alignedPoint.x, alignedPoint.y],
|
||||||
@ -377,7 +377,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toolState.selected === 'eraser') {
|
if (toolState.selected === 'eraser') {
|
||||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||||
if (drawingBuffer) {
|
if (drawingBuffer) {
|
||||||
if (drawingBuffer.type === 'eraser_line') {
|
if (drawingBuffer.type === 'eraser_line') {
|
||||||
const nextPoint = getNextPoint(pos, toolState, getLastPointOfLine(drawingBuffer.points));
|
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 normalizedPoint = offsetCoord(nextPoint, selectedEntity.position);
|
||||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||||
setLastAddedPoint(alignedPoint);
|
setLastAddedPoint(alignedPoint);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
await selectedEntityAdapter.renderer.clearBuffer();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (selectedEntityAdapter.getDrawingBuffer()) {
|
if (selectedEntityAdapter.renderer.buffer) {
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||||
await selectedEntityAdapter.setDrawingBuffer({
|
await selectedEntityAdapter.renderer.setBuffer({
|
||||||
id: getObjectId('eraser_line', true),
|
id: getObjectId('eraser_line', true),
|
||||||
type: 'eraser_line',
|
type: 'eraser_line',
|
||||||
points: [alignedPoint.x, alignedPoint.y],
|
points: [alignedPoint.x, alignedPoint.y],
|
||||||
@ -409,15 +409,15 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toolState.selected === 'rect') {
|
if (toolState.selected === 'rect') {
|
||||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||||
if (drawingBuffer) {
|
if (drawingBuffer) {
|
||||||
if (drawingBuffer.type === 'rect') {
|
if (drawingBuffer.type === 'rect') {
|
||||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||||
drawingBuffer.width = Math.round(normalizedPoint.x - drawingBuffer.x);
|
drawingBuffer.width = Math.round(normalizedPoint.x - drawingBuffer.x);
|
||||||
drawingBuffer.height = Math.round(normalizedPoint.y - drawingBuffer.y);
|
drawingBuffer.height = Math.round(normalizedPoint.y - drawingBuffer.y);
|
||||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||||
} else {
|
} else {
|
||||||
await selectedEntityAdapter.setDrawingBuffer(null);
|
await selectedEntityAdapter.renderer.clearBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -443,23 +443,23 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
|||||||
!getSpaceKey() &&
|
!getSpaceKey() &&
|
||||||
getIsPrimaryMouseDown(e)
|
getIsPrimaryMouseDown(e)
|
||||||
) {
|
) {
|
||||||
const drawingBuffer = selectedEntityAdapter.getDrawingBuffer();
|
const drawingBuffer = selectedEntityAdapter.renderer.buffer;
|
||||||
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
const normalizedPoint = offsetCoord(pos, selectedEntity.position);
|
||||||
if (toolState.selected === 'brush' && drawingBuffer?.type === 'brush_line') {
|
if (toolState.selected === 'brush' && drawingBuffer?.type === 'brush_line') {
|
||||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
} else if (toolState.selected === 'eraser' && drawingBuffer?.type === 'eraser_line') {
|
} else if (toolState.selected === 'eraser' && drawingBuffer?.type === 'eraser_line') {
|
||||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||||
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
drawingBuffer.points.push(alignedPoint.x, alignedPoint.y);
|
||||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
} else if (toolState.selected === 'rect' && drawingBuffer?.type === 'rect') {
|
} else if (toolState.selected === 'rect' && drawingBuffer?.type === 'rect') {
|
||||||
drawingBuffer.width = Math.round(normalizedPoint.x - drawingBuffer.x);
|
drawingBuffer.width = Math.round(normalizedPoint.x - drawingBuffer.x);
|
||||||
drawingBuffer.height = Math.round(normalizedPoint.y - drawingBuffer.y);
|
drawingBuffer.height = Math.round(normalizedPoint.y - drawingBuffer.y);
|
||||||
await selectedEntityAdapter.setDrawingBuffer(drawingBuffer);
|
await selectedEntityAdapter.renderer.setBuffer(drawingBuffer);
|
||||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
await selectedEntityAdapter.renderer.commitBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { getImageDataTransparency } from 'common/util/arrayBuffer';
|
import { getImageDataTransparency } from 'common/util/arrayBuffer';
|
||||||
import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
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 { isValidLayer } from 'features/nodes/util/graph/generation/addLayers';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||||
@ -414,8 +413,6 @@ export function getCompositeLayerStageClone(arg: { manager: CanvasManager }): Ko
|
|||||||
if (!layer) {
|
if (!layer) {
|
||||||
console.log('deleting', konvaLayer);
|
console.log('deleting', konvaLayer);
|
||||||
toDelete.push(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>;
|
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 type CanvasObjectState = z.infer<typeof zCanvasObjectState>;
|
||||||
|
export function isCanvasBrushLineState(obj: CanvasObjectState): obj is CanvasBrushLineState {
|
||||||
|
return obj.type === 'brush_line';
|
||||||
|
}
|
||||||
|
|
||||||
export const zCanvasLayerState = z.object({
|
export const zCanvasLayerState = z.object({
|
||||||
id: zId,
|
id: zId,
|
||||||
@ -603,7 +611,13 @@ export type IPAdapterConfig = Pick<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const zMaskObject = z
|
const zMaskObject = z
|
||||||
.discriminatedUnion('type', [zOLD_VectorMaskLine, zOLD_VectorMaskRect, zCanvasBrushLineState, zCanvasEraserLineState, zCanvasRectState])
|
.discriminatedUnion('type', [
|
||||||
|
zOLD_VectorMaskLine,
|
||||||
|
zOLD_VectorMaskRect,
|
||||||
|
zCanvasBrushLineState,
|
||||||
|
zCanvasEraserLineState,
|
||||||
|
zCanvasRectState,
|
||||||
|
])
|
||||||
.transform((val) => {
|
.transform((val) => {
|
||||||
// Migrate old vector mask objects to new format
|
// Migrate old vector mask objects to new format
|
||||||
if (val.type === 'vector_mask_line') {
|
if (val.type === 'vector_mask_line') {
|
||||||
@ -713,7 +727,10 @@ const zCanvasT2IAdapteState = zCanvasControlAdapterStateBase.extend({
|
|||||||
});
|
});
|
||||||
export type CanvasT2IAdapterState = z.infer<typeof zCanvasT2IAdapteState>;
|
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 CanvasControlAdapterState = z.infer<typeof zCanvasControlAdapterState>;
|
||||||
export type ControlNetConfig = Pick<
|
export type ControlNetConfig = Pick<
|
||||||
CanvasControlNetState,
|
CanvasControlNetState,
|
||||||
@ -949,7 +966,9 @@ export type RemoveIndexString<T> = {
|
|||||||
|
|
||||||
export type GenerationMode = 'txt2img' | 'img2img' | 'inpaint' | 'outpaint';
|
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';
|
return entity.type === 'layer' || entity.type === 'regional_guidance' || entity.type === 'inpaint_mask';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user