mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
tidy(ui): update canvas classes, organise location of konva nodes
This commit is contained in:
parent
2ef8a8cf5a
commit
9483c8cc29
@ -33,16 +33,19 @@ export class CanvasBackground {
|
|||||||
static BASE_NAME = 'background';
|
static BASE_NAME = 'background';
|
||||||
static LAYER_NAME = `${CanvasBackground.BASE_NAME}_layer`;
|
static LAYER_NAME = `${CanvasBackground.BASE_NAME}_layer`;
|
||||||
|
|
||||||
layer: Konva.Layer;
|
konva: {
|
||||||
|
layer: Konva.Layer;
|
||||||
|
};
|
||||||
|
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
|
|
||||||
constructor(manager: CanvasManager) {
|
constructor(manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.layer = new Konva.Layer({ name: CanvasBackground.LAYER_NAME, listening: false });
|
this.konva = { layer: new Konva.Layer({ name: CanvasBackground.LAYER_NAME, listening: false }) };
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.layer.zIndex(0);
|
this.konva.layer.zIndex(0);
|
||||||
const scale = this.manager.stage.scaleX();
|
const scale = this.manager.stage.scaleX();
|
||||||
const gridSpacing = getGridSpacing(scale);
|
const gridSpacing = getGridSpacing(scale);
|
||||||
const x = this.manager.stage.x();
|
const x = this.manager.stage.x();
|
||||||
@ -86,11 +89,11 @@ export class CanvasBackground {
|
|||||||
let _x = 0;
|
let _x = 0;
|
||||||
let _y = 0;
|
let _y = 0;
|
||||||
|
|
||||||
this.layer.destroyChildren();
|
this.konva.layer.destroyChildren();
|
||||||
|
|
||||||
for (let i = 0; i < xSteps; i++) {
|
for (let i = 0; i < xSteps; i++) {
|
||||||
_x = gridFullRect.x1 + i * gridSpacing;
|
_x = gridFullRect.x1 + i * gridSpacing;
|
||||||
this.layer.add(
|
this.konva.layer.add(
|
||||||
new Konva.Line({
|
new Konva.Line({
|
||||||
x: _x,
|
x: _x,
|
||||||
y: gridFullRect.y1,
|
y: gridFullRect.y1,
|
||||||
@ -103,7 +106,7 @@ export class CanvasBackground {
|
|||||||
}
|
}
|
||||||
for (let i = 0; i < ySteps; i++) {
|
for (let i = 0; i < ySteps; i++) {
|
||||||
_y = gridFullRect.y1 + i * gridSpacing;
|
_y = gridFullRect.y1 + i * gridSpacing;
|
||||||
this.layer.add(
|
this.konva.layer.add(
|
||||||
new Konva.Line({
|
new Konva.Line({
|
||||||
x: gridFullRect.x1,
|
x: gridFullRect.x1,
|
||||||
y: _y,
|
y: _y,
|
||||||
|
@ -11,11 +11,10 @@ export class CanvasBbox {
|
|||||||
static RECT_NAME = `${CanvasBbox.BASE_NAME}_rect`;
|
static RECT_NAME = `${CanvasBbox.BASE_NAME}_rect`;
|
||||||
static TRANSFORMER_NAME = `${CanvasBbox.BASE_NAME}_transformer`;
|
static TRANSFORMER_NAME = `${CanvasBbox.BASE_NAME}_transformer`;
|
||||||
|
|
||||||
group: Konva.Group;
|
|
||||||
rect: Konva.Rect;
|
|
||||||
transformer: Konva.Transformer;
|
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
|
|
||||||
|
konva: { group: Konva.Group; rect: Konva.Rect; transformer: Konva.Transformer };
|
||||||
|
|
||||||
ALL_ANCHORS: string[] = [
|
ALL_ANCHORS: string[] = [
|
||||||
'top-left',
|
'top-left',
|
||||||
'top-center',
|
'top-center',
|
||||||
@ -36,88 +35,89 @@ export class CanvasBbox {
|
|||||||
const bbox = this.manager.stateApi.getBbox();
|
const bbox = this.manager.stateApi.getBbox();
|
||||||
const $aspectRatioBuffer = atom(bbox.rect.width / bbox.rect.height);
|
const $aspectRatioBuffer = atom(bbox.rect.width / bbox.rect.height);
|
||||||
|
|
||||||
// Use a transformer for the generation bbox. Transformers need some shape to transform, we will use a fully
|
this.konva = {
|
||||||
// transparent rect for this purpose.
|
group: new Konva.Group({ name: CanvasBbox.GROUP_NAME, listening: false }),
|
||||||
this.group = new Konva.Group({ name: CanvasBbox.GROUP_NAME, listening: false });
|
// Use a transformer for the generation bbox. Transformers need some shape to transform, we will use a fully
|
||||||
this.rect = new Konva.Rect({
|
// transparent rect for this purpose.
|
||||||
name: CanvasBbox.RECT_NAME,
|
rect: new Konva.Rect({
|
||||||
listening: false,
|
name: CanvasBbox.RECT_NAME,
|
||||||
strokeEnabled: false,
|
listening: false,
|
||||||
draggable: true,
|
strokeEnabled: false,
|
||||||
...this.manager.stateApi.getBbox(),
|
draggable: true,
|
||||||
});
|
...this.manager.stateApi.getBbox(),
|
||||||
this.rect.on('dragmove', () => {
|
}),
|
||||||
|
transformer: new Konva.Transformer({
|
||||||
|
name: CanvasBbox.TRANSFORMER_NAME,
|
||||||
|
borderDash: [5, 5],
|
||||||
|
borderStroke: 'rgba(212,216,234,1)',
|
||||||
|
borderEnabled: true,
|
||||||
|
rotateEnabled: false,
|
||||||
|
keepRatio: false,
|
||||||
|
ignoreStroke: true,
|
||||||
|
listening: false,
|
||||||
|
flipEnabled: false,
|
||||||
|
anchorFill: 'rgba(212,216,234,1)',
|
||||||
|
anchorStroke: 'rgb(42,42,42)',
|
||||||
|
anchorSize: 12,
|
||||||
|
anchorCornerRadius: 3,
|
||||||
|
shiftBehavior: 'none', // we will implement our own shift behavior
|
||||||
|
centeredScaling: false,
|
||||||
|
anchorStyleFunc: (anchor) => {
|
||||||
|
// Make the x/y resize anchors little bars
|
||||||
|
if (anchor.hasName('top-center') || anchor.hasName('bottom-center')) {
|
||||||
|
anchor.height(8);
|
||||||
|
anchor.offsetY(4);
|
||||||
|
anchor.width(30);
|
||||||
|
anchor.offsetX(15);
|
||||||
|
}
|
||||||
|
if (anchor.hasName('middle-left') || anchor.hasName('middle-right')) {
|
||||||
|
anchor.height(30);
|
||||||
|
anchor.offsetY(15);
|
||||||
|
anchor.width(8);
|
||||||
|
anchor.offsetX(4);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
anchorDragBoundFunc: (_oldAbsPos, newAbsPos) => {
|
||||||
|
// This function works with absolute position - that is, a position in "physical" pixels on the screen, as opposed
|
||||||
|
// to konva's internal coordinate system.
|
||||||
|
const stage = this.konva.transformer.getStage();
|
||||||
|
assert(stage, 'Stage must exist');
|
||||||
|
|
||||||
|
// We need to snap the anchors to the grid. If the user is holding ctrl/meta, we use the finer 8px grid.
|
||||||
|
const gridSize = this.manager.stateApi.getCtrlKey() || this.manager.stateApi.getMetaKey() ? 8 : 64;
|
||||||
|
// Because we are working in absolute coordinates, we need to scale the grid size by the stage scale.
|
||||||
|
const scaledGridSize = gridSize * stage.scaleX();
|
||||||
|
// To snap the anchor to the grid, we need to calculate an offset from the stage's absolute position.
|
||||||
|
const stageAbsPos = stage.getAbsolutePosition();
|
||||||
|
// The offset is the remainder of the stage's absolute position divided by the scaled grid size.
|
||||||
|
const offsetX = stageAbsPos.x % scaledGridSize;
|
||||||
|
const offsetY = stageAbsPos.y % scaledGridSize;
|
||||||
|
// Finally, calculate the position by rounding to the grid and adding the offset.
|
||||||
|
return {
|
||||||
|
x: roundToMultiple(newAbsPos.x, scaledGridSize) + offsetX,
|
||||||
|
y: roundToMultiple(newAbsPos.y, scaledGridSize) + offsetY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
this.konva.rect.on('dragmove', () => {
|
||||||
const gridSize = this.manager.stateApi.getCtrlKey() || this.manager.stateApi.getMetaKey() ? 8 : 64;
|
const gridSize = this.manager.stateApi.getCtrlKey() || this.manager.stateApi.getMetaKey() ? 8 : 64;
|
||||||
const bbox = this.manager.stateApi.getBbox();
|
const bbox = this.manager.stateApi.getBbox();
|
||||||
const bboxRect: Rect = {
|
const bboxRect: Rect = {
|
||||||
...bbox.rect,
|
...bbox.rect,
|
||||||
x: roundToMultiple(this.rect.x(), gridSize),
|
x: roundToMultiple(this.konva.rect.x(), gridSize),
|
||||||
y: roundToMultiple(this.rect.y(), gridSize),
|
y: roundToMultiple(this.konva.rect.y(), gridSize),
|
||||||
};
|
};
|
||||||
this.rect.setAttrs(bboxRect);
|
this.konva.rect.setAttrs(bboxRect);
|
||||||
if (bbox.rect.x !== bboxRect.x || bbox.rect.y !== bboxRect.y) {
|
if (bbox.rect.x !== bboxRect.x || bbox.rect.y !== bboxRect.y) {
|
||||||
this.manager.stateApi.onBboxTransformed(bboxRect);
|
this.manager.stateApi.onBboxTransformed(bboxRect);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.transformer = new Konva.Transformer({
|
this.konva.transformer.on('transform', () => {
|
||||||
name: CanvasBbox.TRANSFORMER_NAME,
|
|
||||||
borderDash: [5, 5],
|
|
||||||
borderStroke: 'rgba(212,216,234,1)',
|
|
||||||
borderEnabled: true,
|
|
||||||
rotateEnabled: false,
|
|
||||||
keepRatio: false,
|
|
||||||
ignoreStroke: true,
|
|
||||||
listening: false,
|
|
||||||
flipEnabled: false,
|
|
||||||
anchorFill: 'rgba(212,216,234,1)',
|
|
||||||
anchorStroke: 'rgb(42,42,42)',
|
|
||||||
anchorSize: 12,
|
|
||||||
anchorCornerRadius: 3,
|
|
||||||
shiftBehavior: 'none', // we will implement our own shift behavior
|
|
||||||
centeredScaling: false,
|
|
||||||
anchorStyleFunc: (anchor) => {
|
|
||||||
// Make the x/y resize anchors little bars
|
|
||||||
if (anchor.hasName('top-center') || anchor.hasName('bottom-center')) {
|
|
||||||
anchor.height(8);
|
|
||||||
anchor.offsetY(4);
|
|
||||||
anchor.width(30);
|
|
||||||
anchor.offsetX(15);
|
|
||||||
}
|
|
||||||
if (anchor.hasName('middle-left') || anchor.hasName('middle-right')) {
|
|
||||||
anchor.height(30);
|
|
||||||
anchor.offsetY(15);
|
|
||||||
anchor.width(8);
|
|
||||||
anchor.offsetX(4);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
anchorDragBoundFunc: (_oldAbsPos, newAbsPos) => {
|
|
||||||
// This function works with absolute position - that is, a position in "physical" pixels on the screen, as opposed
|
|
||||||
// to konva's internal coordinate system.
|
|
||||||
const stage = this.transformer.getStage();
|
|
||||||
assert(stage, 'Stage must exist');
|
|
||||||
|
|
||||||
// We need to snap the anchors to the grid. If the user is holding ctrl/meta, we use the finer 8px grid.
|
|
||||||
const gridSize = this.manager.stateApi.getCtrlKey() || this.manager.stateApi.getMetaKey() ? 8 : 64;
|
|
||||||
// Because we are working in absolute coordinates, we need to scale the grid size by the stage scale.
|
|
||||||
const scaledGridSize = gridSize * stage.scaleX();
|
|
||||||
// To snap the anchor to the grid, we need to calculate an offset from the stage's absolute position.
|
|
||||||
const stageAbsPos = stage.getAbsolutePosition();
|
|
||||||
// The offset is the remainder of the stage's absolute position divided by the scaled grid size.
|
|
||||||
const offsetX = stageAbsPos.x % scaledGridSize;
|
|
||||||
const offsetY = stageAbsPos.y % scaledGridSize;
|
|
||||||
// Finally, calculate the position by rounding to the grid and adding the offset.
|
|
||||||
return {
|
|
||||||
x: roundToMultiple(newAbsPos.x, scaledGridSize) + offsetX,
|
|
||||||
y: roundToMultiple(newAbsPos.y, scaledGridSize) + offsetY,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.transformer.on('transform', () => {
|
|
||||||
// In the transform callback, we calculate the bbox's new dims and pos and update the konva object.
|
// In the transform callback, we calculate the bbox's new dims and pos and update the konva object.
|
||||||
// Some special handling is needed depending on the anchor being dragged.
|
// Some special handling is needed depending on the anchor being dragged.
|
||||||
const anchor = this.transformer.getActiveAnchor();
|
const anchor = this.konva.transformer.getActiveAnchor();
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
// Pretty sure we should always have an anchor here?
|
// Pretty sure we should always have an anchor here?
|
||||||
return;
|
return;
|
||||||
@ -140,14 +140,14 @@ export class CanvasBbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The coords should be correct per the anchorDragBoundFunc.
|
// The coords should be correct per the anchorDragBoundFunc.
|
||||||
let x = this.rect.x();
|
let x = this.konva.rect.x();
|
||||||
let y = this.rect.y();
|
let y = this.konva.rect.y();
|
||||||
|
|
||||||
// Konva transforms by scaling the dims, not directly changing width and height. At this point, the width and height
|
// Konva transforms by scaling the dims, not directly changing width and height. At this point, the width and height
|
||||||
// *have not changed*, only the scale has changed. To get the final height, we need to scale the dims and then snap
|
// *have not changed*, only the scale has changed. To get the final height, we need to scale the dims and then snap
|
||||||
// them to the grid.
|
// them to the grid.
|
||||||
let width = roundToMultipleMin(this.rect.width() * this.rect.scaleX(), gridSize);
|
let width = roundToMultipleMin(this.konva.rect.width() * this.konva.rect.scaleX(), gridSize);
|
||||||
let height = roundToMultipleMin(this.rect.height() * this.rect.scaleY(), gridSize);
|
let height = roundToMultipleMin(this.konva.rect.height() * this.konva.rect.scaleY(), gridSize);
|
||||||
|
|
||||||
// If shift is held and we are resizing from a corner, retain aspect ratio - needs special handling. We skip this
|
// If shift is held and we are resizing from a corner, retain aspect ratio - needs special handling. We skip this
|
||||||
// if alt/opt is held - this requires math too big for my brain.
|
// if alt/opt is held - this requires math too big for my brain.
|
||||||
@ -187,7 +187,7 @@ export class CanvasBbox {
|
|||||||
// Update the bboxRect's attrs directly with the new transform, and reset its scale to 1.
|
// Update the bboxRect's attrs directly with the new transform, and reset its scale to 1.
|
||||||
// TODO(psyche): In `renderBboxPreview()` we also call setAttrs, need to do it twice to ensure it renders correctly.
|
// TODO(psyche): In `renderBboxPreview()` we also call setAttrs, need to do it twice to ensure it renders correctly.
|
||||||
// Gotta be a way to avoid setting it twice...
|
// Gotta be a way to avoid setting it twice...
|
||||||
this.rect.setAttrs({ ...bboxRect, scaleX: 1, scaleY: 1 });
|
this.konva.rect.setAttrs({ ...bboxRect, scaleX: 1, scaleY: 1 });
|
||||||
|
|
||||||
// Update the bbox in internal state.
|
// Update the bbox in internal state.
|
||||||
this.manager.stateApi.onBboxTransformed(bboxRect);
|
this.manager.stateApi.onBboxTransformed(bboxRect);
|
||||||
@ -199,16 +199,16 @@ export class CanvasBbox {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.transformer.on('transformend', () => {
|
this.konva.transformer.on('transformend', () => {
|
||||||
// Always update the aspect ratio buffer when the transform ends, so if the next transform starts with shift held,
|
// Always update the aspect ratio buffer when the transform ends, so if the next transform starts with shift held,
|
||||||
// we have the correct aspect ratio to start from.
|
// we have the correct aspect ratio to start from.
|
||||||
$aspectRatioBuffer.set(this.rect.width() / this.rect.height());
|
$aspectRatioBuffer.set(this.konva.rect.width() / this.konva.rect.height());
|
||||||
});
|
});
|
||||||
|
|
||||||
// The transformer will always be transforming the dummy rect
|
// The transformer will always be transforming the dummy rect
|
||||||
this.transformer.nodes([this.rect]);
|
this.konva.transformer.nodes([this.konva.rect]);
|
||||||
this.group.add(this.rect);
|
this.konva.group.add(this.konva.rect);
|
||||||
this.group.add(this.transformer);
|
this.konva.group.add(this.konva.transformer);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -217,14 +217,14 @@ export class CanvasBbox {
|
|||||||
const toolState = this.manager.stateApi.getToolState();
|
const toolState = this.manager.stateApi.getToolState();
|
||||||
|
|
||||||
if (!session.isActive) {
|
if (!session.isActive) {
|
||||||
this.group.listening(false);
|
this.konva.group.listening(false);
|
||||||
this.group.visible(false);
|
this.konva.group.visible(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.group.visible(true);
|
this.konva.group.visible(true);
|
||||||
this.group.listening(toolState.selected === 'bbox');
|
this.konva.group.listening(toolState.selected === 'bbox');
|
||||||
this.rect.setAttrs({
|
this.konva.rect.setAttrs({
|
||||||
x: bbox.rect.x,
|
x: bbox.rect.x,
|
||||||
y: bbox.rect.y,
|
y: bbox.rect.y,
|
||||||
width: bbox.rect.width,
|
width: bbox.rect.width,
|
||||||
@ -233,7 +233,7 @@ export class CanvasBbox {
|
|||||||
scaleY: 1,
|
scaleY: 1,
|
||||||
listening: toolState.selected === 'bbox',
|
listening: toolState.selected === 'bbox',
|
||||||
});
|
});
|
||||||
this.transformer.setAttrs({
|
this.konva.transformer.setAttrs({
|
||||||
listening: toolState.selected === 'bbox',
|
listening: toolState.selected === 'bbox',
|
||||||
enabledAnchors: toolState.selected === 'bbox' ? this.ALL_ANCHORS : this.NO_ANCHORS,
|
enabledAnchors: toolState.selected === 'bbox' ? this.ALL_ANCHORS : this.NO_ANCHORS,
|
||||||
});
|
});
|
||||||
|
@ -8,40 +8,44 @@ export class CanvasBrushLine {
|
|||||||
static LINE_NAME = `${CanvasBrushLine.NAME_PREFIX}_line`;
|
static LINE_NAME = `${CanvasBrushLine.NAME_PREFIX}_line`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
konvaLineGroup: Konva.Group;
|
konva: {
|
||||||
konvaLine: Konva.Line;
|
group: Konva.Group;
|
||||||
|
line: Konva.Line;
|
||||||
|
};
|
||||||
lastBrushLine: BrushLine;
|
lastBrushLine: BrushLine;
|
||||||
|
|
||||||
constructor(brushLine: BrushLine) {
|
constructor(brushLine: BrushLine) {
|
||||||
const { id, strokeWidth, clip, color, points } = brushLine;
|
const { id, strokeWidth, clip, color, points } = brushLine;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.konvaLineGroup = new Konva.Group({
|
this.konva = {
|
||||||
name: CanvasBrushLine.GROUP_NAME,
|
group: new Konva.Group({
|
||||||
clip,
|
name: CanvasBrushLine.GROUP_NAME,
|
||||||
listening: false,
|
clip,
|
||||||
});
|
listening: false,
|
||||||
this.konvaLine = new Konva.Line({
|
}),
|
||||||
name: CanvasBrushLine.LINE_NAME,
|
line: new Konva.Line({
|
||||||
id,
|
name: CanvasBrushLine.LINE_NAME,
|
||||||
listening: false,
|
id,
|
||||||
shadowForStrokeEnabled: false,
|
listening: false,
|
||||||
strokeWidth,
|
shadowForStrokeEnabled: false,
|
||||||
tension: 0,
|
strokeWidth,
|
||||||
lineCap: 'round',
|
tension: 0,
|
||||||
lineJoin: 'round',
|
lineCap: 'round',
|
||||||
globalCompositeOperation: 'source-over',
|
lineJoin: 'round',
|
||||||
stroke: rgbaColorToString(color),
|
globalCompositeOperation: 'source-over',
|
||||||
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
stroke: rgbaColorToString(color),
|
||||||
points: points.length === 2 ? [...points, ...points] : points,
|
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
||||||
});
|
points: points.length === 2 ? [...points, ...points] : points,
|
||||||
this.konvaLineGroup.add(this.konvaLine);
|
}),
|
||||||
|
};
|
||||||
|
this.konva.group.add(this.konva.line);
|
||||||
this.lastBrushLine = brushLine;
|
this.lastBrushLine = brushLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(brushLine: BrushLine, force?: boolean): boolean {
|
update(brushLine: BrushLine, force?: boolean): boolean {
|
||||||
if (this.lastBrushLine !== brushLine || force) {
|
if (this.lastBrushLine !== brushLine || force) {
|
||||||
const { points, color, clip, strokeWidth } = brushLine;
|
const { points, color, clip, strokeWidth } = brushLine;
|
||||||
this.konvaLine.setAttrs({
|
this.konva.line.setAttrs({
|
||||||
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
||||||
points: points.length === 2 ? [...points, ...points] : points,
|
points: points.length === 2 ? [...points, ...points] : points,
|
||||||
stroke: rgbaColorToString(color),
|
stroke: rgbaColorToString(color),
|
||||||
@ -56,6 +60,6 @@ export class CanvasBrushLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.konvaLineGroup.destroy();
|
this.konva.group.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,51 +14,60 @@ export class CanvasControlAdapter {
|
|||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
layer: Konva.Layer;
|
|
||||||
group: Konva.Group;
|
konva: {
|
||||||
objectsGroup: Konva.Group;
|
layer: Konva.Layer;
|
||||||
|
group: Konva.Group;
|
||||||
|
objectGroup: Konva.Group;
|
||||||
|
transformer: Konva.Transformer;
|
||||||
|
};
|
||||||
|
|
||||||
image: CanvasImage | null;
|
image: CanvasImage | null;
|
||||||
transformer: Konva.Transformer;
|
|
||||||
|
|
||||||
constructor(controlAdapterState: ControlAdapterEntity, manager: CanvasManager) {
|
constructor(controlAdapterState: ControlAdapterEntity, manager: CanvasManager) {
|
||||||
const { id } = controlAdapterState;
|
const { id } = controlAdapterState;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.layer = new Konva.Layer({
|
this.konva = {
|
||||||
name: CanvasControlAdapter.LAYER_NAME,
|
layer: new Konva.Layer({
|
||||||
imageSmoothingEnabled: false,
|
name: CanvasControlAdapter.LAYER_NAME,
|
||||||
listening: false,
|
imageSmoothingEnabled: false,
|
||||||
});
|
listening: false,
|
||||||
this.group = new Konva.Group({
|
}),
|
||||||
name: CanvasControlAdapter.GROUP_NAME,
|
group: new Konva.Group({
|
||||||
listening: false,
|
name: CanvasControlAdapter.GROUP_NAME,
|
||||||
});
|
listening: false,
|
||||||
this.objectsGroup = new Konva.Group({ name: CanvasControlAdapter.GROUP_NAME, listening: false });
|
}),
|
||||||
this.group.add(this.objectsGroup);
|
objectGroup: new Konva.Group({ name: CanvasControlAdapter.GROUP_NAME, listening: false }),
|
||||||
this.layer.add(this.group);
|
transformer: new Konva.Transformer({
|
||||||
|
name: CanvasControlAdapter.TRANSFORMER_NAME,
|
||||||
this.transformer = new Konva.Transformer({
|
shouldOverdrawWholeArea: true,
|
||||||
name: CanvasControlAdapter.TRANSFORMER_NAME,
|
draggable: true,
|
||||||
shouldOverdrawWholeArea: true,
|
dragDistance: 0,
|
||||||
draggable: true,
|
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||||
dragDistance: 0,
|
rotateEnabled: false,
|
||||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
flipEnabled: false,
|
||||||
rotateEnabled: false,
|
}),
|
||||||
flipEnabled: false,
|
};
|
||||||
});
|
this.konva.transformer.on('transformend', () => {
|
||||||
this.transformer.on('transformend', () => {
|
|
||||||
this.manager.stateApi.onScaleChanged(
|
this.manager.stateApi.onScaleChanged(
|
||||||
{ id: this.id, scale: this.group.scaleX(), position: { x: this.group.x(), y: this.group.y() } },
|
{
|
||||||
|
id: this.id,
|
||||||
|
scale: this.konva.group.scaleX(),
|
||||||
|
position: { x: this.konva.group.x(), y: this.konva.group.y() },
|
||||||
|
},
|
||||||
'control_adapter'
|
'control_adapter'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.transformer.on('dragend', () => {
|
this.konva.transformer.on('dragend', () => {
|
||||||
this.manager.stateApi.onPosChanged(
|
this.manager.stateApi.onPosChanged(
|
||||||
{ id: this.id, position: { x: this.group.x(), y: this.group.y() } },
|
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||||
'control_adapter'
|
'control_adapter'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.layer.add(this.transformer);
|
this.konva.group.add(this.konva.objectGroup);
|
||||||
|
this.konva.layer.add(this.konva.group);
|
||||||
|
this.konva.layer.add(this.konva.transformer);
|
||||||
|
|
||||||
this.image = null;
|
this.image = null;
|
||||||
this.controlAdapterState = controlAdapterState;
|
this.controlAdapterState = controlAdapterState;
|
||||||
@ -68,7 +77,7 @@ export class CanvasControlAdapter {
|
|||||||
this.controlAdapterState = controlAdapterState;
|
this.controlAdapterState = controlAdapterState;
|
||||||
|
|
||||||
// Update the layer's position and listening state
|
// Update the layer's position and listening state
|
||||||
this.group.setAttrs({
|
this.konva.group.setAttrs({
|
||||||
x: controlAdapterState.position.x,
|
x: controlAdapterState.position.x,
|
||||||
y: controlAdapterState.position.y,
|
y: controlAdapterState.position.y,
|
||||||
scaleX: 1,
|
scaleX: 1,
|
||||||
@ -81,13 +90,13 @@ export class CanvasControlAdapter {
|
|||||||
|
|
||||||
if (!imageObject) {
|
if (!imageObject) {
|
||||||
if (this.image) {
|
if (this.image) {
|
||||||
this.image.konvaImageGroup.visible(false);
|
this.image.konva.group.visible(false);
|
||||||
didDraw = true;
|
didDraw = true;
|
||||||
}
|
}
|
||||||
} else if (!this.image) {
|
} else if (!this.image) {
|
||||||
this.image = new CanvasImage(imageObject);
|
this.image = new CanvasImage(imageObject);
|
||||||
this.updateGroup(true);
|
this.updateGroup(true);
|
||||||
this.objectsGroup.add(this.image.konvaImageGroup);
|
this.konva.objectGroup.add(this.image.konva.group);
|
||||||
await this.image.updateImageSource(imageObject.image.name);
|
await this.image.updateImageSource(imageObject.image.name);
|
||||||
} else if (!this.image.isLoading && !this.image.isError) {
|
} else if (!this.image.isLoading && !this.image.isError) {
|
||||||
if (await this.image.update(imageObject)) {
|
if (await this.image.update(imageObject)) {
|
||||||
@ -99,18 +108,18 @@ export class CanvasControlAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateGroup(didDraw: boolean) {
|
updateGroup(didDraw: boolean) {
|
||||||
this.layer.visible(this.controlAdapterState.isEnabled);
|
this.konva.layer.visible(this.controlAdapterState.isEnabled);
|
||||||
|
|
||||||
this.group.opacity(this.controlAdapterState.opacity);
|
this.konva.group.opacity(this.controlAdapterState.opacity);
|
||||||
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||||
const selectedTool = this.manager.stateApi.getToolState().selected;
|
const selectedTool = this.manager.stateApi.getToolState().selected;
|
||||||
|
|
||||||
if (!this.image?.konvaImage) {
|
if (!this.image?.image) {
|
||||||
// If the layer is totally empty, reset the cache and bail out.
|
// If the layer is totally empty, reset the cache and bail out.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -118,32 +127,32 @@ export class CanvasControlAdapter {
|
|||||||
if (isSelected && selectedTool === 'move') {
|
if (isSelected && selectedTool === 'move') {
|
||||||
// When the layer is selected and being moved, we should always cache it.
|
// When the layer is selected and being moved, we should always cache it.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
// Activate the transformer
|
// Activate the transformer
|
||||||
this.layer.listening(true);
|
this.konva.layer.listening(true);
|
||||||
this.transformer.nodes([this.group]);
|
this.konva.transformer.nodes([this.konva.group]);
|
||||||
this.transformer.forceUpdate();
|
this.konva.transformer.forceUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelected && selectedTool !== 'move') {
|
if (isSelected && selectedTool !== 'move') {
|
||||||
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (isDrawingTool(selectedTool)) {
|
if (isDrawingTool(selectedTool)) {
|
||||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||||
// should never be cached.
|
// should never be cached.
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -151,12 +160,12 @@ export class CanvasControlAdapter {
|
|||||||
|
|
||||||
if (!isSelected) {
|
if (!isSelected) {
|
||||||
// Unselected layers should not be listening
|
// Unselected layers should not be listening
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
// Update the layer's cache if it's not already cached or we drew to it.
|
// Update the layer's cache if it's not already cached or we drew to it.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -164,6 +173,6 @@ export class CanvasControlAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.layer.destroy();
|
this.konva.layer.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,40 +9,44 @@ export class CanvasEraserLine {
|
|||||||
static LINE_NAME = `${CanvasEraserLine.NAME_PREFIX}_line`;
|
static LINE_NAME = `${CanvasEraserLine.NAME_PREFIX}_line`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
konvaLineGroup: Konva.Group;
|
konva: {
|
||||||
konvaLine: Konva.Line;
|
group: Konva.Group;
|
||||||
|
line: Konva.Line;
|
||||||
|
};
|
||||||
lastEraserLine: EraserLine;
|
lastEraserLine: EraserLine;
|
||||||
|
|
||||||
constructor(eraserLine: EraserLine) {
|
constructor(eraserLine: EraserLine) {
|
||||||
const { id, strokeWidth, clip, points } = eraserLine;
|
const { id, strokeWidth, clip, points } = eraserLine;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.konvaLineGroup = new Konva.Group({
|
this.konva = {
|
||||||
name: CanvasEraserLine.GROUP_NAME,
|
group: new Konva.Group({
|
||||||
clip,
|
name: CanvasEraserLine.GROUP_NAME,
|
||||||
listening: false,
|
clip,
|
||||||
});
|
listening: false,
|
||||||
this.konvaLine = new Konva.Line({
|
}),
|
||||||
name: CanvasEraserLine.LINE_NAME,
|
line: new Konva.Line({
|
||||||
id,
|
name: CanvasEraserLine.LINE_NAME,
|
||||||
listening: false,
|
id,
|
||||||
shadowForStrokeEnabled: false,
|
listening: false,
|
||||||
strokeWidth,
|
shadowForStrokeEnabled: false,
|
||||||
tension: 0,
|
strokeWidth,
|
||||||
lineCap: 'round',
|
tension: 0,
|
||||||
lineJoin: 'round',
|
lineCap: 'round',
|
||||||
globalCompositeOperation: 'destination-out',
|
lineJoin: 'round',
|
||||||
stroke: rgbaColorToString(RGBA_RED),
|
globalCompositeOperation: 'destination-out',
|
||||||
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
stroke: rgbaColorToString(RGBA_RED),
|
||||||
points: points.length === 2 ? [...points, ...points] : points,
|
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
||||||
});
|
points: points.length === 2 ? [...points, ...points] : points,
|
||||||
this.konvaLineGroup.add(this.konvaLine);
|
}),
|
||||||
|
};
|
||||||
|
this.konva.group.add(this.konva.line);
|
||||||
this.lastEraserLine = eraserLine;
|
this.lastEraserLine = eraserLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(eraserLine: EraserLine, force?: boolean): boolean {
|
update(eraserLine: EraserLine, force?: boolean): boolean {
|
||||||
if (this.lastEraserLine !== eraserLine || force) {
|
if (this.lastEraserLine !== eraserLine || force) {
|
||||||
const { points, clip, strokeWidth } = eraserLine;
|
const { points, clip, strokeWidth } = eraserLine;
|
||||||
this.konvaLine.setAttrs({
|
this.konva.line.setAttrs({
|
||||||
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
||||||
points: points.length === 2 ? [...points, ...points] : points,
|
points: points.length === 2 ? [...points, ...points] : points,
|
||||||
clip,
|
clip,
|
||||||
@ -56,6 +60,6 @@ export class CanvasEraserLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.konvaLineGroup.destroy();
|
this.konva.group.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,48 +15,51 @@ export class CanvasImage {
|
|||||||
static PLACEHOLDER_TEXT_NAME = `${CanvasImage.NAME_PREFIX}_placeholder-text`;
|
static PLACEHOLDER_TEXT_NAME = `${CanvasImage.NAME_PREFIX}_placeholder-text`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
konvaImageGroup: Konva.Group;
|
konva: {
|
||||||
konvaPlaceholderGroup: Konva.Group;
|
group: Konva.Group;
|
||||||
konvaPlaceholderRect: Konva.Rect;
|
placeholder: { group: Konva.Group; rect: Konva.Rect; text: Konva.Text };
|
||||||
konvaPlaceholderText: Konva.Text;
|
};
|
||||||
imageName: string | null;
|
imageName: string | null;
|
||||||
konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
|
image: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
lastImageObject: ImageObject;
|
lastImageObject: ImageObject;
|
||||||
|
|
||||||
constructor(imageObject: ImageObject) {
|
constructor(imageObject: ImageObject) {
|
||||||
const { id, width, height, x, y } = imageObject;
|
const { id, width, height, x, y } = imageObject;
|
||||||
this.konvaImageGroup = new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y });
|
this.konva = {
|
||||||
this.konvaPlaceholderGroup = new Konva.Group({ name: CanvasImage.PLACEHOLDER_GROUP_NAME, listening: false });
|
group: new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y }),
|
||||||
this.konvaPlaceholderRect = new Konva.Rect({
|
placeholder: {
|
||||||
name: CanvasImage.PLACEHOLDER_RECT_NAME,
|
group: new Konva.Group({ name: CanvasImage.PLACEHOLDER_GROUP_NAME, listening: false }),
|
||||||
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
|
rect: new Konva.Rect({
|
||||||
width,
|
name: CanvasImage.PLACEHOLDER_RECT_NAME,
|
||||||
height,
|
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
|
||||||
listening: false,
|
width,
|
||||||
});
|
height,
|
||||||
this.konvaPlaceholderText = new Konva.Text({
|
listening: false,
|
||||||
name: CanvasImage.PLACEHOLDER_TEXT_NAME,
|
}),
|
||||||
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
|
text: new Konva.Text({
|
||||||
width,
|
name: CanvasImage.PLACEHOLDER_TEXT_NAME,
|
||||||
height,
|
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
|
||||||
align: 'center',
|
width,
|
||||||
verticalAlign: 'middle',
|
height,
|
||||||
fontFamily: '"Inter Variable", sans-serif',
|
align: 'center',
|
||||||
fontSize: width / 16,
|
verticalAlign: 'middle',
|
||||||
fontStyle: '600',
|
fontFamily: '"Inter Variable", sans-serif',
|
||||||
text: t('common.loadingImage', 'Loading Image'),
|
fontSize: width / 16,
|
||||||
listening: false,
|
fontStyle: '600',
|
||||||
});
|
text: t('common.loadingImage', 'Loading Image'),
|
||||||
|
listening: false,
|
||||||
this.konvaPlaceholderGroup.add(this.konvaPlaceholderRect);
|
}),
|
||||||
this.konvaPlaceholderGroup.add(this.konvaPlaceholderText);
|
},
|
||||||
this.konvaImageGroup.add(this.konvaPlaceholderGroup);
|
};
|
||||||
|
this.konva.placeholder.group.add(this.konva.placeholder.rect);
|
||||||
|
this.konva.placeholder.group.add(this.konva.placeholder.text);
|
||||||
|
this.konva.group.add(this.konva.placeholder.group);
|
||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.imageName = null;
|
this.imageName = null;
|
||||||
this.konvaImage = null;
|
this.image = null;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.isError = false;
|
this.isError = false;
|
||||||
this.lastImageObject = imageObject;
|
this.lastImageObject = imageObject;
|
||||||
@ -65,51 +68,51 @@ export class CanvasImage {
|
|||||||
async updateImageSource(imageName: string) {
|
async updateImageSource(imageName: string) {
|
||||||
try {
|
try {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.konvaImageGroup.visible(true);
|
this.konva.group.visible(true);
|
||||||
|
|
||||||
if (!this.konvaImage) {
|
if (!this.image) {
|
||||||
this.konvaPlaceholderGroup.visible(true);
|
this.konva.placeholder.group.visible(true);
|
||||||
this.konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image'));
|
this.konva.placeholder.text.text(t('common.loadingImage', 'Loading Image'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageDTO = await getImageDTO(imageName);
|
const imageDTO = await getImageDTO(imageName);
|
||||||
assert(imageDTO !== null, 'imageDTO is null');
|
assert(imageDTO !== null, 'imageDTO is null');
|
||||||
const imageEl = await loadImage(imageDTO.image_url);
|
const imageEl = await loadImage(imageDTO.image_url);
|
||||||
|
|
||||||
if (this.konvaImage) {
|
if (this.image) {
|
||||||
this.konvaImage.setAttrs({
|
this.image.setAttrs({
|
||||||
image: imageEl,
|
image: imageEl,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.konvaImage = new Konva.Image({
|
this.image = new Konva.Image({
|
||||||
name: CanvasImage.IMAGE_NAME,
|
name: CanvasImage.IMAGE_NAME,
|
||||||
listening: false,
|
listening: false,
|
||||||
image: imageEl,
|
image: imageEl,
|
||||||
width: this.lastImageObject.width,
|
width: this.lastImageObject.width,
|
||||||
height: this.lastImageObject.height,
|
height: this.lastImageObject.height,
|
||||||
});
|
});
|
||||||
this.konvaImageGroup.add(this.konvaImage);
|
this.konva.group.add(this.image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.lastImageObject.filters.length > 0) {
|
if (this.lastImageObject.filters.length > 0) {
|
||||||
this.konvaImage.cache();
|
this.image.cache();
|
||||||
this.konvaImage.filters(this.lastImageObject.filters.map((f) => FILTER_MAP[f]));
|
this.image.filters(this.lastImageObject.filters.map((f) => FILTER_MAP[f]));
|
||||||
} else {
|
} else {
|
||||||
this.konvaImage.clearCache();
|
this.image.clearCache();
|
||||||
this.konvaImage.filters([]);
|
this.image.filters([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.imageName = imageName;
|
this.imageName = imageName;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.isError = false;
|
this.isError = false;
|
||||||
this.konvaPlaceholderGroup.visible(false);
|
this.konva.placeholder.group.visible(false);
|
||||||
} catch {
|
} catch {
|
||||||
this.konvaImage?.visible(false);
|
this.image?.visible(false);
|
||||||
this.imageName = null;
|
this.imageName = null;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.isError = true;
|
this.isError = true;
|
||||||
this.konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
|
this.konva.placeholder.text.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
|
||||||
this.konvaPlaceholderGroup.visible(true);
|
this.konva.placeholder.group.visible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,16 +122,16 @@ export class CanvasImage {
|
|||||||
if (this.lastImageObject.image.name !== image.name || force) {
|
if (this.lastImageObject.image.name !== image.name || force) {
|
||||||
await this.updateImageSource(image.name);
|
await this.updateImageSource(image.name);
|
||||||
}
|
}
|
||||||
this.konvaImage?.setAttrs({ x, y, width, height });
|
this.image?.setAttrs({ x, y, width, height });
|
||||||
if (filters.length > 0) {
|
if (filters.length > 0) {
|
||||||
this.konvaImage?.cache();
|
this.image?.cache();
|
||||||
this.konvaImage?.filters(filters.map((f) => FILTER_MAP[f]));
|
this.image?.filters(filters.map((f) => FILTER_MAP[f]));
|
||||||
} else {
|
} else {
|
||||||
this.konvaImage?.clearCache();
|
this.image?.clearCache();
|
||||||
this.konvaImage?.filters([]);
|
this.image?.filters([]);
|
||||||
}
|
}
|
||||||
this.konvaPlaceholderRect.setAttrs({ width, height });
|
this.konva.placeholder.rect.setAttrs({ width, height });
|
||||||
this.konvaPlaceholderText.setAttrs({ width, height, fontSize: width / 16 });
|
this.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
|
||||||
this.lastImageObject = imageObject;
|
this.lastImageObject = imageObject;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -137,6 +140,6 @@ export class CanvasImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.konvaImageGroup.destroy();
|
this.konva.group.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,37 @@
|
|||||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
|
|
||||||
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
export class CanvasInitialImage {
|
export class CanvasInitialImage {
|
||||||
|
static NAME_PREFIX = 'initial-image';
|
||||||
|
static LAYER_NAME = `${CanvasInitialImage.NAME_PREFIX}_layer`;
|
||||||
|
static GROUP_NAME = `${CanvasInitialImage.NAME_PREFIX}_group`;
|
||||||
|
static OBJECT_GROUP_NAME = `${CanvasInitialImage.NAME_PREFIX}_object-group`;
|
||||||
|
|
||||||
id = 'initial_image';
|
id = 'initial_image';
|
||||||
manager: CanvasManager;
|
|
||||||
layer: Konva.Layer;
|
|
||||||
group: Konva.Group;
|
|
||||||
objectsGroup: Konva.Group;
|
|
||||||
image: CanvasImage | null;
|
|
||||||
private initialImageState: InitialImageEntity;
|
private initialImageState: InitialImageEntity;
|
||||||
|
|
||||||
|
manager: CanvasManager;
|
||||||
|
|
||||||
|
konva: {
|
||||||
|
layer: Konva.Layer;
|
||||||
|
group: Konva.Group;
|
||||||
|
objectGroup: Konva.Group;
|
||||||
|
};
|
||||||
|
|
||||||
|
image: CanvasImage | null;
|
||||||
|
|
||||||
constructor(initialImageState: InitialImageEntity, manager: CanvasManager) {
|
constructor(initialImageState: InitialImageEntity, manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.layer = new Konva.Layer({
|
this.konva = {
|
||||||
id: this.id,
|
layer: new Konva.Layer({ name: CanvasInitialImage.LAYER_NAME, imageSmoothingEnabled: true, listening: false }),
|
||||||
imageSmoothingEnabled: true,
|
group: new Konva.Group({ name: CanvasInitialImage.GROUP_NAME, listening: false }),
|
||||||
listening: false,
|
objectGroup: new Konva.Group({ name: CanvasInitialImage.OBJECT_GROUP_NAME, listening: false }),
|
||||||
});
|
};
|
||||||
this.group = new Konva.Group({
|
this.konva.group.add(this.konva.objectGroup);
|
||||||
id: getObjectGroupId(this.layer.id(), uuidv4()),
|
this.konva.layer.add(this.konva.group);
|
||||||
listening: false,
|
|
||||||
});
|
|
||||||
this.objectsGroup = new Konva.Group({ listening: false });
|
|
||||||
this.group.add(this.objectsGroup);
|
|
||||||
this.layer.add(this.group);
|
|
||||||
|
|
||||||
this.image = null;
|
this.image = null;
|
||||||
this.initialImageState = initialImageState;
|
this.initialImageState = initialImageState;
|
||||||
@ -37,26 +41,26 @@ export class CanvasInitialImage {
|
|||||||
this.initialImageState = initialImageState;
|
this.initialImageState = initialImageState;
|
||||||
|
|
||||||
if (!this.initialImageState.imageObject) {
|
if (!this.initialImageState.imageObject) {
|
||||||
this.layer.visible(false);
|
this.konva.layer.visible(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.image) {
|
if (!this.image) {
|
||||||
this.image = new CanvasImage(this.initialImageState.imageObject);
|
this.image = new CanvasImage(this.initialImageState.imageObject);
|
||||||
this.objectsGroup.add(this.image.konvaImageGroup);
|
this.konva.objectGroup.add(this.image.konva.group);
|
||||||
await this.image.update(this.initialImageState.imageObject, true);
|
await this.image.update(this.initialImageState.imageObject, true);
|
||||||
} else if (!this.image.isLoading && !this.image.isError) {
|
} else if (!this.image.isLoading && !this.image.isError) {
|
||||||
await this.image.update(this.initialImageState.imageObject);
|
await this.image.update(this.initialImageState.imageObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.initialImageState && this.initialImageState.isEnabled && !this.image?.isLoading && !this.image?.isError) {
|
if (this.initialImageState && this.initialImageState.isEnabled && !this.image?.isLoading && !this.image?.isError) {
|
||||||
this.layer.visible(true);
|
this.konva.layer.visible(true);
|
||||||
} else {
|
} else {
|
||||||
this.layer.visible(false);
|
this.konva.layer.visible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.layer.destroy();
|
this.konva.layer.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,57 +23,64 @@ export class CanvasInpaintMask {
|
|||||||
|
|
||||||
id = 'inpaint_mask';
|
id = 'inpaint_mask';
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
layer: Konva.Layer;
|
|
||||||
group: Konva.Group;
|
konva: {
|
||||||
objectsGroup: Konva.Group;
|
layer: Konva.Layer;
|
||||||
compositingRect: Konva.Rect;
|
group: Konva.Group;
|
||||||
transformer: Konva.Transformer;
|
objectGroup: Konva.Group;
|
||||||
|
transformer: Konva.Transformer;
|
||||||
|
compositingRect: Konva.Rect;
|
||||||
|
};
|
||||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
||||||
|
|
||||||
constructor(entity: InpaintMaskEntity, manager: CanvasManager) {
|
constructor(entity: InpaintMaskEntity, manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.layer = new Konva.Layer({ name: CanvasInpaintMask.LAYER_NAME });
|
|
||||||
|
|
||||||
this.group = new Konva.Group({
|
this.konva = {
|
||||||
name: CanvasInpaintMask.GROUP_NAME,
|
layer: new Konva.Layer({ name: CanvasInpaintMask.LAYER_NAME }),
|
||||||
listening: false,
|
group: new Konva.Group({ name: CanvasInpaintMask.GROUP_NAME, listening: false }),
|
||||||
});
|
objectGroup: new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false }),
|
||||||
this.objectsGroup = new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false });
|
transformer: new Konva.Transformer({
|
||||||
this.group.add(this.objectsGroup);
|
name: CanvasInpaintMask.TRANSFORMER_NAME,
|
||||||
this.layer.add(this.group);
|
shouldOverdrawWholeArea: true,
|
||||||
|
draggable: true,
|
||||||
|
dragDistance: 0,
|
||||||
|
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||||
|
rotateEnabled: false,
|
||||||
|
flipEnabled: false,
|
||||||
|
}),
|
||||||
|
compositingRect: new Konva.Rect({ name: CanvasInpaintMask.COMPOSITING_RECT_NAME, listening: false }),
|
||||||
|
};
|
||||||
|
|
||||||
this.transformer = new Konva.Transformer({
|
this.konva.group.add(this.konva.objectGroup);
|
||||||
name: CanvasInpaintMask.TRANSFORMER_NAME,
|
this.konva.layer.add(this.konva.group);
|
||||||
shouldOverdrawWholeArea: true,
|
|
||||||
draggable: true,
|
this.konva.transformer.on('transformend', () => {
|
||||||
dragDistance: 0,
|
|
||||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
|
||||||
rotateEnabled: false,
|
|
||||||
flipEnabled: false,
|
|
||||||
});
|
|
||||||
this.transformer.on('transformend', () => {
|
|
||||||
this.manager.stateApi.onScaleChanged(
|
this.manager.stateApi.onScaleChanged(
|
||||||
{ id: this.id, scale: this.group.scaleX(), position: { x: this.group.x(), y: this.group.y() } },
|
{
|
||||||
|
id: this.id,
|
||||||
|
scale: this.konva.group.scaleX(),
|
||||||
|
position: { x: this.konva.group.x(), y: this.konva.group.y() },
|
||||||
|
},
|
||||||
'inpaint_mask'
|
'inpaint_mask'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.transformer.on('dragend', () => {
|
this.konva.transformer.on('dragend', () => {
|
||||||
this.manager.stateApi.onPosChanged(
|
this.manager.stateApi.onPosChanged(
|
||||||
{ id: this.id, position: { x: this.group.x(), y: this.group.y() } },
|
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||||
'inpaint_mask'
|
'inpaint_mask'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.layer.add(this.transformer);
|
this.konva.layer.add(this.konva.transformer);
|
||||||
|
|
||||||
this.compositingRect = new Konva.Rect({ name: CanvasInpaintMask.COMPOSITING_RECT_NAME, listening: false });
|
this.konva.group.add(this.konva.compositingRect);
|
||||||
this.group.add(this.compositingRect);
|
|
||||||
this.objects = new Map();
|
this.objects = new Map();
|
||||||
this.drawingBuffer = null;
|
this.drawingBuffer = null;
|
||||||
this.inpaintMaskState = entity;
|
this.inpaintMaskState = entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.layer.destroy();
|
this.konva.layer.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDrawingBuffer() {
|
getDrawingBuffer() {
|
||||||
@ -112,7 +119,7 @@ export class CanvasInpaintMask {
|
|||||||
this.inpaintMaskState = inpaintMaskState;
|
this.inpaintMaskState = inpaintMaskState;
|
||||||
|
|
||||||
// Update the layer's position and listening state
|
// Update the layer's position and listening state
|
||||||
this.group.setAttrs({
|
this.konva.group.setAttrs({
|
||||||
x: inpaintMaskState.position.x,
|
x: inpaintMaskState.position.x,
|
||||||
y: inpaintMaskState.position.y,
|
y: inpaintMaskState.position.y,
|
||||||
scaleX: 1,
|
scaleX: 1,
|
||||||
@ -154,7 +161,7 @@ export class CanvasInpaintMask {
|
|||||||
if (!brushLine) {
|
if (!brushLine) {
|
||||||
brushLine = new CanvasBrushLine(obj);
|
brushLine = new CanvasBrushLine(obj);
|
||||||
this.objects.set(brushLine.id, brushLine);
|
this.objects.set(brushLine.id, brushLine);
|
||||||
this.objectsGroup.add(brushLine.konvaLineGroup);
|
this.konva.objectGroup.add(brushLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (brushLine.update(obj, force)) {
|
if (brushLine.update(obj, force)) {
|
||||||
@ -168,7 +175,7 @@ export class CanvasInpaintMask {
|
|||||||
if (!eraserLine) {
|
if (!eraserLine) {
|
||||||
eraserLine = new CanvasEraserLine(obj);
|
eraserLine = new CanvasEraserLine(obj);
|
||||||
this.objects.set(eraserLine.id, eraserLine);
|
this.objects.set(eraserLine.id, eraserLine);
|
||||||
this.objectsGroup.add(eraserLine.konvaLineGroup);
|
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (eraserLine.update(obj, force)) {
|
if (eraserLine.update(obj, force)) {
|
||||||
@ -182,7 +189,7 @@ export class CanvasInpaintMask {
|
|||||||
if (!rect) {
|
if (!rect) {
|
||||||
rect = new CanvasRect(obj);
|
rect = new CanvasRect(obj);
|
||||||
this.objects.set(rect.id, rect);
|
this.objects.set(rect.id, rect);
|
||||||
this.objectsGroup.add(rect.konvaRect);
|
this.konva.objectGroup.add(rect.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (rect.update(obj, force)) {
|
if (rect.update(obj, force)) {
|
||||||
@ -195,19 +202,19 @@ export class CanvasInpaintMask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateGroup(didDraw: boolean) {
|
updateGroup(didDraw: boolean) {
|
||||||
this.layer.visible(this.inpaintMaskState.isEnabled);
|
this.konva.layer.visible(this.inpaintMaskState.isEnabled);
|
||||||
|
|
||||||
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
||||||
this.group.opacity(1);
|
this.konva.group.opacity(1);
|
||||||
|
|
||||||
if (didDraw) {
|
if (didDraw) {
|
||||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||||
const rgbColor = rgbColorToString(this.inpaintMaskState.fill);
|
const rgbColor = rgbColorToString(this.inpaintMaskState.fill);
|
||||||
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||||
|
|
||||||
this.compositingRect.setAttrs({
|
this.konva.compositingRect.setAttrs({
|
||||||
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
||||||
...getNodeBboxFast(this.objectsGroup),
|
...getNodeBboxFast(this.konva.objectGroup),
|
||||||
fill: rgbColor,
|
fill: rgbColor,
|
||||||
opacity: maskOpacity,
|
opacity: maskOpacity,
|
||||||
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
||||||
@ -223,10 +230,10 @@ export class CanvasInpaintMask {
|
|||||||
|
|
||||||
if (this.objects.size === 0) {
|
if (this.objects.size === 0) {
|
||||||
// If the layer is totally empty, reset the cache and bail out.
|
// If the layer is totally empty, reset the cache and bail out.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -234,32 +241,32 @@ export class CanvasInpaintMask {
|
|||||||
if (isSelected && selectedTool === 'move') {
|
if (isSelected && selectedTool === 'move') {
|
||||||
// When the layer is selected and being moved, we should always cache it.
|
// When the layer is selected and being moved, we should always cache it.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
// Activate the transformer
|
// Activate the transformer
|
||||||
this.layer.listening(true);
|
this.konva.layer.listening(true);
|
||||||
this.transformer.nodes([this.group]);
|
this.konva.transformer.nodes([this.konva.group]);
|
||||||
this.transformer.forceUpdate();
|
this.konva.transformer.forceUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelected && selectedTool !== 'move') {
|
if (isSelected && selectedTool !== 'move') {
|
||||||
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (isDrawingTool(selectedTool)) {
|
if (isDrawingTool(selectedTool)) {
|
||||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||||
// should never be cached.
|
// should never be cached.
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -267,12 +274,12 @@ export class CanvasInpaintMask {
|
|||||||
|
|
||||||
if (!isSelected) {
|
if (!isSelected) {
|
||||||
// Unselected layers should not be listening
|
// Unselected layers should not be listening
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
// Update the layer's cache if it's not already cached or we drew to it.
|
// Update the layer's cache if it's not already cached or we drew to it.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -21,40 +21,53 @@ export class CanvasLayer {
|
|||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
layer: Konva.Layer;
|
|
||||||
group: Konva.Group;
|
konva: {
|
||||||
objectsGroup: Konva.Group;
|
layer: Konva.Layer;
|
||||||
transformer: Konva.Transformer;
|
group: Konva.Group;
|
||||||
|
objectGroup: Konva.Group;
|
||||||
|
transformer: Konva.Transformer;
|
||||||
|
};
|
||||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
||||||
|
|
||||||
constructor(entity: LayerEntity, manager: CanvasManager) {
|
constructor(entity: LayerEntity, manager: CanvasManager) {
|
||||||
this.id = entity.id;
|
this.id = entity.id;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.layer = new Konva.Layer({ name: CanvasLayer.LAYER_NAME, listening: false });
|
this.konva = {
|
||||||
this.group = new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: false });
|
layer: new Konva.Layer({ name: CanvasLayer.LAYER_NAME, listening: false }),
|
||||||
this.objectsGroup = new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false });
|
group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: false }),
|
||||||
this.group.add(this.objectsGroup);
|
objectGroup: new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false }),
|
||||||
this.layer.add(this.group);
|
transformer: new Konva.Transformer({
|
||||||
|
name: CanvasLayer.TRANSFORMER_NAME,
|
||||||
|
shouldOverdrawWholeArea: true,
|
||||||
|
draggable: true,
|
||||||
|
dragDistance: 0,
|
||||||
|
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||||
|
rotateEnabled: false,
|
||||||
|
flipEnabled: false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
this.transformer = new Konva.Transformer({
|
this.konva.group.add(this.konva.objectGroup);
|
||||||
name: CanvasLayer.TRANSFORMER_NAME,
|
this.konva.layer.add(this.konva.group);
|
||||||
shouldOverdrawWholeArea: true,
|
|
||||||
draggable: true,
|
this.konva.transformer.on('transformend', () => {
|
||||||
dragDistance: 0,
|
|
||||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
|
||||||
rotateEnabled: false,
|
|
||||||
flipEnabled: false,
|
|
||||||
});
|
|
||||||
this.transformer.on('transformend', () => {
|
|
||||||
this.manager.stateApi.onScaleChanged(
|
this.manager.stateApi.onScaleChanged(
|
||||||
{ id: this.id, scale: this.group.scaleX(), position: { x: this.group.x(), y: this.group.y() } },
|
{
|
||||||
|
id: this.id,
|
||||||
|
scale: this.konva.group.scaleX(),
|
||||||
|
position: { x: this.konva.group.x(), y: this.konva.group.y() },
|
||||||
|
},
|
||||||
'layer'
|
'layer'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.transformer.on('dragend', () => {
|
this.konva.transformer.on('dragend', () => {
|
||||||
this.manager.stateApi.onPosChanged({ id: this.id, position: { x: this.group.x(), y: this.group.y() } }, 'layer');
|
this.manager.stateApi.onPosChanged(
|
||||||
|
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||||
|
'layer'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
this.layer.add(this.transformer);
|
this.konva.layer.add(this.konva.transformer);
|
||||||
|
|
||||||
this.objects = new Map();
|
this.objects = new Map();
|
||||||
this.drawingBuffer = null;
|
this.drawingBuffer = null;
|
||||||
@ -62,7 +75,7 @@ export class CanvasLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.layer.destroy();
|
this.konva.layer.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDrawingBuffer() {
|
getDrawingBuffer() {
|
||||||
@ -97,7 +110,7 @@ export class CanvasLayer {
|
|||||||
this.layerState = layerState;
|
this.layerState = layerState;
|
||||||
|
|
||||||
// Update the layer's position and listening state
|
// Update the layer's position and listening state
|
||||||
this.group.setAttrs({
|
this.konva.group.setAttrs({
|
||||||
x: layerState.position.x,
|
x: layerState.position.x,
|
||||||
y: layerState.position.y,
|
y: layerState.position.y,
|
||||||
scaleX: 1,
|
scaleX: 1,
|
||||||
@ -139,7 +152,7 @@ export class CanvasLayer {
|
|||||||
if (!brushLine) {
|
if (!brushLine) {
|
||||||
brushLine = new CanvasBrushLine(obj);
|
brushLine = new CanvasBrushLine(obj);
|
||||||
this.objects.set(brushLine.id, brushLine);
|
this.objects.set(brushLine.id, brushLine);
|
||||||
this.objectsGroup.add(brushLine.konvaLineGroup);
|
this.konva.objectGroup.add(brushLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (brushLine.update(obj, force)) {
|
if (brushLine.update(obj, force)) {
|
||||||
@ -153,7 +166,7 @@ export class CanvasLayer {
|
|||||||
if (!eraserLine) {
|
if (!eraserLine) {
|
||||||
eraserLine = new CanvasEraserLine(obj);
|
eraserLine = new CanvasEraserLine(obj);
|
||||||
this.objects.set(eraserLine.id, eraserLine);
|
this.objects.set(eraserLine.id, eraserLine);
|
||||||
this.objectsGroup.add(eraserLine.konvaLineGroup);
|
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (eraserLine.update(obj, force)) {
|
if (eraserLine.update(obj, force)) {
|
||||||
@ -167,7 +180,7 @@ export class CanvasLayer {
|
|||||||
if (!rect) {
|
if (!rect) {
|
||||||
rect = new CanvasRect(obj);
|
rect = new CanvasRect(obj);
|
||||||
this.objects.set(rect.id, rect);
|
this.objects.set(rect.id, rect);
|
||||||
this.objectsGroup.add(rect.konvaRect);
|
this.konva.objectGroup.add(rect.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (rect.update(obj, force)) {
|
if (rect.update(obj, force)) {
|
||||||
@ -181,7 +194,7 @@ export class CanvasLayer {
|
|||||||
if (!image) {
|
if (!image) {
|
||||||
image = new CanvasImage(obj);
|
image = new CanvasImage(obj);
|
||||||
this.objects.set(image.id, image);
|
this.objects.set(image.id, image);
|
||||||
this.objectsGroup.add(image.konvaImageGroup);
|
this.konva.objectGroup.add(image.konva.group);
|
||||||
await image.updateImageSource(obj.image.name);
|
await image.updateImageSource(obj.image.name);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -196,58 +209,58 @@ export class CanvasLayer {
|
|||||||
|
|
||||||
updateGroup(didDraw: boolean) {
|
updateGroup(didDraw: boolean) {
|
||||||
if (!this.layerState.isEnabled) {
|
if (!this.layerState.isEnabled) {
|
||||||
this.layer.visible(false);
|
this.konva.layer.visible(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.layer.visible(true);
|
this.konva.layer.visible(true);
|
||||||
this.group.opacity(this.layerState.opacity);
|
this.konva.group.opacity(this.layerState.opacity);
|
||||||
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||||
const selectedTool = this.manager.stateApi.getToolState().selected;
|
const selectedTool = this.manager.stateApi.getToolState().selected;
|
||||||
|
|
||||||
if (this.objects.size === 0) {
|
if (this.objects.size === 0) {
|
||||||
// If the layer is totally empty, reset the cache and bail out.
|
// If the layer is totally empty, reset the cache and bail out.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
} else if (isSelected && selectedTool === 'move') {
|
} else if (isSelected && selectedTool === 'move') {
|
||||||
// When the layer is selected and being moved, we should always cache it.
|
// When the layer is selected and being moved, we should always cache it.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
// Activate the transformer
|
// Activate the transformer
|
||||||
this.layer.listening(true);
|
this.konva.layer.listening(true);
|
||||||
this.transformer.nodes([this.group]);
|
this.konva.transformer.nodes([this.konva.group]);
|
||||||
this.transformer.forceUpdate();
|
this.konva.transformer.forceUpdate();
|
||||||
} else if (isSelected && selectedTool !== 'move') {
|
} else if (isSelected && selectedTool !== 'move') {
|
||||||
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (isDrawingTool(selectedTool)) {
|
if (isDrawingTool(selectedTool)) {
|
||||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||||
// should never be cached.
|
// should never be cached.
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!isSelected) {
|
} else if (!isSelected) {
|
||||||
// Unselected layers should not be listening
|
// Unselected layers should not be listening
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
// Update the layer's cache if it's not already cached or we drew to it.
|
// Update the layer's cache if it's not already cached or we drew to it.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,17 +97,17 @@ export class CanvasManager {
|
|||||||
this.stage.add(this.preview.layer);
|
this.stage.add(this.preview.layer);
|
||||||
|
|
||||||
this.background = new CanvasBackground(this);
|
this.background = new CanvasBackground(this);
|
||||||
this.stage.add(this.background.layer);
|
this.stage.add(this.background.konva.layer);
|
||||||
|
|
||||||
this.inpaintMask = new CanvasInpaintMask(this.stateApi.getInpaintMaskState(), this);
|
this.inpaintMask = new CanvasInpaintMask(this.stateApi.getInpaintMaskState(), this);
|
||||||
this.stage.add(this.inpaintMask.layer);
|
this.stage.add(this.inpaintMask.konva.layer);
|
||||||
|
|
||||||
this.layers = new Map();
|
this.layers = new Map();
|
||||||
this.regions = new Map();
|
this.regions = new Map();
|
||||||
this.controlAdapters = new Map();
|
this.controlAdapters = new Map();
|
||||||
|
|
||||||
this.initialImage = new CanvasInitialImage(this.stateApi.getInitialImageState(), this);
|
this.initialImage = new CanvasInitialImage(this.stateApi.getInitialImageState(), this);
|
||||||
this.stage.add(this.initialImage.layer);
|
this.stage.add(this.initialImage.konva.layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderInitialImage() {
|
async renderInitialImage() {
|
||||||
@ -129,7 +129,7 @@ export class CanvasManager {
|
|||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
adapter = new CanvasLayer(entity, this);
|
adapter = new CanvasLayer(entity, this);
|
||||||
this.layers.set(adapter.id, adapter);
|
this.layers.set(adapter.id, adapter);
|
||||||
this.stage.add(adapter.layer);
|
this.stage.add(adapter.konva.layer);
|
||||||
}
|
}
|
||||||
await adapter.render(entity);
|
await adapter.render(entity);
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ export class CanvasManager {
|
|||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
adapter = new CanvasRegion(entity, this);
|
adapter = new CanvasRegion(entity, this);
|
||||||
this.regions.set(adapter.id, adapter);
|
this.regions.set(adapter.id, adapter);
|
||||||
this.stage.add(adapter.layer);
|
this.stage.add(adapter.konva.layer);
|
||||||
}
|
}
|
||||||
await adapter.render(entity);
|
await adapter.render(entity);
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ export class CanvasManager {
|
|||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
adapter = new CanvasControlAdapter(entity, this);
|
adapter = new CanvasControlAdapter(entity, this);
|
||||||
this.controlAdapters.set(adapter.id, adapter);
|
this.controlAdapters.set(adapter.id, adapter);
|
||||||
this.stage.add(adapter.layer);
|
this.stage.add(adapter.konva.layer);
|
||||||
}
|
}
|
||||||
await adapter.render(entity);
|
await adapter.render(entity);
|
||||||
}
|
}
|
||||||
@ -193,18 +193,18 @@ export class CanvasManager {
|
|||||||
const controlAdapters = getControlAdaptersState().entities;
|
const controlAdapters = getControlAdaptersState().entities;
|
||||||
const regions = getRegionsState().entities;
|
const regions = getRegionsState().entities;
|
||||||
let zIndex = 0;
|
let zIndex = 0;
|
||||||
this.background.layer.zIndex(++zIndex);
|
this.background.konva.layer.zIndex(++zIndex);
|
||||||
this.initialImage.layer.zIndex(++zIndex);
|
this.initialImage.konva.layer.zIndex(++zIndex);
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
this.layers.get(layer.id)?.layer.zIndex(++zIndex);
|
this.layers.get(layer.id)?.konva.layer.zIndex(++zIndex);
|
||||||
}
|
}
|
||||||
for (const ca of controlAdapters) {
|
for (const ca of controlAdapters) {
|
||||||
this.controlAdapters.get(ca.id)?.layer.zIndex(++zIndex);
|
this.controlAdapters.get(ca.id)?.konva.layer.zIndex(++zIndex);
|
||||||
}
|
}
|
||||||
for (const rg of regions) {
|
for (const rg of regions) {
|
||||||
this.regions.get(rg.id)?.layer.zIndex(++zIndex);
|
this.regions.get(rg.id)?.konva.layer.zIndex(++zIndex);
|
||||||
}
|
}
|
||||||
this.inpaintMask.layer.zIndex(++zIndex);
|
this.inpaintMask.konva.layer.zIndex(++zIndex);
|
||||||
this.preview.layer.zIndex(++zIndex);
|
this.preview.layer.zIndex(++zIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,15 +21,15 @@ export class CanvasPreview {
|
|||||||
this.layer = new Konva.Layer({ listening: true, imageSmoothingEnabled: false });
|
this.layer = new Konva.Layer({ listening: true, imageSmoothingEnabled: false });
|
||||||
|
|
||||||
this.stagingArea = stagingArea;
|
this.stagingArea = stagingArea;
|
||||||
this.layer.add(this.stagingArea.group);
|
this.layer.add(this.stagingArea.konva.group);
|
||||||
|
|
||||||
this.bbox = bbox;
|
this.bbox = bbox;
|
||||||
this.layer.add(this.bbox.group);
|
this.layer.add(this.bbox.konva.group);
|
||||||
|
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
this.layer.add(this.tool.group);
|
this.layer.add(this.tool.konva.group);
|
||||||
|
|
||||||
this.progressPreview = progressPreview;
|
this.progressPreview = progressPreview;
|
||||||
this.layer.add(this.progressPreview.group);
|
this.layer.add(this.progressPreview.konva.group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,17 +8,21 @@ export class CanvasProgressImage {
|
|||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
progressImageId: string | null;
|
progressImageId: string | null;
|
||||||
konvaImageGroup: Konva.Group;
|
konva: {
|
||||||
konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
|
group: Konva.Group;
|
||||||
|
image: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
|
||||||
|
};
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
|
|
||||||
constructor(arg: { id: string }) {
|
constructor(arg: { id: string }) {
|
||||||
const { id } = arg;
|
const { id } = arg;
|
||||||
this.konvaImageGroup = new Konva.Group({ name: CanvasProgressImage.GROUP_NAME, listening: false });
|
this.konva = {
|
||||||
|
group: new Konva.Group({ name: CanvasProgressImage.GROUP_NAME, listening: false }),
|
||||||
|
image: null,
|
||||||
|
};
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.progressImageId = null;
|
this.progressImageId = null;
|
||||||
this.konvaImage = null;
|
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.isError = false;
|
this.isError = false;
|
||||||
}
|
}
|
||||||
@ -37,8 +41,8 @@ export class CanvasProgressImage {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
try {
|
try {
|
||||||
const imageEl = await loadImage(dataURL);
|
const imageEl = await loadImage(dataURL);
|
||||||
if (this.konvaImage) {
|
if (this.konva.image) {
|
||||||
this.konvaImage.setAttrs({
|
this.konva.image.setAttrs({
|
||||||
image: imageEl,
|
image: imageEl,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
@ -46,7 +50,7 @@ export class CanvasProgressImage {
|
|||||||
height,
|
height,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.konvaImage = new Konva.Image({
|
this.konva.image = new Konva.Image({
|
||||||
name: CanvasProgressImage.IMAGE_NAME,
|
name: CanvasProgressImage.IMAGE_NAME,
|
||||||
listening: false,
|
listening: false,
|
||||||
image: imageEl,
|
image: imageEl,
|
||||||
@ -55,7 +59,7 @@ export class CanvasProgressImage {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
});
|
});
|
||||||
this.konvaImageGroup.add(this.konvaImage);
|
this.konva.group.add(this.konva.image);
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.id = progressImageId;
|
this.id = progressImageId;
|
||||||
@ -65,6 +69,6 @@ export class CanvasProgressImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.konvaImageGroup.destroy();
|
this.konva.group.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,19 @@ export class CanvasProgressPreview {
|
|||||||
static NAME_PREFIX = 'progress-preview';
|
static NAME_PREFIX = 'progress-preview';
|
||||||
static GROUP_NAME = `${CanvasProgressPreview.NAME_PREFIX}_group`;
|
static GROUP_NAME = `${CanvasProgressPreview.NAME_PREFIX}_group`;
|
||||||
|
|
||||||
group: Konva.Group;
|
konva: {
|
||||||
progressImage: CanvasProgressImage;
|
group: Konva.Group;
|
||||||
|
progressImage: CanvasProgressImage;
|
||||||
|
};
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
|
|
||||||
constructor(manager: CanvasManager) {
|
constructor(manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.group = new Konva.Group({ name: CanvasProgressPreview.GROUP_NAME, listening: false });
|
this.konva = {
|
||||||
this.progressImage = new CanvasProgressImage({ id: 'progress-image' });
|
group: new Konva.Group({ name: CanvasProgressPreview.GROUP_NAME, listening: false }),
|
||||||
this.group.add(this.progressImage.konvaImageGroup);
|
progressImage: new CanvasProgressImage({ id: 'progress-image' }),
|
||||||
|
};
|
||||||
|
this.konva.group.add(this.konva.progressImage.konva.group);
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(lastProgressEvent: InvocationDenoiseProgressEvent | null) {
|
async render(lastProgressEvent: InvocationDenoiseProgressEvent | null) {
|
||||||
@ -28,15 +32,15 @@ export class CanvasProgressPreview {
|
|||||||
const { x, y, width, height } = bboxRect;
|
const { x, y, width, height } = bboxRect;
|
||||||
const progressImageId = `${invocation.id}_${step}`;
|
const progressImageId = `${invocation.id}_${step}`;
|
||||||
if (
|
if (
|
||||||
!this.progressImage.isLoading &&
|
!this.konva.progressImage.isLoading &&
|
||||||
!this.progressImage.isError &&
|
!this.konva.progressImage.isError &&
|
||||||
this.progressImage.progressImageId !== progressImageId
|
this.konva.progressImage.progressImageId !== progressImageId
|
||||||
) {
|
) {
|
||||||
await this.progressImage.updateImageSource(progressImageId, dataURL, x, y, width, height);
|
await this.konva.progressImage.updateImageSource(progressImageId, dataURL, x, y, width, height);
|
||||||
this.progressImage.konvaImageGroup.visible(true);
|
this.konva.progressImage.konva.group.visible(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.progressImage.konvaImageGroup.visible(false);
|
this.konva.progressImage.konva.group.visible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,33 +4,40 @@ import Konva from 'konva';
|
|||||||
|
|
||||||
export class CanvasRect {
|
export class CanvasRect {
|
||||||
static NAME_PREFIX = 'canvas-rect';
|
static NAME_PREFIX = 'canvas-rect';
|
||||||
|
static GROUP_NAME = `${CanvasRect.NAME_PREFIX}_group`;
|
||||||
static RECT_NAME = `${CanvasRect.NAME_PREFIX}_rect`;
|
static RECT_NAME = `${CanvasRect.NAME_PREFIX}_rect`;
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
konvaRect: Konva.Rect;
|
konva: {
|
||||||
|
group: Konva.Group;
|
||||||
|
rect: Konva.Rect;
|
||||||
|
};
|
||||||
lastRectShape: RectShape;
|
lastRectShape: RectShape;
|
||||||
|
|
||||||
constructor(rectShape: RectShape) {
|
constructor(rectShape: RectShape) {
|
||||||
const { id, x, y, width, height } = rectShape;
|
const { id, x, y, width, height } = rectShape;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
const konvaRect = new Konva.Rect({
|
this.konva = {
|
||||||
name: CanvasRect.RECT_NAME,
|
group: new Konva.Group({ name: CanvasRect.GROUP_NAME, listening: false }),
|
||||||
id,
|
rect: new Konva.Rect({
|
||||||
x,
|
name: CanvasRect.RECT_NAME,
|
||||||
y,
|
id,
|
||||||
width,
|
x,
|
||||||
height,
|
y,
|
||||||
listening: false,
|
width,
|
||||||
fill: rgbaColorToString(rectShape.color),
|
height,
|
||||||
});
|
listening: false,
|
||||||
this.konvaRect = konvaRect;
|
fill: rgbaColorToString(rectShape.color),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
this.konva.group.add(this.konva.rect);
|
||||||
this.lastRectShape = rectShape;
|
this.lastRectShape = rectShape;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(rectShape: RectShape, force?: boolean): boolean {
|
update(rectShape: RectShape, force?: boolean): boolean {
|
||||||
if (this.lastRectShape !== rectShape || force) {
|
if (this.lastRectShape !== rectShape || force) {
|
||||||
const { x, y, width, height, color } = rectShape;
|
const { x, y, width, height, color } = rectShape;
|
||||||
this.konvaRect.setAttrs({
|
this.konva.rect.setAttrs({
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
@ -45,6 +52,6 @@ export class CanvasRect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.konvaRect.destroy();
|
this.konva.group.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,54 +23,63 @@ export class CanvasRegion {
|
|||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
layer: Konva.Layer;
|
|
||||||
group: Konva.Group;
|
konva: {
|
||||||
objectsGroup: Konva.Group;
|
layer: Konva.Layer;
|
||||||
compositingRect: Konva.Rect;
|
group: Konva.Group;
|
||||||
transformer: Konva.Transformer;
|
objectGroup: Konva.Group;
|
||||||
|
compositingRect: Konva.Rect;
|
||||||
|
transformer: Konva.Transformer;
|
||||||
|
};
|
||||||
|
|
||||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
||||||
|
|
||||||
constructor(entity: RegionEntity, manager: CanvasManager) {
|
constructor(entity: RegionEntity, manager: CanvasManager) {
|
||||||
this.id = entity.id;
|
this.id = entity.id;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.layer = new Konva.Layer({ name: CanvasRegion.LAYER_NAME, listening: false });
|
|
||||||
this.group = new Konva.Group({ name: CanvasRegion.GROUP_NAME, listening: false });
|
|
||||||
this.objectsGroup = new Konva.Group({ name: CanvasRegion.OBJECT_GROUP_NAME, listening: false });
|
|
||||||
this.group.add(this.objectsGroup);
|
|
||||||
this.layer.add(this.group);
|
|
||||||
|
|
||||||
this.transformer = new Konva.Transformer({
|
this.konva = {
|
||||||
name: CanvasRegion.TRANSFORMER_NAME,
|
layer: new Konva.Layer({ name: CanvasRegion.LAYER_NAME, listening: false }),
|
||||||
shouldOverdrawWholeArea: true,
|
group: new Konva.Group({ name: CanvasRegion.GROUP_NAME, listening: false }),
|
||||||
draggable: true,
|
objectGroup: new Konva.Group({ name: CanvasRegion.OBJECT_GROUP_NAME, listening: false }),
|
||||||
dragDistance: 0,
|
transformer: new Konva.Transformer({
|
||||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
name: CanvasRegion.TRANSFORMER_NAME,
|
||||||
rotateEnabled: false,
|
shouldOverdrawWholeArea: true,
|
||||||
flipEnabled: false,
|
draggable: true,
|
||||||
});
|
dragDistance: 0,
|
||||||
this.transformer.on('transformend', () => {
|
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||||
|
rotateEnabled: false,
|
||||||
|
flipEnabled: false,
|
||||||
|
}),
|
||||||
|
compositingRect: new Konva.Rect({ name: CanvasRegion.COMPOSITING_RECT_NAME, listening: false }),
|
||||||
|
};
|
||||||
|
this.konva.group.add(this.konva.objectGroup);
|
||||||
|
this.konva.layer.add(this.konva.group);
|
||||||
|
this.konva.transformer.on('transformend', () => {
|
||||||
this.manager.stateApi.onScaleChanged(
|
this.manager.stateApi.onScaleChanged(
|
||||||
{ id: this.id, scale: this.group.scaleX(), position: { x: this.group.x(), y: this.group.y() } },
|
{
|
||||||
|
id: this.id,
|
||||||
|
scale: this.konva.group.scaleX(),
|
||||||
|
position: { x: this.konva.group.x(), y: this.konva.group.y() },
|
||||||
|
},
|
||||||
'regional_guidance'
|
'regional_guidance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.transformer.on('dragend', () => {
|
this.konva.transformer.on('dragend', () => {
|
||||||
this.manager.stateApi.onPosChanged(
|
this.manager.stateApi.onPosChanged(
|
||||||
{ id: this.id, position: { x: this.group.x(), y: this.group.y() } },
|
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||||
'regional_guidance'
|
'regional_guidance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.layer.add(this.transformer);
|
this.konva.layer.add(this.konva.transformer);
|
||||||
|
this.konva.group.add(this.konva.compositingRect);
|
||||||
this.compositingRect = new Konva.Rect({ name: CanvasRegion.COMPOSITING_RECT_NAME, listening: false });
|
|
||||||
this.group.add(this.compositingRect);
|
|
||||||
this.objects = new Map();
|
this.objects = new Map();
|
||||||
this.drawingBuffer = null;
|
this.drawingBuffer = null;
|
||||||
this.regionState = entity;
|
this.regionState = entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.layer.destroy();
|
this.konva.layer.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDrawingBuffer() {
|
getDrawingBuffer() {
|
||||||
@ -109,7 +118,7 @@ export class CanvasRegion {
|
|||||||
this.regionState = regionState;
|
this.regionState = regionState;
|
||||||
|
|
||||||
// Update the layer's position and listening state
|
// Update the layer's position and listening state
|
||||||
this.group.setAttrs({
|
this.konva.group.setAttrs({
|
||||||
x: regionState.position.x,
|
x: regionState.position.x,
|
||||||
y: regionState.position.y,
|
y: regionState.position.y,
|
||||||
scaleX: 1,
|
scaleX: 1,
|
||||||
@ -151,7 +160,7 @@ export class CanvasRegion {
|
|||||||
if (!brushLine) {
|
if (!brushLine) {
|
||||||
brushLine = new CanvasBrushLine(obj);
|
brushLine = new CanvasBrushLine(obj);
|
||||||
this.objects.set(brushLine.id, brushLine);
|
this.objects.set(brushLine.id, brushLine);
|
||||||
this.objectsGroup.add(brushLine.konvaLineGroup);
|
this.konva.objectGroup.add(brushLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (brushLine.update(obj, force)) {
|
if (brushLine.update(obj, force)) {
|
||||||
@ -165,7 +174,7 @@ export class CanvasRegion {
|
|||||||
if (!eraserLine) {
|
if (!eraserLine) {
|
||||||
eraserLine = new CanvasEraserLine(obj);
|
eraserLine = new CanvasEraserLine(obj);
|
||||||
this.objects.set(eraserLine.id, eraserLine);
|
this.objects.set(eraserLine.id, eraserLine);
|
||||||
this.objectsGroup.add(eraserLine.konvaLineGroup);
|
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (eraserLine.update(obj, force)) {
|
if (eraserLine.update(obj, force)) {
|
||||||
@ -179,7 +188,7 @@ export class CanvasRegion {
|
|||||||
if (!rect) {
|
if (!rect) {
|
||||||
rect = new CanvasRect(obj);
|
rect = new CanvasRect(obj);
|
||||||
this.objects.set(rect.id, rect);
|
this.objects.set(rect.id, rect);
|
||||||
this.objectsGroup.add(rect.konvaRect);
|
this.konva.objectGroup.add(rect.konva.group);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (rect.update(obj, force)) {
|
if (rect.update(obj, force)) {
|
||||||
@ -192,18 +201,18 @@ export class CanvasRegion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateGroup(didDraw: boolean) {
|
updateGroup(didDraw: boolean) {
|
||||||
this.layer.visible(this.regionState.isEnabled);
|
this.konva.layer.visible(this.regionState.isEnabled);
|
||||||
|
|
||||||
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
||||||
this.group.opacity(1);
|
this.konva.group.opacity(1);
|
||||||
|
|
||||||
if (didDraw) {
|
if (didDraw) {
|
||||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||||
const rgbColor = rgbColorToString(this.regionState.fill);
|
const rgbColor = rgbColorToString(this.regionState.fill);
|
||||||
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||||
this.compositingRect.setAttrs({
|
this.konva.compositingRect.setAttrs({
|
||||||
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
||||||
...getNodeBboxFast(this.objectsGroup),
|
...getNodeBboxFast(this.konva.objectGroup),
|
||||||
fill: rgbColor,
|
fill: rgbColor,
|
||||||
opacity: maskOpacity,
|
opacity: maskOpacity,
|
||||||
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
||||||
@ -218,10 +227,10 @@ export class CanvasRegion {
|
|||||||
|
|
||||||
if (this.objects.size === 0) {
|
if (this.objects.size === 0) {
|
||||||
// If the layer is totally empty, reset the cache and bail out.
|
// If the layer is totally empty, reset the cache and bail out.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -229,32 +238,32 @@ export class CanvasRegion {
|
|||||||
if (isSelected && selectedTool === 'move') {
|
if (isSelected && selectedTool === 'move') {
|
||||||
// When the layer is selected and being moved, we should always cache it.
|
// When the layer is selected and being moved, we should always cache it.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
// Activate the transformer
|
// Activate the transformer
|
||||||
this.layer.listening(true);
|
this.konva.layer.listening(true);
|
||||||
this.transformer.nodes([this.group]);
|
this.konva.transformer.nodes([this.konva.group]);
|
||||||
this.transformer.forceUpdate();
|
this.konva.transformer.forceUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelected && selectedTool !== 'move') {
|
if (isSelected && selectedTool !== 'move') {
|
||||||
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
if (isDrawingTool(selectedTool)) {
|
if (isDrawingTool(selectedTool)) {
|
||||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||||
// should never be cached.
|
// should never be cached.
|
||||||
if (this.group.isCached()) {
|
if (this.konva.group.isCached()) {
|
||||||
this.group.clearCache();
|
this.konva.group.clearCache();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
||||||
// We should update the cache if we drew to the layer.
|
// We should update the cache if we drew to the layer.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -262,12 +271,12 @@ export class CanvasRegion {
|
|||||||
|
|
||||||
if (!isSelected) {
|
if (!isSelected) {
|
||||||
// Unselected layers should not be listening
|
// Unselected layers should not be listening
|
||||||
this.layer.listening(false);
|
this.konva.layer.listening(false);
|
||||||
// The transformer also does not need to be active.
|
// The transformer also does not need to be active.
|
||||||
this.transformer.nodes([]);
|
this.konva.transformer.nodes([]);
|
||||||
// Update the layer's cache if it's not already cached or we drew to it.
|
// Update the layer's cache if it's not already cached or we drew to it.
|
||||||
if (!this.group.isCached() || didDraw) {
|
if (!this.konva.group.isCached() || didDraw) {
|
||||||
this.group.cache();
|
this.konva.group.cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -4,14 +4,18 @@ import type { StagingAreaImage } from 'features/controlLayers/store/types';
|
|||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
|
||||||
export class CanvasStagingArea {
|
export class CanvasStagingArea {
|
||||||
group: Konva.Group;
|
static NAME_PREFIX = 'staging-area';
|
||||||
|
static GROUP_NAME = `${CanvasStagingArea.NAME_PREFIX}_group`;
|
||||||
|
|
||||||
|
konva: { group: Konva.Group };
|
||||||
|
|
||||||
image: CanvasImage | null;
|
image: CanvasImage | null;
|
||||||
selectedImage: StagingAreaImage | null;
|
selectedImage: StagingAreaImage | null;
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
|
|
||||||
constructor(manager: CanvasManager) {
|
constructor(manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.group = new Konva.Group({ listening: false });
|
this.konva = { group: new Konva.Group({ name: CanvasStagingArea.GROUP_NAME, listening: false }) };
|
||||||
this.image = null;
|
this.image = null;
|
||||||
this.selectedImage = null;
|
this.selectedImage = null;
|
||||||
}
|
}
|
||||||
@ -42,20 +46,20 @@ export class CanvasStagingArea {
|
|||||||
height,
|
height,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.group.add(this.image.konvaImageGroup);
|
this.konva.group.add(this.image.konva.group);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
|
if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
|
||||||
this.image.konvaImage?.width(imageDTO.width);
|
this.image.image?.width(imageDTO.width);
|
||||||
this.image.konvaImage?.height(imageDTO.height);
|
this.image.image?.height(imageDTO.height);
|
||||||
this.image.konvaImageGroup.x(bboxRect.x + offsetX);
|
this.image.konva.group.x(bboxRect.x + offsetX);
|
||||||
this.image.konvaImageGroup.y(bboxRect.y + offsetY);
|
this.image.konva.group.y(bboxRect.y + offsetY);
|
||||||
await this.image.updateImageSource(imageDTO.image_name);
|
await this.image.updateImageSource(imageDTO.image_name);
|
||||||
this.manager.stateApi.resetLastProgressEvent();
|
this.manager.stateApi.resetLastProgressEvent();
|
||||||
}
|
}
|
||||||
this.image.konvaImageGroup.visible(shouldShowStagedImage);
|
this.image.konva.group.visible(shouldShowStagedImage);
|
||||||
} else {
|
} else {
|
||||||
this.image?.konvaImageGroup.visible(false);
|
this.image?.konva.group.visible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,80 +25,82 @@ export class CanvasTool {
|
|||||||
static ERASER_OUTER_BORDER_CIRCLE_NAME = `${CanvasTool.ERASER_NAME_PREFIX}_outer-border-circle`;
|
static ERASER_OUTER_BORDER_CIRCLE_NAME = `${CanvasTool.ERASER_NAME_PREFIX}_outer-border-circle`;
|
||||||
|
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
group: Konva.Group;
|
konva: {
|
||||||
brush: {
|
|
||||||
group: Konva.Group;
|
group: Konva.Group;
|
||||||
fillCircle: Konva.Circle;
|
brush: {
|
||||||
innerBorderCircle: Konva.Circle;
|
group: Konva.Group;
|
||||||
outerBorderCircle: Konva.Circle;
|
fillCircle: Konva.Circle;
|
||||||
};
|
innerBorderCircle: Konva.Circle;
|
||||||
eraser: {
|
outerBorderCircle: Konva.Circle;
|
||||||
group: Konva.Group;
|
};
|
||||||
fillCircle: Konva.Circle;
|
eraser: {
|
||||||
innerBorderCircle: Konva.Circle;
|
group: Konva.Group;
|
||||||
outerBorderCircle: Konva.Circle;
|
fillCircle: Konva.Circle;
|
||||||
|
innerBorderCircle: Konva.Circle;
|
||||||
|
outerBorderCircle: Konva.Circle;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(manager: CanvasManager) {
|
constructor(manager: CanvasManager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.group = new Konva.Group({ name: CanvasTool.GROUP_NAME });
|
this.konva = {
|
||||||
|
group: new Konva.Group({ name: CanvasTool.GROUP_NAME }),
|
||||||
// Create the brush preview group & circles
|
brush: {
|
||||||
this.brush = {
|
group: new Konva.Group({ name: CanvasTool.BRUSH_GROUP_NAME }),
|
||||||
group: new Konva.Group({ name: CanvasTool.BRUSH_GROUP_NAME }),
|
fillCircle: new Konva.Circle({
|
||||||
fillCircle: new Konva.Circle({
|
name: CanvasTool.BRUSH_FILL_CIRCLE_NAME,
|
||||||
name: CanvasTool.BRUSH_FILL_CIRCLE_NAME,
|
listening: false,
|
||||||
listening: false,
|
strokeEnabled: false,
|
||||||
strokeEnabled: false,
|
}),
|
||||||
}),
|
innerBorderCircle: new Konva.Circle({
|
||||||
innerBorderCircle: new Konva.Circle({
|
name: CanvasTool.BRUSH_INNER_BORDER_CIRCLE_NAME,
|
||||||
name: CanvasTool.BRUSH_INNER_BORDER_CIRCLE_NAME,
|
listening: false,
|
||||||
listening: false,
|
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
strokeEnabled: true,
|
||||||
strokeEnabled: true,
|
}),
|
||||||
}),
|
outerBorderCircle: new Konva.Circle({
|
||||||
outerBorderCircle: new Konva.Circle({
|
name: CanvasTool.BRUSH_OUTER_BORDER_CIRCLE_NAME,
|
||||||
name: CanvasTool.BRUSH_OUTER_BORDER_CIRCLE_NAME,
|
listening: false,
|
||||||
listening: false,
|
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
strokeEnabled: true,
|
||||||
strokeEnabled: true,
|
}),
|
||||||
}),
|
},
|
||||||
|
eraser: {
|
||||||
|
group: new Konva.Group({ name: CanvasTool.ERASER_GROUP_NAME }),
|
||||||
|
fillCircle: new Konva.Circle({
|
||||||
|
name: CanvasTool.ERASER_FILL_CIRCLE_NAME,
|
||||||
|
listening: false,
|
||||||
|
strokeEnabled: false,
|
||||||
|
fill: 'white',
|
||||||
|
globalCompositeOperation: 'destination-out',
|
||||||
|
}),
|
||||||
|
innerBorderCircle: new Konva.Circle({
|
||||||
|
name: CanvasTool.ERASER_INNER_BORDER_CIRCLE_NAME,
|
||||||
|
listening: false,
|
||||||
|
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||||
|
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||||
|
strokeEnabled: true,
|
||||||
|
}),
|
||||||
|
outerBorderCircle: new Konva.Circle({
|
||||||
|
name: CanvasTool.ERASER_OUTER_BORDER_CIRCLE_NAME,
|
||||||
|
listening: false,
|
||||||
|
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||||
|
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||||
|
strokeEnabled: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
this.brush.group.add(this.brush.fillCircle);
|
this.konva.brush.group.add(this.konva.brush.fillCircle);
|
||||||
this.brush.group.add(this.brush.innerBorderCircle);
|
this.konva.brush.group.add(this.konva.brush.innerBorderCircle);
|
||||||
this.brush.group.add(this.brush.outerBorderCircle);
|
this.konva.brush.group.add(this.konva.brush.outerBorderCircle);
|
||||||
this.group.add(this.brush.group);
|
this.konva.group.add(this.konva.brush.group);
|
||||||
|
|
||||||
this.eraser = {
|
this.konva.eraser.group.add(this.konva.eraser.fillCircle);
|
||||||
group: new Konva.Group({ name: CanvasTool.ERASER_GROUP_NAME }),
|
this.konva.eraser.group.add(this.konva.eraser.innerBorderCircle);
|
||||||
fillCircle: new Konva.Circle({
|
this.konva.eraser.group.add(this.konva.eraser.outerBorderCircle);
|
||||||
name: CanvasTool.ERASER_FILL_CIRCLE_NAME,
|
this.konva.group.add(this.konva.eraser.group);
|
||||||
listening: false,
|
|
||||||
strokeEnabled: false,
|
|
||||||
fill: 'white',
|
|
||||||
globalCompositeOperation: 'destination-out',
|
|
||||||
}),
|
|
||||||
innerBorderCircle: new Konva.Circle({
|
|
||||||
name: CanvasTool.ERASER_INNER_BORDER_CIRCLE_NAME,
|
|
||||||
listening: false,
|
|
||||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
|
||||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
|
||||||
strokeEnabled: true,
|
|
||||||
}),
|
|
||||||
outerBorderCircle: new Konva.Circle({
|
|
||||||
name: CanvasTool.ERASER_OUTER_BORDER_CIRCLE_NAME,
|
|
||||||
listening: false,
|
|
||||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
|
||||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
|
||||||
strokeEnabled: true,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
this.eraser.group.add(this.eraser.fillCircle);
|
|
||||||
this.eraser.group.add(this.eraser.innerBorderCircle);
|
|
||||||
this.eraser.group.add(this.eraser.outerBorderCircle);
|
|
||||||
this.group.add(this.eraser.group);
|
|
||||||
|
|
||||||
// // Create the rect preview - this is a rectangle drawn from the last mouse down position to the current cursor position
|
// // Create the rect preview - this is a rectangle drawn from the last mouse down position to the current cursor position
|
||||||
// this.rect = {
|
// this.rect = {
|
||||||
@ -110,7 +112,7 @@ export class CanvasTool {
|
|||||||
// }),
|
// }),
|
||||||
// };
|
// };
|
||||||
// this.rect.group.add(this.rect.fillRect);
|
// this.rect.group.add(this.rect.fillRect);
|
||||||
// this.group.add(this.rect.group);
|
// this.konva.group.add(this.rect.group);
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleTool = () => {
|
scaleTool = () => {
|
||||||
@ -118,15 +120,15 @@ export class CanvasTool {
|
|||||||
const scale = this.manager.stage.scaleX();
|
const scale = this.manager.stage.scaleX();
|
||||||
|
|
||||||
const brushRadius = toolState.brush.width / 2;
|
const brushRadius = toolState.brush.width / 2;
|
||||||
this.brush.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
this.konva.brush.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
||||||
this.brush.outerBorderCircle.setAttrs({
|
this.konva.brush.outerBorderCircle.setAttrs({
|
||||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
|
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||||
radius: brushRadius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
radius: brushRadius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||||
});
|
});
|
||||||
|
|
||||||
const eraserRadius = toolState.eraser.width / 2;
|
const eraserRadius = toolState.eraser.width / 2;
|
||||||
this.eraser.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
this.konva.eraser.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
||||||
this.eraser.outerBorderCircle.setAttrs({
|
this.konva.eraser.outerBorderCircle.setAttrs({
|
||||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
|
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||||
radius: eraserRadius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
radius: eraserRadius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||||
});
|
});
|
||||||
@ -175,16 +177,16 @@ export class CanvasTool {
|
|||||||
|
|
||||||
if (!cursorPos || renderedEntityCount === 0 || !isDrawableEntity) {
|
if (!cursorPos || renderedEntityCount === 0 || !isDrawableEntity) {
|
||||||
// We can bail early if the mouse isn't over the stage or there are no layers
|
// We can bail early if the mouse isn't over the stage or there are no layers
|
||||||
this.group.visible(false);
|
this.konva.group.visible(false);
|
||||||
} else {
|
} else {
|
||||||
this.group.visible(true);
|
this.konva.group.visible(true);
|
||||||
|
|
||||||
// No need to render the brush preview if the cursor position or color is missing
|
// No need to render the brush preview if the cursor position or color is missing
|
||||||
if (cursorPos && tool === 'brush') {
|
if (cursorPos && tool === 'brush') {
|
||||||
const scale = stage.scaleX();
|
const scale = stage.scaleX();
|
||||||
// Update the fill circle
|
// Update the fill circle
|
||||||
const radius = toolState.brush.width / 2;
|
const radius = toolState.brush.width / 2;
|
||||||
this.brush.fillCircle.setAttrs({
|
this.konva.brush.fillCircle.setAttrs({
|
||||||
x: cursorPos.x,
|
x: cursorPos.x,
|
||||||
y: cursorPos.y,
|
y: cursorPos.y,
|
||||||
radius,
|
radius,
|
||||||
@ -192,10 +194,10 @@ export class CanvasTool {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update the inner border of the brush preview
|
// Update the inner border of the brush preview
|
||||||
this.brush.innerBorderCircle.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
|
this.konva.brush.innerBorderCircle.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
|
||||||
|
|
||||||
// Update the outer border of the brush preview
|
// Update the outer border of the brush preview
|
||||||
this.brush.outerBorderCircle.setAttrs({
|
this.konva.brush.outerBorderCircle.setAttrs({
|
||||||
x: cursorPos.x,
|
x: cursorPos.x,
|
||||||
y: cursorPos.y,
|
y: cursorPos.y,
|
||||||
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||||
@ -203,14 +205,14 @@ export class CanvasTool {
|
|||||||
|
|
||||||
this.scaleTool();
|
this.scaleTool();
|
||||||
|
|
||||||
this.brush.group.visible(true);
|
this.konva.brush.group.visible(true);
|
||||||
this.eraser.group.visible(false);
|
this.konva.eraser.group.visible(false);
|
||||||
// this.rect.group.visible(false);
|
// this.rect.group.visible(false);
|
||||||
} else if (cursorPos && tool === 'eraser') {
|
} else if (cursorPos && tool === 'eraser') {
|
||||||
const scale = stage.scaleX();
|
const scale = stage.scaleX();
|
||||||
// Update the fill circle
|
// Update the fill circle
|
||||||
const radius = toolState.eraser.width / 2;
|
const radius = toolState.eraser.width / 2;
|
||||||
this.eraser.fillCircle.setAttrs({
|
this.konva.eraser.fillCircle.setAttrs({
|
||||||
x: cursorPos.x,
|
x: cursorPos.x,
|
||||||
y: cursorPos.y,
|
y: cursorPos.y,
|
||||||
radius,
|
radius,
|
||||||
@ -218,10 +220,10 @@ export class CanvasTool {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update the inner border of the eraser preview
|
// Update the inner border of the eraser preview
|
||||||
this.eraser.innerBorderCircle.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
|
this.konva.eraser.innerBorderCircle.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
|
||||||
|
|
||||||
// Update the outer border of the eraser preview
|
// Update the outer border of the eraser preview
|
||||||
this.eraser.outerBorderCircle.setAttrs({
|
this.konva.eraser.outerBorderCircle.setAttrs({
|
||||||
x: cursorPos.x,
|
x: cursorPos.x,
|
||||||
y: cursorPos.y,
|
y: cursorPos.y,
|
||||||
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||||
@ -229,8 +231,8 @@ export class CanvasTool {
|
|||||||
|
|
||||||
this.scaleTool();
|
this.scaleTool();
|
||||||
|
|
||||||
this.brush.group.visible(false);
|
this.konva.brush.group.visible(false);
|
||||||
this.eraser.group.visible(true);
|
this.konva.eraser.group.visible(true);
|
||||||
// this.rect.group.visible(false);
|
// this.rect.group.visible(false);
|
||||||
// } else if (cursorPos && lastMouseDownPos && tool === 'rect') {
|
// } else if (cursorPos && lastMouseDownPos && tool === 'rect') {
|
||||||
// this.rect.fillRect.setAttrs({
|
// this.rect.fillRect.setAttrs({
|
||||||
@ -241,12 +243,12 @@ export class CanvasTool {
|
|||||||
// fill: rgbaColorToString(currentFill),
|
// fill: rgbaColorToString(currentFill),
|
||||||
// visible: true,
|
// visible: true,
|
||||||
// });
|
// });
|
||||||
// this.brush.group.visible(false);
|
// this.konva.brush.group.visible(false);
|
||||||
// this.eraser.group.visible(false);
|
// this.konva.eraser.group.visible(false);
|
||||||
// this.rect.group.visible(true);
|
// this.rect.group.visible(true);
|
||||||
} else {
|
} else {
|
||||||
this.brush.group.visible(false);
|
this.konva.brush.group.visible(false);
|
||||||
this.eraser.group.visible(false);
|
this.konva.eraser.group.visible(false);
|
||||||
// this.rect.group.visible(false);
|
// this.rect.group.visible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,8 +269,8 @@ export const previewBlob = async (blob: Blob, label?: string) => {
|
|||||||
|
|
||||||
export function getInpaintMaskLayerClone(arg: { manager: CanvasManager }): Konva.Layer {
|
export function getInpaintMaskLayerClone(arg: { manager: CanvasManager }): Konva.Layer {
|
||||||
const { manager } = arg;
|
const { manager } = arg;
|
||||||
const layerClone = manager.inpaintMask.layer.clone();
|
const layerClone = manager.inpaintMask.konva.layer.clone();
|
||||||
const objectGroupClone = manager.inpaintMask.group.clone();
|
const objectGroupClone = manager.inpaintMask.konva.group.clone();
|
||||||
|
|
||||||
layerClone.destroyChildren();
|
layerClone.destroyChildren();
|
||||||
layerClone.add(objectGroupClone);
|
layerClone.add(objectGroupClone);
|
||||||
@ -287,8 +287,8 @@ export function getRegionMaskLayerClone(arg: { manager: CanvasManager; id: strin
|
|||||||
const canvasRegion = manager.regions.get(id);
|
const canvasRegion = manager.regions.get(id);
|
||||||
assert(canvasRegion, `Canvas region with id ${id} not found`);
|
assert(canvasRegion, `Canvas region with id ${id} not found`);
|
||||||
|
|
||||||
const layerClone = canvasRegion.layer.clone();
|
const layerClone = canvasRegion.konva.layer.clone();
|
||||||
const objectGroupClone = canvasRegion.group.clone();
|
const objectGroupClone = canvasRegion.konva.group.clone();
|
||||||
|
|
||||||
layerClone.destroyChildren();
|
layerClone.destroyChildren();
|
||||||
layerClone.add(objectGroupClone);
|
layerClone.add(objectGroupClone);
|
||||||
@ -305,8 +305,8 @@ export function getControlAdapterLayerClone(arg: { manager: CanvasManager; id: s
|
|||||||
const controlAdapter = manager.controlAdapters.get(id);
|
const controlAdapter = manager.controlAdapters.get(id);
|
||||||
assert(controlAdapter, `Canvas region with id ${id} not found`);
|
assert(controlAdapter, `Canvas region with id ${id} not found`);
|
||||||
|
|
||||||
const controlAdapterClone = controlAdapter.layer.clone();
|
const controlAdapterClone = controlAdapter.konva.layer.clone();
|
||||||
const objectGroupClone = controlAdapter.group.clone();
|
const objectGroupClone = controlAdapter.konva.group.clone();
|
||||||
|
|
||||||
controlAdapterClone.destroyChildren();
|
controlAdapterClone.destroyChildren();
|
||||||
controlAdapterClone.add(objectGroupClone);
|
controlAdapterClone.add(objectGroupClone);
|
||||||
@ -322,8 +322,8 @@ export function getInitialImageLayerClone(arg: { manager: CanvasManager }): Konv
|
|||||||
|
|
||||||
const initialImage = manager.initialImage;
|
const initialImage = manager.initialImage;
|
||||||
|
|
||||||
const initialImageClone = initialImage.layer.clone();
|
const initialImageClone = initialImage.konva.layer.clone();
|
||||||
const objectGroupClone = initialImage.group.clone();
|
const objectGroupClone = initialImage.konva.group.clone();
|
||||||
|
|
||||||
initialImageClone.destroyChildren();
|
initialImageClone.destroyChildren();
|
||||||
initialImageClone.add(objectGroupClone);
|
initialImageClone.add(objectGroupClone);
|
||||||
|
Loading…
Reference in New Issue
Block a user