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 LAYER_NAME = `${CanvasBackground.BASE_NAME}_layer`;
|
||||
|
||||
layer: Konva.Layer;
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
};
|
||||
|
||||
manager: CanvasManager;
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
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() {
|
||||
this.layer.zIndex(0);
|
||||
this.konva.layer.zIndex(0);
|
||||
const scale = this.manager.stage.scaleX();
|
||||
const gridSpacing = getGridSpacing(scale);
|
||||
const x = this.manager.stage.x();
|
||||
@ -86,11 +89,11 @@ export class CanvasBackground {
|
||||
let _x = 0;
|
||||
let _y = 0;
|
||||
|
||||
this.layer.destroyChildren();
|
||||
this.konva.layer.destroyChildren();
|
||||
|
||||
for (let i = 0; i < xSteps; i++) {
|
||||
_x = gridFullRect.x1 + i * gridSpacing;
|
||||
this.layer.add(
|
||||
this.konva.layer.add(
|
||||
new Konva.Line({
|
||||
x: _x,
|
||||
y: gridFullRect.y1,
|
||||
@ -103,7 +106,7 @@ export class CanvasBackground {
|
||||
}
|
||||
for (let i = 0; i < ySteps; i++) {
|
||||
_y = gridFullRect.y1 + i * gridSpacing;
|
||||
this.layer.add(
|
||||
this.konva.layer.add(
|
||||
new Konva.Line({
|
||||
x: gridFullRect.x1,
|
||||
y: _y,
|
||||
|
@ -11,11 +11,10 @@ export class CanvasBbox {
|
||||
static RECT_NAME = `${CanvasBbox.BASE_NAME}_rect`;
|
||||
static TRANSFORMER_NAME = `${CanvasBbox.BASE_NAME}_transformer`;
|
||||
|
||||
group: Konva.Group;
|
||||
rect: Konva.Rect;
|
||||
transformer: Konva.Transformer;
|
||||
manager: CanvasManager;
|
||||
|
||||
konva: { group: Konva.Group; rect: Konva.Rect; transformer: Konva.Transformer };
|
||||
|
||||
ALL_ANCHORS: string[] = [
|
||||
'top-left',
|
||||
'top-center',
|
||||
@ -36,88 +35,89 @@ export class CanvasBbox {
|
||||
const bbox = this.manager.stateApi.getBbox();
|
||||
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
|
||||
// transparent rect for this purpose.
|
||||
this.group = new Konva.Group({ name: CanvasBbox.GROUP_NAME, listening: false });
|
||||
this.rect = new Konva.Rect({
|
||||
name: CanvasBbox.RECT_NAME,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
draggable: true,
|
||||
...this.manager.stateApi.getBbox(),
|
||||
});
|
||||
this.rect.on('dragmove', () => {
|
||||
this.konva = {
|
||||
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
|
||||
// transparent rect for this purpose.
|
||||
rect: new Konva.Rect({
|
||||
name: CanvasBbox.RECT_NAME,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
draggable: true,
|
||||
...this.manager.stateApi.getBbox(),
|
||||
}),
|
||||
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 bbox = this.manager.stateApi.getBbox();
|
||||
const bboxRect: Rect = {
|
||||
...bbox.rect,
|
||||
x: roundToMultiple(this.rect.x(), gridSize),
|
||||
y: roundToMultiple(this.rect.y(), gridSize),
|
||||
x: roundToMultiple(this.konva.rect.x(), 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) {
|
||||
this.manager.stateApi.onBboxTransformed(bboxRect);
|
||||
}
|
||||
});
|
||||
|
||||
this.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.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', () => {
|
||||
this.konva.transformer.on('transform', () => {
|
||||
// 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.
|
||||
const anchor = this.transformer.getActiveAnchor();
|
||||
const anchor = this.konva.transformer.getActiveAnchor();
|
||||
if (!anchor) {
|
||||
// Pretty sure we should always have an anchor here?
|
||||
return;
|
||||
@ -140,14 +140,14 @@ export class CanvasBbox {
|
||||
}
|
||||
|
||||
// The coords should be correct per the anchorDragBoundFunc.
|
||||
let x = this.rect.x();
|
||||
let y = this.rect.y();
|
||||
let x = this.konva.rect.x();
|
||||
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
|
||||
// *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.
|
||||
let width = roundToMultipleMin(this.rect.width() * this.rect.scaleX(), gridSize);
|
||||
let height = roundToMultipleMin(this.rect.height() * this.rect.scaleY(), gridSize);
|
||||
let width = roundToMultipleMin(this.konva.rect.width() * this.konva.rect.scaleX(), 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 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.
|
||||
// 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...
|
||||
this.rect.setAttrs({ ...bboxRect, scaleX: 1, scaleY: 1 });
|
||||
this.konva.rect.setAttrs({ ...bboxRect, scaleX: 1, scaleY: 1 });
|
||||
|
||||
// Update the bbox in internal state.
|
||||
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,
|
||||
// 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
|
||||
this.transformer.nodes([this.rect]);
|
||||
this.group.add(this.rect);
|
||||
this.group.add(this.transformer);
|
||||
this.konva.transformer.nodes([this.konva.rect]);
|
||||
this.konva.group.add(this.konva.rect);
|
||||
this.konva.group.add(this.konva.transformer);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -217,14 +217,14 @@ export class CanvasBbox {
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
|
||||
if (!session.isActive) {
|
||||
this.group.listening(false);
|
||||
this.group.visible(false);
|
||||
this.konva.group.listening(false);
|
||||
this.konva.group.visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.group.visible(true);
|
||||
this.group.listening(toolState.selected === 'bbox');
|
||||
this.rect.setAttrs({
|
||||
this.konva.group.visible(true);
|
||||
this.konva.group.listening(toolState.selected === 'bbox');
|
||||
this.konva.rect.setAttrs({
|
||||
x: bbox.rect.x,
|
||||
y: bbox.rect.y,
|
||||
width: bbox.rect.width,
|
||||
@ -233,7 +233,7 @@ export class CanvasBbox {
|
||||
scaleY: 1,
|
||||
listening: toolState.selected === 'bbox',
|
||||
});
|
||||
this.transformer.setAttrs({
|
||||
this.konva.transformer.setAttrs({
|
||||
listening: toolState.selected === 'bbox',
|
||||
enabledAnchors: toolState.selected === 'bbox' ? this.ALL_ANCHORS : this.NO_ANCHORS,
|
||||
});
|
||||
|
@ -8,40 +8,44 @@ export class CanvasBrushLine {
|
||||
static LINE_NAME = `${CanvasBrushLine.NAME_PREFIX}_line`;
|
||||
|
||||
id: string;
|
||||
konvaLineGroup: Konva.Group;
|
||||
konvaLine: Konva.Line;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
line: Konva.Line;
|
||||
};
|
||||
lastBrushLine: BrushLine;
|
||||
|
||||
constructor(brushLine: BrushLine) {
|
||||
const { id, strokeWidth, clip, color, points } = brushLine;
|
||||
this.id = id;
|
||||
this.konvaLineGroup = new Konva.Group({
|
||||
name: CanvasBrushLine.GROUP_NAME,
|
||||
clip,
|
||||
listening: false,
|
||||
});
|
||||
this.konvaLine = new Konva.Line({
|
||||
name: CanvasBrushLine.LINE_NAME,
|
||||
id,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
tension: 0,
|
||||
lineCap: 'round',
|
||||
lineJoin: 'round',
|
||||
globalCompositeOperation: 'source-over',
|
||||
stroke: rgbaColorToString(color),
|
||||
// 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: new Konva.Group({
|
||||
name: CanvasBrushLine.GROUP_NAME,
|
||||
clip,
|
||||
listening: false,
|
||||
}),
|
||||
line: new Konva.Line({
|
||||
name: CanvasBrushLine.LINE_NAME,
|
||||
id,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
tension: 0,
|
||||
lineCap: 'round',
|
||||
lineJoin: 'round',
|
||||
globalCompositeOperation: 'source-over',
|
||||
stroke: rgbaColorToString(color),
|
||||
// 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.konva.group.add(this.konva.line);
|
||||
this.lastBrushLine = brushLine;
|
||||
}
|
||||
|
||||
update(brushLine: BrushLine, force?: boolean): boolean {
|
||||
if (this.lastBrushLine !== brushLine || force) {
|
||||
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
|
||||
points: points.length === 2 ? [...points, ...points] : points,
|
||||
stroke: rgbaColorToString(color),
|
||||
@ -56,6 +60,6 @@ export class CanvasBrushLine {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.konvaLineGroup.destroy();
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -14,51 +14,60 @@ export class CanvasControlAdapter {
|
||||
|
||||
id: string;
|
||||
manager: CanvasManager;
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectsGroup: Konva.Group;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectGroup: Konva.Group;
|
||||
transformer: Konva.Transformer;
|
||||
};
|
||||
|
||||
image: CanvasImage | null;
|
||||
transformer: Konva.Transformer;
|
||||
|
||||
constructor(controlAdapterState: ControlAdapterEntity, manager: CanvasManager) {
|
||||
const { id } = controlAdapterState;
|
||||
this.id = id;
|
||||
this.manager = manager;
|
||||
this.layer = new Konva.Layer({
|
||||
name: CanvasControlAdapter.LAYER_NAME,
|
||||
imageSmoothingEnabled: false,
|
||||
listening: false,
|
||||
});
|
||||
this.group = new Konva.Group({
|
||||
name: CanvasControlAdapter.GROUP_NAME,
|
||||
listening: false,
|
||||
});
|
||||
this.objectsGroup = new Konva.Group({ name: CanvasControlAdapter.GROUP_NAME, listening: false });
|
||||
this.group.add(this.objectsGroup);
|
||||
this.layer.add(this.group);
|
||||
|
||||
this.transformer = new Konva.Transformer({
|
||||
name: CanvasControlAdapter.TRANSFORMER_NAME,
|
||||
shouldOverdrawWholeArea: true,
|
||||
draggable: true,
|
||||
dragDistance: 0,
|
||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||
rotateEnabled: false,
|
||||
flipEnabled: false,
|
||||
});
|
||||
this.transformer.on('transformend', () => {
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({
|
||||
name: CanvasControlAdapter.LAYER_NAME,
|
||||
imageSmoothingEnabled: false,
|
||||
listening: false,
|
||||
}),
|
||||
group: new Konva.Group({
|
||||
name: CanvasControlAdapter.GROUP_NAME,
|
||||
listening: false,
|
||||
}),
|
||||
objectGroup: new Konva.Group({ name: CanvasControlAdapter.GROUP_NAME, listening: false }),
|
||||
transformer: new Konva.Transformer({
|
||||
name: CanvasControlAdapter.TRANSFORMER_NAME,
|
||||
shouldOverdrawWholeArea: true,
|
||||
draggable: true,
|
||||
dragDistance: 0,
|
||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||
rotateEnabled: false,
|
||||
flipEnabled: false,
|
||||
}),
|
||||
};
|
||||
this.konva.transformer.on('transformend', () => {
|
||||
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'
|
||||
);
|
||||
});
|
||||
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() } },
|
||||
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||
'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.controlAdapterState = controlAdapterState;
|
||||
@ -68,7 +77,7 @@ export class CanvasControlAdapter {
|
||||
this.controlAdapterState = controlAdapterState;
|
||||
|
||||
// Update the layer's position and listening state
|
||||
this.group.setAttrs({
|
||||
this.konva.group.setAttrs({
|
||||
x: controlAdapterState.position.x,
|
||||
y: controlAdapterState.position.y,
|
||||
scaleX: 1,
|
||||
@ -81,13 +90,13 @@ export class CanvasControlAdapter {
|
||||
|
||||
if (!imageObject) {
|
||||
if (this.image) {
|
||||
this.image.konvaImageGroup.visible(false);
|
||||
this.image.konva.group.visible(false);
|
||||
didDraw = true;
|
||||
}
|
||||
} else if (!this.image) {
|
||||
this.image = new CanvasImage(imageObject);
|
||||
this.updateGroup(true);
|
||||
this.objectsGroup.add(this.image.konvaImageGroup);
|
||||
this.konva.objectGroup.add(this.image.konva.group);
|
||||
await this.image.updateImageSource(imageObject.image.name);
|
||||
} else if (!this.image.isLoading && !this.image.isError) {
|
||||
if (await this.image.update(imageObject)) {
|
||||
@ -99,18 +108,18 @@ export class CanvasControlAdapter {
|
||||
}
|
||||
|
||||
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 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.
|
||||
this.layer.listening(false);
|
||||
this.transformer.nodes([]);
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
this.konva.layer.listening(false);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -118,32 +127,32 @@ export class CanvasControlAdapter {
|
||||
if (isSelected && selectedTool === 'move') {
|
||||
// When the layer is selected and being moved, we should always cache it.
|
||||
// We should update the cache if we drew to the layer.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
// Activate the transformer
|
||||
this.layer.listening(true);
|
||||
this.transformer.nodes([this.group]);
|
||||
this.transformer.forceUpdate();
|
||||
this.konva.layer.listening(true);
|
||||
this.konva.transformer.nodes([this.konva.group]);
|
||||
this.konva.transformer.forceUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelected && selectedTool !== 'move') {
|
||||
// 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.
|
||||
this.transformer.nodes([]);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (isDrawingTool(selectedTool)) {
|
||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||
// should never be cached.
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -151,12 +160,12 @@ export class CanvasControlAdapter {
|
||||
|
||||
if (!isSelected) {
|
||||
// Unselected layers should not be listening
|
||||
this.layer.listening(false);
|
||||
this.konva.layer.listening(false);
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
|
||||
return;
|
||||
@ -164,6 +173,6 @@ export class CanvasControlAdapter {
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.layer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -9,40 +9,44 @@ export class CanvasEraserLine {
|
||||
static LINE_NAME = `${CanvasEraserLine.NAME_PREFIX}_line`;
|
||||
|
||||
id: string;
|
||||
konvaLineGroup: Konva.Group;
|
||||
konvaLine: Konva.Line;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
line: Konva.Line;
|
||||
};
|
||||
lastEraserLine: EraserLine;
|
||||
|
||||
constructor(eraserLine: EraserLine) {
|
||||
const { id, strokeWidth, clip, points } = eraserLine;
|
||||
this.id = id;
|
||||
this.konvaLineGroup = new Konva.Group({
|
||||
name: CanvasEraserLine.GROUP_NAME,
|
||||
clip,
|
||||
listening: false,
|
||||
});
|
||||
this.konvaLine = new Konva.Line({
|
||||
name: CanvasEraserLine.LINE_NAME,
|
||||
id,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
tension: 0,
|
||||
lineCap: 'round',
|
||||
lineJoin: 'round',
|
||||
globalCompositeOperation: 'destination-out',
|
||||
stroke: rgbaColorToString(RGBA_RED),
|
||||
// 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: new Konva.Group({
|
||||
name: CanvasEraserLine.GROUP_NAME,
|
||||
clip,
|
||||
listening: false,
|
||||
}),
|
||||
line: new Konva.Line({
|
||||
name: CanvasEraserLine.LINE_NAME,
|
||||
id,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
tension: 0,
|
||||
lineCap: 'round',
|
||||
lineJoin: 'round',
|
||||
globalCompositeOperation: 'destination-out',
|
||||
stroke: rgbaColorToString(RGBA_RED),
|
||||
// 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.konva.group.add(this.konva.line);
|
||||
this.lastEraserLine = eraserLine;
|
||||
}
|
||||
|
||||
update(eraserLine: EraserLine, force?: boolean): boolean {
|
||||
if (this.lastEraserLine !== eraserLine || force) {
|
||||
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
|
||||
points: points.length === 2 ? [...points, ...points] : points,
|
||||
clip,
|
||||
@ -56,6 +60,6 @@ export class CanvasEraserLine {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.konvaLineGroup.destroy();
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -15,48 +15,51 @@ export class CanvasImage {
|
||||
static PLACEHOLDER_TEXT_NAME = `${CanvasImage.NAME_PREFIX}_placeholder-text`;
|
||||
|
||||
id: string;
|
||||
konvaImageGroup: Konva.Group;
|
||||
konvaPlaceholderGroup: Konva.Group;
|
||||
konvaPlaceholderRect: Konva.Rect;
|
||||
konvaPlaceholderText: Konva.Text;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
placeholder: { group: Konva.Group; rect: Konva.Rect; text: Konva.Text };
|
||||
};
|
||||
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;
|
||||
isError: boolean;
|
||||
lastImageObject: ImageObject;
|
||||
|
||||
constructor(imageObject: ImageObject) {
|
||||
const { id, width, height, x, y } = imageObject;
|
||||
this.konvaImageGroup = new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y });
|
||||
this.konvaPlaceholderGroup = new Konva.Group({ name: CanvasImage.PLACEHOLDER_GROUP_NAME, listening: false });
|
||||
this.konvaPlaceholderRect = new Konva.Rect({
|
||||
name: CanvasImage.PLACEHOLDER_RECT_NAME,
|
||||
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
|
||||
width,
|
||||
height,
|
||||
listening: false,
|
||||
});
|
||||
this.konvaPlaceholderText = new Konva.Text({
|
||||
name: CanvasImage.PLACEHOLDER_TEXT_NAME,
|
||||
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
|
||||
width,
|
||||
height,
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
fontFamily: '"Inter Variable", sans-serif',
|
||||
fontSize: width / 16,
|
||||
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 = {
|
||||
group: new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y }),
|
||||
placeholder: {
|
||||
group: new Konva.Group({ name: CanvasImage.PLACEHOLDER_GROUP_NAME, listening: false }),
|
||||
rect: new Konva.Rect({
|
||||
name: CanvasImage.PLACEHOLDER_RECT_NAME,
|
||||
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
|
||||
width,
|
||||
height,
|
||||
listening: false,
|
||||
}),
|
||||
text: new Konva.Text({
|
||||
name: CanvasImage.PLACEHOLDER_TEXT_NAME,
|
||||
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
|
||||
width,
|
||||
height,
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
fontFamily: '"Inter Variable", sans-serif',
|
||||
fontSize: width / 16,
|
||||
fontStyle: '600',
|
||||
text: t('common.loadingImage', 'Loading Image'),
|
||||
listening: false,
|
||||
}),
|
||||
},
|
||||
};
|
||||
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.imageName = null;
|
||||
this.konvaImage = null;
|
||||
this.image = null;
|
||||
this.isLoading = false;
|
||||
this.isError = false;
|
||||
this.lastImageObject = imageObject;
|
||||
@ -65,51 +68,51 @@ export class CanvasImage {
|
||||
async updateImageSource(imageName: string) {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
this.konvaImageGroup.visible(true);
|
||||
this.konva.group.visible(true);
|
||||
|
||||
if (!this.konvaImage) {
|
||||
this.konvaPlaceholderGroup.visible(true);
|
||||
this.konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image'));
|
||||
if (!this.image) {
|
||||
this.konva.placeholder.group.visible(true);
|
||||
this.konva.placeholder.text.text(t('common.loadingImage', 'Loading Image'));
|
||||
}
|
||||
|
||||
const imageDTO = await getImageDTO(imageName);
|
||||
assert(imageDTO !== null, 'imageDTO is null');
|
||||
const imageEl = await loadImage(imageDTO.image_url);
|
||||
|
||||
if (this.konvaImage) {
|
||||
this.konvaImage.setAttrs({
|
||||
if (this.image) {
|
||||
this.image.setAttrs({
|
||||
image: imageEl,
|
||||
});
|
||||
} else {
|
||||
this.konvaImage = new Konva.Image({
|
||||
this.image = new Konva.Image({
|
||||
name: CanvasImage.IMAGE_NAME,
|
||||
listening: false,
|
||||
image: imageEl,
|
||||
width: this.lastImageObject.width,
|
||||
height: this.lastImageObject.height,
|
||||
});
|
||||
this.konvaImageGroup.add(this.konvaImage);
|
||||
this.konva.group.add(this.image);
|
||||
}
|
||||
|
||||
if (this.lastImageObject.filters.length > 0) {
|
||||
this.konvaImage.cache();
|
||||
this.konvaImage.filters(this.lastImageObject.filters.map((f) => FILTER_MAP[f]));
|
||||
this.image.cache();
|
||||
this.image.filters(this.lastImageObject.filters.map((f) => FILTER_MAP[f]));
|
||||
} else {
|
||||
this.konvaImage.clearCache();
|
||||
this.konvaImage.filters([]);
|
||||
this.image.clearCache();
|
||||
this.image.filters([]);
|
||||
}
|
||||
|
||||
this.imageName = imageName;
|
||||
this.isLoading = false;
|
||||
this.isError = false;
|
||||
this.konvaPlaceholderGroup.visible(false);
|
||||
this.konva.placeholder.group.visible(false);
|
||||
} catch {
|
||||
this.konvaImage?.visible(false);
|
||||
this.image?.visible(false);
|
||||
this.imageName = null;
|
||||
this.isLoading = false;
|
||||
this.isError = true;
|
||||
this.konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
|
||||
this.konvaPlaceholderGroup.visible(true);
|
||||
this.konva.placeholder.text.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
|
||||
this.konva.placeholder.group.visible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,16 +122,16 @@ export class CanvasImage {
|
||||
if (this.lastImageObject.image.name !== image.name || force) {
|
||||
await this.updateImageSource(image.name);
|
||||
}
|
||||
this.konvaImage?.setAttrs({ x, y, width, height });
|
||||
this.image?.setAttrs({ x, y, width, height });
|
||||
if (filters.length > 0) {
|
||||
this.konvaImage?.cache();
|
||||
this.konvaImage?.filters(filters.map((f) => FILTER_MAP[f]));
|
||||
this.image?.cache();
|
||||
this.image?.filters(filters.map((f) => FILTER_MAP[f]));
|
||||
} else {
|
||||
this.konvaImage?.clearCache();
|
||||
this.konvaImage?.filters([]);
|
||||
this.image?.clearCache();
|
||||
this.image?.filters([]);
|
||||
}
|
||||
this.konvaPlaceholderRect.setAttrs({ width, height });
|
||||
this.konvaPlaceholderText.setAttrs({ width, height, fontSize: width / 16 });
|
||||
this.konva.placeholder.rect.setAttrs({ width, height });
|
||||
this.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
|
||||
this.lastImageObject = imageObject;
|
||||
return true;
|
||||
} else {
|
||||
@ -137,6 +140,6 @@ export class CanvasImage {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.konvaImageGroup.destroy();
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,37 @@
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
|
||||
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
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';
|
||||
manager: CanvasManager;
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectsGroup: Konva.Group;
|
||||
image: CanvasImage | null;
|
||||
|
||||
private initialImageState: InitialImageEntity;
|
||||
|
||||
manager: CanvasManager;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectGroup: Konva.Group;
|
||||
};
|
||||
|
||||
image: CanvasImage | null;
|
||||
|
||||
constructor(initialImageState: InitialImageEntity, manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.layer = new Konva.Layer({
|
||||
id: this.id,
|
||||
imageSmoothingEnabled: true,
|
||||
listening: false,
|
||||
});
|
||||
this.group = new Konva.Group({
|
||||
id: getObjectGroupId(this.layer.id(), uuidv4()),
|
||||
listening: false,
|
||||
});
|
||||
this.objectsGroup = new Konva.Group({ listening: false });
|
||||
this.group.add(this.objectsGroup);
|
||||
this.layer.add(this.group);
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ name: CanvasInitialImage.LAYER_NAME, imageSmoothingEnabled: true, listening: false }),
|
||||
group: new Konva.Group({ name: CanvasInitialImage.GROUP_NAME, listening: false }),
|
||||
objectGroup: new Konva.Group({ name: CanvasInitialImage.OBJECT_GROUP_NAME, listening: false }),
|
||||
};
|
||||
this.konva.group.add(this.konva.objectGroup);
|
||||
this.konva.layer.add(this.konva.group);
|
||||
|
||||
this.image = null;
|
||||
this.initialImageState = initialImageState;
|
||||
@ -37,26 +41,26 @@ export class CanvasInitialImage {
|
||||
this.initialImageState = initialImageState;
|
||||
|
||||
if (!this.initialImageState.imageObject) {
|
||||
this.layer.visible(false);
|
||||
this.konva.layer.visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.image) {
|
||||
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);
|
||||
} else if (!this.image.isLoading && !this.image.isError) {
|
||||
await this.image.update(this.initialImageState.imageObject);
|
||||
}
|
||||
|
||||
if (this.initialImageState && this.initialImageState.isEnabled && !this.image?.isLoading && !this.image?.isError) {
|
||||
this.layer.visible(true);
|
||||
this.konva.layer.visible(true);
|
||||
} else {
|
||||
this.layer.visible(false);
|
||||
this.konva.layer.visible(false);
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.layer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -23,57 +23,64 @@ export class CanvasInpaintMask {
|
||||
|
||||
id = 'inpaint_mask';
|
||||
manager: CanvasManager;
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectsGroup: Konva.Group;
|
||||
compositingRect: Konva.Rect;
|
||||
transformer: Konva.Transformer;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectGroup: Konva.Group;
|
||||
transformer: Konva.Transformer;
|
||||
compositingRect: Konva.Rect;
|
||||
};
|
||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
||||
|
||||
constructor(entity: InpaintMaskEntity, manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.layer = new Konva.Layer({ name: CanvasInpaintMask.LAYER_NAME });
|
||||
|
||||
this.group = new Konva.Group({
|
||||
name: CanvasInpaintMask.GROUP_NAME,
|
||||
listening: false,
|
||||
});
|
||||
this.objectsGroup = new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false });
|
||||
this.group.add(this.objectsGroup);
|
||||
this.layer.add(this.group);
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ name: CanvasInpaintMask.LAYER_NAME }),
|
||||
group: new Konva.Group({ name: CanvasInpaintMask.GROUP_NAME, listening: false }),
|
||||
objectGroup: new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false }),
|
||||
transformer: new Konva.Transformer({
|
||||
name: CanvasInpaintMask.TRANSFORMER_NAME,
|
||||
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({
|
||||
name: CanvasInpaintMask.TRANSFORMER_NAME,
|
||||
shouldOverdrawWholeArea: true,
|
||||
draggable: true,
|
||||
dragDistance: 0,
|
||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||
rotateEnabled: false,
|
||||
flipEnabled: false,
|
||||
});
|
||||
this.transformer.on('transformend', () => {
|
||||
this.konva.group.add(this.konva.objectGroup);
|
||||
this.konva.layer.add(this.konva.group);
|
||||
|
||||
this.konva.transformer.on('transformend', () => {
|
||||
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'
|
||||
);
|
||||
});
|
||||
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() } },
|
||||
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||
'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.group.add(this.compositingRect);
|
||||
this.konva.group.add(this.konva.compositingRect);
|
||||
this.objects = new Map();
|
||||
this.drawingBuffer = null;
|
||||
this.inpaintMaskState = entity;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.layer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
|
||||
getDrawingBuffer() {
|
||||
@ -112,7 +119,7 @@ export class CanvasInpaintMask {
|
||||
this.inpaintMaskState = inpaintMaskState;
|
||||
|
||||
// Update the layer's position and listening state
|
||||
this.group.setAttrs({
|
||||
this.konva.group.setAttrs({
|
||||
x: inpaintMaskState.position.x,
|
||||
y: inpaintMaskState.position.y,
|
||||
scaleX: 1,
|
||||
@ -154,7 +161,7 @@ export class CanvasInpaintMask {
|
||||
if (!brushLine) {
|
||||
brushLine = new CanvasBrushLine(obj);
|
||||
this.objects.set(brushLine.id, brushLine);
|
||||
this.objectsGroup.add(brushLine.konvaLineGroup);
|
||||
this.konva.objectGroup.add(brushLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (brushLine.update(obj, force)) {
|
||||
@ -168,7 +175,7 @@ export class CanvasInpaintMask {
|
||||
if (!eraserLine) {
|
||||
eraserLine = new CanvasEraserLine(obj);
|
||||
this.objects.set(eraserLine.id, eraserLine);
|
||||
this.objectsGroup.add(eraserLine.konvaLineGroup);
|
||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (eraserLine.update(obj, force)) {
|
||||
@ -182,7 +189,7 @@ export class CanvasInpaintMask {
|
||||
if (!rect) {
|
||||
rect = new CanvasRect(obj);
|
||||
this.objects.set(rect.id, rect);
|
||||
this.objectsGroup.add(rect.konvaRect);
|
||||
this.konva.objectGroup.add(rect.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (rect.update(obj, force)) {
|
||||
@ -195,19 +202,19 @@ export class CanvasInpaintMask {
|
||||
}
|
||||
|
||||
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
|
||||
this.group.opacity(1);
|
||||
this.konva.group.opacity(1);
|
||||
|
||||
if (didDraw) {
|
||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||
const rgbColor = rgbColorToString(this.inpaintMaskState.fill);
|
||||
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
|
||||
...getNodeBboxFast(this.objectsGroup),
|
||||
...getNodeBboxFast(this.konva.objectGroup),
|
||||
fill: rgbColor,
|
||||
opacity: maskOpacity,
|
||||
// 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 the layer is totally empty, reset the cache and bail out.
|
||||
this.layer.listening(false);
|
||||
this.transformer.nodes([]);
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
this.konva.layer.listening(false);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -234,32 +241,32 @@ export class CanvasInpaintMask {
|
||||
if (isSelected && selectedTool === 'move') {
|
||||
// When the layer is selected and being moved, we should always cache it.
|
||||
// We should update the cache if we drew to the layer.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
// Activate the transformer
|
||||
this.layer.listening(true);
|
||||
this.transformer.nodes([this.group]);
|
||||
this.transformer.forceUpdate();
|
||||
this.konva.layer.listening(true);
|
||||
this.konva.transformer.nodes([this.konva.group]);
|
||||
this.konva.transformer.forceUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelected && selectedTool !== 'move') {
|
||||
// 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.
|
||||
this.transformer.nodes([]);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (isDrawingTool(selectedTool)) {
|
||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||
// should never be cached.
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -267,12 +274,12 @@ export class CanvasInpaintMask {
|
||||
|
||||
if (!isSelected) {
|
||||
// Unselected layers should not be listening
|
||||
this.layer.listening(false);
|
||||
this.konva.layer.listening(false);
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -21,40 +21,53 @@ export class CanvasLayer {
|
||||
|
||||
id: string;
|
||||
manager: CanvasManager;
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectsGroup: Konva.Group;
|
||||
transformer: Konva.Transformer;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectGroup: Konva.Group;
|
||||
transformer: Konva.Transformer;
|
||||
};
|
||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
||||
|
||||
constructor(entity: LayerEntity, manager: CanvasManager) {
|
||||
this.id = entity.id;
|
||||
this.manager = manager;
|
||||
this.layer = new Konva.Layer({ name: CanvasLayer.LAYER_NAME, listening: false });
|
||||
this.group = new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: false });
|
||||
this.objectsGroup = new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false });
|
||||
this.group.add(this.objectsGroup);
|
||||
this.layer.add(this.group);
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ name: CanvasLayer.LAYER_NAME, listening: false }),
|
||||
group: new Konva.Group({ name: CanvasLayer.GROUP_NAME, listening: false }),
|
||||
objectGroup: new Konva.Group({ name: CanvasLayer.OBJECT_GROUP_NAME, listening: false }),
|
||||
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({
|
||||
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.on('transformend', () => {
|
||||
this.konva.group.add(this.konva.objectGroup);
|
||||
this.konva.layer.add(this.konva.group);
|
||||
|
||||
this.konva.transformer.on('transformend', () => {
|
||||
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'
|
||||
);
|
||||
});
|
||||
this.transformer.on('dragend', () => {
|
||||
this.manager.stateApi.onPosChanged({ id: this.id, position: { x: this.group.x(), y: this.group.y() } }, 'layer');
|
||||
this.konva.transformer.on('dragend', () => {
|
||||
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.drawingBuffer = null;
|
||||
@ -62,7 +75,7 @@ export class CanvasLayer {
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.layer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
|
||||
getDrawingBuffer() {
|
||||
@ -97,7 +110,7 @@ export class CanvasLayer {
|
||||
this.layerState = layerState;
|
||||
|
||||
// Update the layer's position and listening state
|
||||
this.group.setAttrs({
|
||||
this.konva.group.setAttrs({
|
||||
x: layerState.position.x,
|
||||
y: layerState.position.y,
|
||||
scaleX: 1,
|
||||
@ -139,7 +152,7 @@ export class CanvasLayer {
|
||||
if (!brushLine) {
|
||||
brushLine = new CanvasBrushLine(obj);
|
||||
this.objects.set(brushLine.id, brushLine);
|
||||
this.objectsGroup.add(brushLine.konvaLineGroup);
|
||||
this.konva.objectGroup.add(brushLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (brushLine.update(obj, force)) {
|
||||
@ -153,7 +166,7 @@ export class CanvasLayer {
|
||||
if (!eraserLine) {
|
||||
eraserLine = new CanvasEraserLine(obj);
|
||||
this.objects.set(eraserLine.id, eraserLine);
|
||||
this.objectsGroup.add(eraserLine.konvaLineGroup);
|
||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (eraserLine.update(obj, force)) {
|
||||
@ -167,7 +180,7 @@ export class CanvasLayer {
|
||||
if (!rect) {
|
||||
rect = new CanvasRect(obj);
|
||||
this.objects.set(rect.id, rect);
|
||||
this.objectsGroup.add(rect.konvaRect);
|
||||
this.konva.objectGroup.add(rect.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (rect.update(obj, force)) {
|
||||
@ -181,7 +194,7 @@ export class CanvasLayer {
|
||||
if (!image) {
|
||||
image = new CanvasImage(obj);
|
||||
this.objects.set(image.id, image);
|
||||
this.objectsGroup.add(image.konvaImageGroup);
|
||||
this.konva.objectGroup.add(image.konva.group);
|
||||
await image.updateImageSource(obj.image.name);
|
||||
return true;
|
||||
} else {
|
||||
@ -196,58 +209,58 @@ export class CanvasLayer {
|
||||
|
||||
updateGroup(didDraw: boolean) {
|
||||
if (!this.layerState.isEnabled) {
|
||||
this.layer.visible(false);
|
||||
this.konva.layer.visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.layer.visible(true);
|
||||
this.group.opacity(this.layerState.opacity);
|
||||
this.konva.layer.visible(true);
|
||||
this.konva.group.opacity(this.layerState.opacity);
|
||||
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||
const selectedTool = this.manager.stateApi.getToolState().selected;
|
||||
|
||||
if (this.objects.size === 0) {
|
||||
// If the layer is totally empty, reset the cache and bail out.
|
||||
this.layer.listening(false);
|
||||
this.transformer.nodes([]);
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
this.konva.layer.listening(false);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
} else if (isSelected && selectedTool === 'move') {
|
||||
// When the layer is selected and being moved, we should always cache it.
|
||||
// We should update the cache if we drew to the layer.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
// Activate the transformer
|
||||
this.layer.listening(true);
|
||||
this.transformer.nodes([this.group]);
|
||||
this.transformer.forceUpdate();
|
||||
this.konva.layer.listening(true);
|
||||
this.konva.transformer.nodes([this.konva.group]);
|
||||
this.konva.transformer.forceUpdate();
|
||||
} 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.
|
||||
this.layer.listening(false);
|
||||
this.konva.layer.listening(false);
|
||||
// The transformer also does not need to be active.
|
||||
this.transformer.nodes([]);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (isDrawingTool(selectedTool)) {
|
||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||
// should never be cached.
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
}
|
||||
} else if (!isSelected) {
|
||||
// Unselected layers should not be listening
|
||||
this.layer.listening(false);
|
||||
this.konva.layer.listening(false);
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,17 +97,17 @@ export class CanvasManager {
|
||||
this.stage.add(this.preview.layer);
|
||||
|
||||
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.stage.add(this.inpaintMask.layer);
|
||||
this.stage.add(this.inpaintMask.konva.layer);
|
||||
|
||||
this.layers = new Map();
|
||||
this.regions = new Map();
|
||||
this.controlAdapters = new Map();
|
||||
|
||||
this.initialImage = new CanvasInitialImage(this.stateApi.getInitialImageState(), this);
|
||||
this.stage.add(this.initialImage.layer);
|
||||
this.stage.add(this.initialImage.konva.layer);
|
||||
}
|
||||
|
||||
async renderInitialImage() {
|
||||
@ -129,7 +129,7 @@ export class CanvasManager {
|
||||
if (!adapter) {
|
||||
adapter = new CanvasLayer(entity, this);
|
||||
this.layers.set(adapter.id, adapter);
|
||||
this.stage.add(adapter.layer);
|
||||
this.stage.add(adapter.konva.layer);
|
||||
}
|
||||
await adapter.render(entity);
|
||||
}
|
||||
@ -151,7 +151,7 @@ export class CanvasManager {
|
||||
if (!adapter) {
|
||||
adapter = new CanvasRegion(entity, this);
|
||||
this.regions.set(adapter.id, adapter);
|
||||
this.stage.add(adapter.layer);
|
||||
this.stage.add(adapter.konva.layer);
|
||||
}
|
||||
await adapter.render(entity);
|
||||
}
|
||||
@ -181,7 +181,7 @@ export class CanvasManager {
|
||||
if (!adapter) {
|
||||
adapter = new CanvasControlAdapter(entity, this);
|
||||
this.controlAdapters.set(adapter.id, adapter);
|
||||
this.stage.add(adapter.layer);
|
||||
this.stage.add(adapter.konva.layer);
|
||||
}
|
||||
await adapter.render(entity);
|
||||
}
|
||||
@ -193,18 +193,18 @@ export class CanvasManager {
|
||||
const controlAdapters = getControlAdaptersState().entities;
|
||||
const regions = getRegionsState().entities;
|
||||
let zIndex = 0;
|
||||
this.background.layer.zIndex(++zIndex);
|
||||
this.initialImage.layer.zIndex(++zIndex);
|
||||
this.background.konva.layer.zIndex(++zIndex);
|
||||
this.initialImage.konva.layer.zIndex(++zIndex);
|
||||
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) {
|
||||
this.controlAdapters.get(ca.id)?.layer.zIndex(++zIndex);
|
||||
this.controlAdapters.get(ca.id)?.konva.layer.zIndex(++zIndex);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -21,15 +21,15 @@ export class CanvasPreview {
|
||||
this.layer = new Konva.Layer({ listening: true, imageSmoothingEnabled: false });
|
||||
|
||||
this.stagingArea = stagingArea;
|
||||
this.layer.add(this.stagingArea.group);
|
||||
this.layer.add(this.stagingArea.konva.group);
|
||||
|
||||
this.bbox = bbox;
|
||||
this.layer.add(this.bbox.group);
|
||||
this.layer.add(this.bbox.konva.group);
|
||||
|
||||
this.tool = tool;
|
||||
this.layer.add(this.tool.group);
|
||||
this.layer.add(this.tool.konva.group);
|
||||
|
||||
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;
|
||||
progressImageId: string | null;
|
||||
konvaImageGroup: Konva.Group;
|
||||
konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
image: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
|
||||
};
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
|
||||
constructor(arg: { id: string }) {
|
||||
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.progressImageId = null;
|
||||
this.konvaImage = null;
|
||||
this.isLoading = false;
|
||||
this.isError = false;
|
||||
}
|
||||
@ -37,8 +41,8 @@ export class CanvasProgressImage {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const imageEl = await loadImage(dataURL);
|
||||
if (this.konvaImage) {
|
||||
this.konvaImage.setAttrs({
|
||||
if (this.konva.image) {
|
||||
this.konva.image.setAttrs({
|
||||
image: imageEl,
|
||||
x,
|
||||
y,
|
||||
@ -46,7 +50,7 @@ export class CanvasProgressImage {
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
this.konvaImage = new Konva.Image({
|
||||
this.konva.image = new Konva.Image({
|
||||
name: CanvasProgressImage.IMAGE_NAME,
|
||||
listening: false,
|
||||
image: imageEl,
|
||||
@ -55,7 +59,7 @@ export class CanvasProgressImage {
|
||||
width,
|
||||
height,
|
||||
});
|
||||
this.konvaImageGroup.add(this.konvaImage);
|
||||
this.konva.group.add(this.konva.image);
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.id = progressImageId;
|
||||
@ -65,6 +69,6 @@ export class CanvasProgressImage {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.konvaImageGroup.destroy();
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,19 @@ export class CanvasProgressPreview {
|
||||
static NAME_PREFIX = 'progress-preview';
|
||||
static GROUP_NAME = `${CanvasProgressPreview.NAME_PREFIX}_group`;
|
||||
|
||||
group: Konva.Group;
|
||||
progressImage: CanvasProgressImage;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
progressImage: CanvasProgressImage;
|
||||
};
|
||||
manager: CanvasManager;
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.group = new Konva.Group({ name: CanvasProgressPreview.GROUP_NAME, listening: false });
|
||||
this.progressImage = new CanvasProgressImage({ id: 'progress-image' });
|
||||
this.group.add(this.progressImage.konvaImageGroup);
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasProgressPreview.GROUP_NAME, listening: false }),
|
||||
progressImage: new CanvasProgressImage({ id: 'progress-image' }),
|
||||
};
|
||||
this.konva.group.add(this.konva.progressImage.konva.group);
|
||||
}
|
||||
|
||||
async render(lastProgressEvent: InvocationDenoiseProgressEvent | null) {
|
||||
@ -28,15 +32,15 @@ export class CanvasProgressPreview {
|
||||
const { x, y, width, height } = bboxRect;
|
||||
const progressImageId = `${invocation.id}_${step}`;
|
||||
if (
|
||||
!this.progressImage.isLoading &&
|
||||
!this.progressImage.isError &&
|
||||
this.progressImage.progressImageId !== progressImageId
|
||||
!this.konva.progressImage.isLoading &&
|
||||
!this.konva.progressImage.isError &&
|
||||
this.konva.progressImage.progressImageId !== progressImageId
|
||||
) {
|
||||
await this.progressImage.updateImageSource(progressImageId, dataURL, x, y, width, height);
|
||||
this.progressImage.konvaImageGroup.visible(true);
|
||||
await this.konva.progressImage.updateImageSource(progressImageId, dataURL, x, y, width, height);
|
||||
this.konva.progressImage.konva.group.visible(true);
|
||||
}
|
||||
} else {
|
||||
this.progressImage.konvaImageGroup.visible(false);
|
||||
this.konva.progressImage.konva.group.visible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,33 +4,40 @@ import Konva from 'konva';
|
||||
|
||||
export class CanvasRect {
|
||||
static NAME_PREFIX = 'canvas-rect';
|
||||
static GROUP_NAME = `${CanvasRect.NAME_PREFIX}_group`;
|
||||
static RECT_NAME = `${CanvasRect.NAME_PREFIX}_rect`;
|
||||
|
||||
id: string;
|
||||
konvaRect: Konva.Rect;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
rect: Konva.Rect;
|
||||
};
|
||||
lastRectShape: RectShape;
|
||||
|
||||
constructor(rectShape: RectShape) {
|
||||
const { id, x, y, width, height } = rectShape;
|
||||
this.id = id;
|
||||
const konvaRect = new Konva.Rect({
|
||||
name: CanvasRect.RECT_NAME,
|
||||
id,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
listening: false,
|
||||
fill: rgbaColorToString(rectShape.color),
|
||||
});
|
||||
this.konvaRect = konvaRect;
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasRect.GROUP_NAME, listening: false }),
|
||||
rect: new Konva.Rect({
|
||||
name: CanvasRect.RECT_NAME,
|
||||
id,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
listening: false,
|
||||
fill: rgbaColorToString(rectShape.color),
|
||||
}),
|
||||
};
|
||||
this.konva.group.add(this.konva.rect);
|
||||
this.lastRectShape = rectShape;
|
||||
}
|
||||
|
||||
update(rectShape: RectShape, force?: boolean): boolean {
|
||||
if (this.lastRectShape !== rectShape || force) {
|
||||
const { x, y, width, height, color } = rectShape;
|
||||
this.konvaRect.setAttrs({
|
||||
this.konva.rect.setAttrs({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
@ -45,6 +52,6 @@ export class CanvasRect {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.konvaRect.destroy();
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -23,54 +23,63 @@ export class CanvasRegion {
|
||||
|
||||
id: string;
|
||||
manager: CanvasManager;
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectsGroup: Konva.Group;
|
||||
compositingRect: Konva.Rect;
|
||||
transformer: Konva.Transformer;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectGroup: Konva.Group;
|
||||
compositingRect: Konva.Rect;
|
||||
transformer: Konva.Transformer;
|
||||
};
|
||||
|
||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
|
||||
|
||||
constructor(entity: RegionEntity, manager: CanvasManager) {
|
||||
this.id = entity.id;
|
||||
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({
|
||||
name: CanvasRegion.TRANSFORMER_NAME,
|
||||
shouldOverdrawWholeArea: true,
|
||||
draggable: true,
|
||||
dragDistance: 0,
|
||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||
rotateEnabled: false,
|
||||
flipEnabled: false,
|
||||
});
|
||||
this.transformer.on('transformend', () => {
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ name: CanvasRegion.LAYER_NAME, listening: false }),
|
||||
group: new Konva.Group({ name: CanvasRegion.GROUP_NAME, listening: false }),
|
||||
objectGroup: new Konva.Group({ name: CanvasRegion.OBJECT_GROUP_NAME, listening: false }),
|
||||
transformer: new Konva.Transformer({
|
||||
name: CanvasRegion.TRANSFORMER_NAME,
|
||||
shouldOverdrawWholeArea: true,
|
||||
draggable: true,
|
||||
dragDistance: 0,
|
||||
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(
|
||||
{ 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'
|
||||
);
|
||||
});
|
||||
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() } },
|
||||
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||
'regional_guidance'
|
||||
);
|
||||
});
|
||||
this.layer.add(this.transformer);
|
||||
|
||||
this.compositingRect = new Konva.Rect({ name: CanvasRegion.COMPOSITING_RECT_NAME, listening: false });
|
||||
this.group.add(this.compositingRect);
|
||||
this.konva.layer.add(this.konva.transformer);
|
||||
this.konva.group.add(this.konva.compositingRect);
|
||||
this.objects = new Map();
|
||||
this.drawingBuffer = null;
|
||||
this.regionState = entity;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.layer.destroy();
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
|
||||
getDrawingBuffer() {
|
||||
@ -109,7 +118,7 @@ export class CanvasRegion {
|
||||
this.regionState = regionState;
|
||||
|
||||
// Update the layer's position and listening state
|
||||
this.group.setAttrs({
|
||||
this.konva.group.setAttrs({
|
||||
x: regionState.position.x,
|
||||
y: regionState.position.y,
|
||||
scaleX: 1,
|
||||
@ -151,7 +160,7 @@ export class CanvasRegion {
|
||||
if (!brushLine) {
|
||||
brushLine = new CanvasBrushLine(obj);
|
||||
this.objects.set(brushLine.id, brushLine);
|
||||
this.objectsGroup.add(brushLine.konvaLineGroup);
|
||||
this.konva.objectGroup.add(brushLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (brushLine.update(obj, force)) {
|
||||
@ -165,7 +174,7 @@ export class CanvasRegion {
|
||||
if (!eraserLine) {
|
||||
eraserLine = new CanvasEraserLine(obj);
|
||||
this.objects.set(eraserLine.id, eraserLine);
|
||||
this.objectsGroup.add(eraserLine.konvaLineGroup);
|
||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (eraserLine.update(obj, force)) {
|
||||
@ -179,7 +188,7 @@ export class CanvasRegion {
|
||||
if (!rect) {
|
||||
rect = new CanvasRect(obj);
|
||||
this.objects.set(rect.id, rect);
|
||||
this.objectsGroup.add(rect.konvaRect);
|
||||
this.konva.objectGroup.add(rect.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (rect.update(obj, force)) {
|
||||
@ -192,18 +201,18 @@ export class CanvasRegion {
|
||||
}
|
||||
|
||||
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
|
||||
this.group.opacity(1);
|
||||
this.konva.group.opacity(1);
|
||||
|
||||
if (didDraw) {
|
||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||
const rgbColor = rgbColorToString(this.regionState.fill);
|
||||
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
|
||||
...getNodeBboxFast(this.objectsGroup),
|
||||
...getNodeBboxFast(this.konva.objectGroup),
|
||||
fill: rgbColor,
|
||||
opacity: maskOpacity,
|
||||
// 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 the layer is totally empty, reset the cache and bail out.
|
||||
this.layer.listening(false);
|
||||
this.transformer.nodes([]);
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
this.konva.layer.listening(false);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -229,32 +238,32 @@ export class CanvasRegion {
|
||||
if (isSelected && selectedTool === 'move') {
|
||||
// When the layer is selected and being moved, we should always cache it.
|
||||
// We should update the cache if we drew to the layer.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
// Activate the transformer
|
||||
this.layer.listening(true);
|
||||
this.transformer.nodes([this.group]);
|
||||
this.transformer.forceUpdate();
|
||||
this.konva.layer.listening(true);
|
||||
this.konva.transformer.nodes([this.konva.group]);
|
||||
this.konva.transformer.forceUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelected && selectedTool !== 'move') {
|
||||
// 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.
|
||||
this.transformer.nodes([]);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (isDrawingTool(selectedTool)) {
|
||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||
// should never be cached.
|
||||
if (this.group.isCached()) {
|
||||
this.group.clearCache();
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -262,12 +271,12 @@ export class CanvasRegion {
|
||||
|
||||
if (!isSelected) {
|
||||
// Unselected layers should not be listening
|
||||
this.layer.listening(false);
|
||||
this.konva.layer.listening(false);
|
||||
// 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.
|
||||
if (!this.group.isCached() || didDraw) {
|
||||
this.group.cache();
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -4,14 +4,18 @@ import type { StagingAreaImage } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
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;
|
||||
selectedImage: StagingAreaImage | null;
|
||||
manager: CanvasManager;
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
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.selectedImage = null;
|
||||
}
|
||||
@ -42,20 +46,20 @@ export class CanvasStagingArea {
|
||||
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) {
|
||||
this.image.konvaImage?.width(imageDTO.width);
|
||||
this.image.konvaImage?.height(imageDTO.height);
|
||||
this.image.konvaImageGroup.x(bboxRect.x + offsetX);
|
||||
this.image.konvaImageGroup.y(bboxRect.y + offsetY);
|
||||
this.image.image?.width(imageDTO.width);
|
||||
this.image.image?.height(imageDTO.height);
|
||||
this.image.konva.group.x(bboxRect.x + offsetX);
|
||||
this.image.konva.group.y(bboxRect.y + offsetY);
|
||||
await this.image.updateImageSource(imageDTO.image_name);
|
||||
this.manager.stateApi.resetLastProgressEvent();
|
||||
}
|
||||
this.image.konvaImageGroup.visible(shouldShowStagedImage);
|
||||
this.image.konva.group.visible(shouldShowStagedImage);
|
||||
} 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`;
|
||||
|
||||
manager: CanvasManager;
|
||||
group: Konva.Group;
|
||||
brush: {
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
fillCircle: Konva.Circle;
|
||||
innerBorderCircle: Konva.Circle;
|
||||
outerBorderCircle: Konva.Circle;
|
||||
};
|
||||
eraser: {
|
||||
group: Konva.Group;
|
||||
fillCircle: Konva.Circle;
|
||||
innerBorderCircle: Konva.Circle;
|
||||
outerBorderCircle: Konva.Circle;
|
||||
brush: {
|
||||
group: Konva.Group;
|
||||
fillCircle: Konva.Circle;
|
||||
innerBorderCircle: Konva.Circle;
|
||||
outerBorderCircle: Konva.Circle;
|
||||
};
|
||||
eraser: {
|
||||
group: Konva.Group;
|
||||
fillCircle: Konva.Circle;
|
||||
innerBorderCircle: Konva.Circle;
|
||||
outerBorderCircle: Konva.Circle;
|
||||
};
|
||||
};
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.group = new Konva.Group({ name: CanvasTool.GROUP_NAME });
|
||||
|
||||
// Create the brush preview group & circles
|
||||
this.brush = {
|
||||
group: new Konva.Group({ name: CanvasTool.BRUSH_GROUP_NAME }),
|
||||
fillCircle: new Konva.Circle({
|
||||
name: CanvasTool.BRUSH_FILL_CIRCLE_NAME,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
innerBorderCircle: new Konva.Circle({
|
||||
name: CanvasTool.BRUSH_INNER_BORDER_CIRCLE_NAME,
|
||||
listening: false,
|
||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||
strokeEnabled: true,
|
||||
}),
|
||||
outerBorderCircle: new Konva.Circle({
|
||||
name: CanvasTool.BRUSH_OUTER_BORDER_CIRCLE_NAME,
|
||||
listening: false,
|
||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||
strokeEnabled: true,
|
||||
}),
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasTool.GROUP_NAME }),
|
||||
brush: {
|
||||
group: new Konva.Group({ name: CanvasTool.BRUSH_GROUP_NAME }),
|
||||
fillCircle: new Konva.Circle({
|
||||
name: CanvasTool.BRUSH_FILL_CIRCLE_NAME,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
innerBorderCircle: new Konva.Circle({
|
||||
name: CanvasTool.BRUSH_INNER_BORDER_CIRCLE_NAME,
|
||||
listening: false,
|
||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||
strokeEnabled: true,
|
||||
}),
|
||||
outerBorderCircle: new Konva.Circle({
|
||||
name: CanvasTool.BRUSH_OUTER_BORDER_CIRCLE_NAME,
|
||||
listening: false,
|
||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH,
|
||||
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.brush.group.add(this.brush.innerBorderCircle);
|
||||
this.brush.group.add(this.brush.outerBorderCircle);
|
||||
this.group.add(this.brush.group);
|
||||
this.konva.brush.group.add(this.konva.brush.fillCircle);
|
||||
this.konva.brush.group.add(this.konva.brush.innerBorderCircle);
|
||||
this.konva.brush.group.add(this.konva.brush.outerBorderCircle);
|
||||
this.konva.group.add(this.konva.brush.group);
|
||||
|
||||
this.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.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);
|
||||
this.konva.eraser.group.add(this.konva.eraser.fillCircle);
|
||||
this.konva.eraser.group.add(this.konva.eraser.innerBorderCircle);
|
||||
this.konva.eraser.group.add(this.konva.eraser.outerBorderCircle);
|
||||
this.konva.group.add(this.konva.eraser.group);
|
||||
|
||||
// // Create the rect preview - this is a rectangle drawn from the last mouse down position to the current cursor position
|
||||
// this.rect = {
|
||||
@ -110,7 +112,7 @@ export class CanvasTool {
|
||||
// }),
|
||||
// };
|
||||
// this.rect.group.add(this.rect.fillRect);
|
||||
// this.group.add(this.rect.group);
|
||||
// this.konva.group.add(this.rect.group);
|
||||
}
|
||||
|
||||
scaleTool = () => {
|
||||
@ -118,15 +120,15 @@ export class CanvasTool {
|
||||
const scale = this.manager.stage.scaleX();
|
||||
|
||||
const brushRadius = toolState.brush.width / 2;
|
||||
this.brush.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
||||
this.brush.outerBorderCircle.setAttrs({
|
||||
this.konva.brush.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
||||
this.konva.brush.outerBorderCircle.setAttrs({
|
||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||
radius: brushRadius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||
});
|
||||
|
||||
const eraserRadius = toolState.eraser.width / 2;
|
||||
this.eraser.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
||||
this.eraser.outerBorderCircle.setAttrs({
|
||||
this.konva.eraser.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale);
|
||||
this.konva.eraser.outerBorderCircle.setAttrs({
|
||||
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||
radius: eraserRadius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||
});
|
||||
@ -175,16 +177,16 @@ export class CanvasTool {
|
||||
|
||||
if (!cursorPos || renderedEntityCount === 0 || !isDrawableEntity) {
|
||||
// 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 {
|
||||
this.group.visible(true);
|
||||
this.konva.group.visible(true);
|
||||
|
||||
// No need to render the brush preview if the cursor position or color is missing
|
||||
if (cursorPos && tool === 'brush') {
|
||||
const scale = stage.scaleX();
|
||||
// Update the fill circle
|
||||
const radius = toolState.brush.width / 2;
|
||||
this.brush.fillCircle.setAttrs({
|
||||
this.konva.brush.fillCircle.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
radius,
|
||||
@ -192,10 +194,10 @@ export class CanvasTool {
|
||||
});
|
||||
|
||||
// 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
|
||||
this.brush.outerBorderCircle.setAttrs({
|
||||
this.konva.brush.outerBorderCircle.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||
@ -203,14 +205,14 @@ export class CanvasTool {
|
||||
|
||||
this.scaleTool();
|
||||
|
||||
this.brush.group.visible(true);
|
||||
this.eraser.group.visible(false);
|
||||
this.konva.brush.group.visible(true);
|
||||
this.konva.eraser.group.visible(false);
|
||||
// this.rect.group.visible(false);
|
||||
} else if (cursorPos && tool === 'eraser') {
|
||||
const scale = stage.scaleX();
|
||||
// Update the fill circle
|
||||
const radius = toolState.eraser.width / 2;
|
||||
this.eraser.fillCircle.setAttrs({
|
||||
this.konva.eraser.fillCircle.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
radius,
|
||||
@ -218,10 +220,10 @@ export class CanvasTool {
|
||||
});
|
||||
|
||||
// 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
|
||||
this.eraser.outerBorderCircle.setAttrs({
|
||||
this.konva.eraser.outerBorderCircle.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
|
||||
@ -229,8 +231,8 @@ export class CanvasTool {
|
||||
|
||||
this.scaleTool();
|
||||
|
||||
this.brush.group.visible(false);
|
||||
this.eraser.group.visible(true);
|
||||
this.konva.brush.group.visible(false);
|
||||
this.konva.eraser.group.visible(true);
|
||||
// this.rect.group.visible(false);
|
||||
// } else if (cursorPos && lastMouseDownPos && tool === 'rect') {
|
||||
// this.rect.fillRect.setAttrs({
|
||||
@ -241,12 +243,12 @@ export class CanvasTool {
|
||||
// fill: rgbaColorToString(currentFill),
|
||||
// visible: true,
|
||||
// });
|
||||
// this.brush.group.visible(false);
|
||||
// this.eraser.group.visible(false);
|
||||
// this.konva.brush.group.visible(false);
|
||||
// this.konva.eraser.group.visible(false);
|
||||
// this.rect.group.visible(true);
|
||||
} else {
|
||||
this.brush.group.visible(false);
|
||||
this.eraser.group.visible(false);
|
||||
this.konva.brush.group.visible(false);
|
||||
this.konva.eraser.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 {
|
||||
const { manager } = arg;
|
||||
const layerClone = manager.inpaintMask.layer.clone();
|
||||
const objectGroupClone = manager.inpaintMask.group.clone();
|
||||
const layerClone = manager.inpaintMask.konva.layer.clone();
|
||||
const objectGroupClone = manager.inpaintMask.konva.group.clone();
|
||||
|
||||
layerClone.destroyChildren();
|
||||
layerClone.add(objectGroupClone);
|
||||
@ -287,8 +287,8 @@ export function getRegionMaskLayerClone(arg: { manager: CanvasManager; id: strin
|
||||
const canvasRegion = manager.regions.get(id);
|
||||
assert(canvasRegion, `Canvas region with id ${id} not found`);
|
||||
|
||||
const layerClone = canvasRegion.layer.clone();
|
||||
const objectGroupClone = canvasRegion.group.clone();
|
||||
const layerClone = canvasRegion.konva.layer.clone();
|
||||
const objectGroupClone = canvasRegion.konva.group.clone();
|
||||
|
||||
layerClone.destroyChildren();
|
||||
layerClone.add(objectGroupClone);
|
||||
@ -305,8 +305,8 @@ export function getControlAdapterLayerClone(arg: { manager: CanvasManager; id: s
|
||||
const controlAdapter = manager.controlAdapters.get(id);
|
||||
assert(controlAdapter, `Canvas region with id ${id} not found`);
|
||||
|
||||
const controlAdapterClone = controlAdapter.layer.clone();
|
||||
const objectGroupClone = controlAdapter.group.clone();
|
||||
const controlAdapterClone = controlAdapter.konva.layer.clone();
|
||||
const objectGroupClone = controlAdapter.konva.group.clone();
|
||||
|
||||
controlAdapterClone.destroyChildren();
|
||||
controlAdapterClone.add(objectGroupClone);
|
||||
@ -322,8 +322,8 @@ export function getInitialImageLayerClone(arg: { manager: CanvasManager }): Konv
|
||||
|
||||
const initialImage = manager.initialImage;
|
||||
|
||||
const initialImageClone = initialImage.layer.clone();
|
||||
const objectGroupClone = initialImage.group.clone();
|
||||
const initialImageClone = initialImage.konva.layer.clone();
|
||||
const objectGroupClone = initialImage.konva.group.clone();
|
||||
|
||||
initialImageClone.destroyChildren();
|
||||
initialImageClone.add(objectGroupClone);
|
||||
|
Loading…
Reference in New Issue
Block a user