mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-05 19:15:28 -05:00
feat(ui): split canvas tool previews into modules
This commit is contained in:
@@ -15,9 +15,8 @@ import {
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants';
|
||||
import { snapToNearest } from 'features/controlLayers/konva/util';
|
||||
import { clamp, round } from 'lodash-es';
|
||||
import { round } from 'lodash-es';
|
||||
import { computed } from 'nanostores';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
@@ -102,7 +101,7 @@ export const CanvasScale = memo(() => {
|
||||
setLocalScale(100);
|
||||
return;
|
||||
}
|
||||
canvasManager.stage.setScale(clamp(localScale / 100, MIN_CANVAS_SCALE, MAX_CANVAS_SCALE));
|
||||
canvasManager.stage.setScale(localScale / 100);
|
||||
}, [canvasManager, localScale]);
|
||||
|
||||
const onChangeNumberInput = useCallback((valueAsString: string, valueAsNumber: number) => {
|
||||
@@ -130,8 +129,8 @@ export const CanvasScale = memo(() => {
|
||||
<NumberInput
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
min={MIN_CANVAS_SCALE * 100}
|
||||
max={MAX_CANVAS_SCALE * 100}
|
||||
min={canvasManager.stage.config.MIN_SCALE * 100}
|
||||
max={canvasManager.stage.config.MAX_SCALE * 100}
|
||||
value={localScale}
|
||||
onChange={onChangeNumberInput}
|
||||
onBlur={onBlur}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useAppStore } from 'app/store/nanostores/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
|
||||
import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { selectCanvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import Konva from 'konva';
|
||||
@@ -82,7 +82,7 @@ export const StageComponent = memo(() => {
|
||||
<Flex
|
||||
position="absolute"
|
||||
borderRadius="base"
|
||||
bgImage={TRANSPARENCY_CHECKER_PATTERN}
|
||||
bgImage={TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL}
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { useEntityAdapter } from 'features/controlLayers/contexts/EntityAdapterContext';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
|
||||
import { selectCanvasSlice, selectEntity } from 'features/controlLayers/store/selectors';
|
||||
import { memo, useEffect, useMemo, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -88,7 +88,7 @@ export const CanvasEntityPreviewImage = memo(() => {
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
bgImage={TRANSPARENCY_CHECKER_PATTERN}
|
||||
bgImage={TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL}
|
||||
bgSize="5px"
|
||||
opacity={0.1}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleABC } from 'features/controlLayers/konva/CanvasModuleABC';
|
||||
import type { CanvasToolModule } from 'features/controlLayers/konva/CanvasToolModule';
|
||||
import { alignCoordForTool, getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
type BrushToolPreviewConfig = {
|
||||
/**
|
||||
* The inner border color for the brush tool preview.
|
||||
*/
|
||||
BORDER_INNER_COLOR: string;
|
||||
/**
|
||||
* The outer border color for the brush tool preview.
|
||||
*/
|
||||
BORDER_OUTER_COLOR: string;
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG: BrushToolPreviewConfig = {
|
||||
BORDER_INNER_COLOR: 'rgba(0,0,0,1)',
|
||||
BORDER_OUTER_COLOR: 'rgba(255,255,255,0.8)',
|
||||
};
|
||||
|
||||
export class CanvasBrushToolPreview extends CanvasModuleABC {
|
||||
readonly type = 'brush_tool_preview';
|
||||
id: string;
|
||||
path: string[];
|
||||
parent: CanvasToolModule;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
config: BrushToolPreviewConfig;
|
||||
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
fillCircle: Konva.Circle;
|
||||
innerBorder: Konva.Ring;
|
||||
outerBorder: Konva.Ring;
|
||||
};
|
||||
|
||||
constructor(parent: CanvasToolModule, config?: Partial<BrushToolPreviewConfig>) {
|
||||
super();
|
||||
this.id = getPrefixedId(this.type);
|
||||
this.parent = parent;
|
||||
this.manager = this.parent.manager;
|
||||
this.path = this.parent.path.concat(this.id);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
this.log.debug('Creating brush tool preview module');
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: `${this.type}:brush_group`, listening: false }),
|
||||
fillCircle: new Konva.Circle({
|
||||
name: `${this.type}:brush_fill_circle`,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
innerBorder: new Konva.Ring({
|
||||
name: `${this.type}:brush_inner_border_ring`,
|
||||
listening: false,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: this.config.BORDER_INNER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
outerBorder: new Konva.Ring({
|
||||
name: `${this.type}:brush_outer_border_ring`,
|
||||
listening: false,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: this.config.BORDER_OUTER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
};
|
||||
this.konva.group.add(this.konva.fillCircle, this.konva.innerBorder, this.konva.outerBorder);
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const cursorPos = this.manager.stateApi.$lastCursorPos.get();
|
||||
|
||||
if (!cursorPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
const brushPreviewFill = this.manager.stateApi.getBrushPreviewFill();
|
||||
const alignedCursorPos = alignCoordForTool(cursorPos, toolState.brush.width);
|
||||
const radius = toolState.brush.width / 2;
|
||||
|
||||
// The circle is scaled
|
||||
this.konva.fillCircle.setAttrs({
|
||||
x: alignedCursorPos.x,
|
||||
y: alignedCursorPos.y,
|
||||
radius,
|
||||
fill: rgbaColorToString(brushPreviewFill),
|
||||
});
|
||||
|
||||
// But the borders are in screen-pixels
|
||||
const onePixel = this.manager.stage.getScaledPixels(1);
|
||||
const twoPixels = this.manager.stage.getScaledPixels(2);
|
||||
|
||||
this.konva.innerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius,
|
||||
outerRadius: radius + onePixel,
|
||||
});
|
||||
this.konva.outerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius + onePixel,
|
||||
outerRadius: radius + twoPixels,
|
||||
});
|
||||
};
|
||||
|
||||
setVisibility = (visible: boolean) => {
|
||||
this.konva.group.visible(visible);
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
config: this.config,
|
||||
};
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying brush tool preview module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.konva.group.destroy();
|
||||
};
|
||||
|
||||
getLoggingContext = () => {
|
||||
return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleABC } from 'features/controlLayers/konva/CanvasModuleABC';
|
||||
import type { CanvasToolModule } from 'features/controlLayers/konva/CanvasToolModule';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
type ColorPickerToolConfig = {
|
||||
/**
|
||||
* The inner radius of the ring.
|
||||
*/
|
||||
RING_INNER_RADIUS: number;
|
||||
/**
|
||||
* The outer radius of the ring.
|
||||
*/
|
||||
RING_OUTER_RADIUS: number;
|
||||
/**
|
||||
* The inner border color of the outside edge of ring.
|
||||
*/
|
||||
RING_BORDER_INNER_COLOR: string;
|
||||
/**
|
||||
* The outer border color of the outside edge of ring.
|
||||
*/
|
||||
RING_BORDER_OUTER_COLOR: string;
|
||||
|
||||
/**
|
||||
* The radius of the space between the center of the ring and start of the crosshair lines.
|
||||
*/
|
||||
CROSSHAIR_INNER_RADIUS: number;
|
||||
/**
|
||||
* The length of the crosshair lines.
|
||||
*/
|
||||
CROSSHAIR_LINE_LENGTH: number;
|
||||
/**
|
||||
* The thickness of the crosshair lines.
|
||||
*/
|
||||
CROSSHAIR_LINE_THICKNESS: number;
|
||||
/**
|
||||
* The color of the crosshair lines.
|
||||
*/
|
||||
CROSSHAIR_LINE_COLOR: string;
|
||||
/**
|
||||
* The thickness of the crosshair lines borders
|
||||
*/
|
||||
CROSSHAIR_LINE_BORDER_THICKNESS: number;
|
||||
/**
|
||||
* The color of the crosshair line borders.
|
||||
*/
|
||||
CROSSHAIR_BORDER_COLOR: string;
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG: ColorPickerToolConfig = {
|
||||
RING_INNER_RADIUS: 25,
|
||||
RING_OUTER_RADIUS: 35,
|
||||
RING_BORDER_INNER_COLOR: 'rgba(0,0,0,1)',
|
||||
RING_BORDER_OUTER_COLOR: 'rgba(255,255,255,0.8)',
|
||||
CROSSHAIR_INNER_RADIUS: 5,
|
||||
CROSSHAIR_LINE_THICKNESS: 1.5,
|
||||
CROSSHAIR_LINE_BORDER_THICKNESS: 0.75,
|
||||
CROSSHAIR_LINE_LENGTH: 10,
|
||||
CROSSHAIR_LINE_COLOR: 'rgba(0,0,0,1)',
|
||||
CROSSHAIR_BORDER_COLOR: 'rgba(255,255,255,0.8)',
|
||||
};
|
||||
|
||||
export class CanvasColorPickerToolPreview extends CanvasModuleABC {
|
||||
readonly type = 'color_picker_tool_preview';
|
||||
|
||||
id: string;
|
||||
path: string[];
|
||||
parent: CanvasToolModule;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
config: ColorPickerToolConfig;
|
||||
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
ringNewColor: Konva.Ring;
|
||||
ringOldColor: Konva.Arc;
|
||||
ringInnerBorder: Konva.Ring;
|
||||
ringOuterBorder: Konva.Ring;
|
||||
crosshairNorthInner: Konva.Line;
|
||||
crosshairNorthOuter: Konva.Line;
|
||||
crosshairEastInner: Konva.Line;
|
||||
crosshairEastOuter: Konva.Line;
|
||||
crosshairSouthInner: Konva.Line;
|
||||
crosshairSouthOuter: Konva.Line;
|
||||
crosshairWestInner: Konva.Line;
|
||||
crosshairWestOuter: Konva.Line;
|
||||
};
|
||||
|
||||
constructor(parent: CanvasToolModule, config?: Partial<ColorPickerToolConfig>) {
|
||||
super();
|
||||
this.id = getPrefixedId(this.type);
|
||||
this.parent = parent;
|
||||
this.manager = this.parent.manager;
|
||||
this.path = this.parent.path.concat(this.id);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
this.log.debug('Creating color picker tool preview module');
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: `${this.type}:color_picker_group`, listening: false }),
|
||||
ringNewColor: new Konva.Ring({
|
||||
name: `${this.type}:color_picker_new_color_ring`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
ringOldColor: new Konva.Arc({
|
||||
name: `${this.type}:color_picker_old_color_arc`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
angle: 180,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
ringInnerBorder: new Konva.Ring({
|
||||
name: `${this.type}:color_picker_inner_border_ring`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: this.config.RING_BORDER_INNER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
ringOuterBorder: new Konva.Ring({
|
||||
name: `${this.type}:color_picker_outer_border_ring`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: this.config.RING_BORDER_OUTER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
crosshairNorthInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_north1_line`,
|
||||
stroke: this.config.CROSSHAIR_LINE_COLOR,
|
||||
}),
|
||||
crosshairNorthOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_north2_line`,
|
||||
stroke: this.config.CROSSHAIR_BORDER_COLOR,
|
||||
}),
|
||||
crosshairEastInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_east1_line`,
|
||||
stroke: this.config.CROSSHAIR_LINE_COLOR,
|
||||
}),
|
||||
crosshairEastOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_east2_line`,
|
||||
stroke: this.config.CROSSHAIR_BORDER_COLOR,
|
||||
}),
|
||||
crosshairSouthInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_south1_line`,
|
||||
stroke: this.config.CROSSHAIR_LINE_COLOR,
|
||||
}),
|
||||
crosshairSouthOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_south2_line`,
|
||||
stroke: this.config.CROSSHAIR_BORDER_COLOR,
|
||||
}),
|
||||
crosshairWestInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_west1_line`,
|
||||
stroke: this.config.CROSSHAIR_LINE_COLOR,
|
||||
}),
|
||||
crosshairWestOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_west2_line`,
|
||||
stroke: this.config.CROSSHAIR_BORDER_COLOR,
|
||||
}),
|
||||
};
|
||||
|
||||
this.konva.group.add(
|
||||
this.konva.ringNewColor,
|
||||
this.konva.ringOldColor,
|
||||
this.konva.ringInnerBorder,
|
||||
this.konva.ringOuterBorder,
|
||||
this.konva.crosshairNorthOuter,
|
||||
this.konva.crosshairNorthInner,
|
||||
this.konva.crosshairEastOuter,
|
||||
this.konva.crosshairEastInner,
|
||||
this.konva.crosshairSouthOuter,
|
||||
this.konva.crosshairSouthInner,
|
||||
this.konva.crosshairWestOuter,
|
||||
this.konva.crosshairWestInner
|
||||
);
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const cursorPos = this.manager.stateApi.$lastCursorPos.get();
|
||||
|
||||
if (!cursorPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
const colorUnderCursor = this.manager.stateApi.$colorUnderCursor.get();
|
||||
const colorPickerInnerRadius = this.manager.stage.getScaledPixels(this.config.RING_INNER_RADIUS);
|
||||
const colorPickerOuterRadius = this.manager.stage.getScaledPixels(this.config.RING_OUTER_RADIUS);
|
||||
const onePixel = this.manager.stage.getScaledPixels(1);
|
||||
const twoPixels = this.manager.stage.getScaledPixels(2);
|
||||
|
||||
this.konva.ringNewColor.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
fill: rgbColorToString(colorUnderCursor),
|
||||
innerRadius: colorPickerInnerRadius,
|
||||
outerRadius: colorPickerOuterRadius,
|
||||
});
|
||||
this.konva.ringOldColor.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
fill: rgbColorToString(toolState.fill),
|
||||
innerRadius: colorPickerInnerRadius,
|
||||
outerRadius: colorPickerOuterRadius,
|
||||
});
|
||||
this.konva.ringInnerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: colorPickerOuterRadius,
|
||||
outerRadius: colorPickerOuterRadius + onePixel,
|
||||
});
|
||||
this.konva.ringOuterBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: colorPickerOuterRadius + onePixel,
|
||||
outerRadius: colorPickerOuterRadius + twoPixels,
|
||||
});
|
||||
|
||||
const size = this.manager.stage.getScaledPixels(this.config.CROSSHAIR_LINE_LENGTH);
|
||||
const space = this.manager.stage.getScaledPixels(this.config.CROSSHAIR_INNER_RADIUS);
|
||||
const innerThickness = this.manager.stage.getScaledPixels(this.config.CROSSHAIR_LINE_THICKNESS);
|
||||
const outerThickness = this.manager.stage.getScaledPixels(
|
||||
this.config.CROSSHAIR_LINE_THICKNESS + this.config.CROSSHAIR_LINE_BORDER_THICKNESS * 2
|
||||
);
|
||||
this.konva.crosshairNorthOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x, cursorPos.y - size, cursorPos.x, cursorPos.y - space],
|
||||
});
|
||||
this.konva.crosshairNorthInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x, cursorPos.y - size, cursorPos.x, cursorPos.y - space],
|
||||
});
|
||||
this.konva.crosshairEastOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x + space, cursorPos.y, cursorPos.x + size, cursorPos.y],
|
||||
});
|
||||
this.konva.crosshairEastInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x + space, cursorPos.y, cursorPos.x + size, cursorPos.y],
|
||||
});
|
||||
this.konva.crosshairSouthOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x, cursorPos.y + space, cursorPos.x, cursorPos.y + size],
|
||||
});
|
||||
this.konva.crosshairSouthInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x, cursorPos.y + space, cursorPos.x, cursorPos.y + size],
|
||||
});
|
||||
this.konva.crosshairWestOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x - space, cursorPos.y, cursorPos.x - size, cursorPos.y],
|
||||
});
|
||||
this.konva.crosshairWestInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x - space, cursorPos.y, cursorPos.x - size, cursorPos.y],
|
||||
});
|
||||
};
|
||||
|
||||
setVisibility = (visible: boolean) => {
|
||||
this.konva.group.visible(visible);
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
config: this.config,
|
||||
};
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying color picker tool preview module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.konva.group.destroy();
|
||||
};
|
||||
|
||||
getLoggingContext = () => {
|
||||
return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleABC } from 'features/controlLayers/konva/CanvasModuleABC';
|
||||
import type { CanvasToolModule } from 'features/controlLayers/konva/CanvasToolModule';
|
||||
import { alignCoordForTool, getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import Konva from 'konva';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
type EraserToolPreviewConfig = {
|
||||
/**
|
||||
* The inner border color for the eraser tool preview.
|
||||
*/
|
||||
BORDER_INNER_COLOR: string;
|
||||
/**
|
||||
* The outer border color for the eraser tool preview.
|
||||
*/
|
||||
BORDER_OUTER_COLOR: string;
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG: EraserToolPreviewConfig = {
|
||||
BORDER_INNER_COLOR: 'rgba(0,0,0,1)',
|
||||
BORDER_OUTER_COLOR: 'rgba(255,255,255,0.8)',
|
||||
};
|
||||
|
||||
export class CanvasEraserToolPreview extends CanvasModuleABC {
|
||||
readonly type = 'eraser_tool_preview';
|
||||
|
||||
id: string;
|
||||
path: string[];
|
||||
parent: CanvasToolModule;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
config: EraserToolPreviewConfig;
|
||||
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
cutoutCircle: Konva.Circle;
|
||||
innerBorder: Konva.Ring;
|
||||
outerBorder: Konva.Ring;
|
||||
};
|
||||
|
||||
constructor(parent: CanvasToolModule, config?: Partial<EraserToolPreviewConfig>) {
|
||||
super();
|
||||
this.id = getPrefixedId(this.type);
|
||||
this.parent = parent;
|
||||
this.manager = this.parent.manager;
|
||||
this.path = this.parent.path.concat(this.id);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
this.log.debug('Creating eraser tool preview module');
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: `${this.type}:eraser_group`, listening: false }),
|
||||
cutoutCircle: new Konva.Circle({
|
||||
name: `${this.type}:eraser_cutout_circle`,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
// The fill is used only to erase what is underneath it, so its color doesn't matter - just needs to be opaque
|
||||
fill: 'white',
|
||||
globalCompositeOperation: 'destination-out',
|
||||
}),
|
||||
innerBorder: new Konva.Ring({
|
||||
name: `${this.type}:eraser_inner_border_ring`,
|
||||
listening: false,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: this.config.BORDER_INNER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
outerBorder: new Konva.Ring({
|
||||
name: `${this.type}:eraser_outer_border_ring`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: this.config.BORDER_OUTER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
};
|
||||
this.konva.group.add(this.konva.cutoutCircle, this.konva.innerBorder, this.konva.outerBorder);
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const cursorPos = this.manager.stateApi.$lastCursorPos.get();
|
||||
|
||||
if (!cursorPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
const alignedCursorPos = alignCoordForTool(cursorPos, toolState.eraser.width);
|
||||
const radius = toolState.eraser.width / 2;
|
||||
|
||||
// The circle is scaled
|
||||
this.konva.cutoutCircle.setAttrs({
|
||||
x: alignedCursorPos.x,
|
||||
y: alignedCursorPos.y,
|
||||
radius,
|
||||
});
|
||||
|
||||
// But the borders are in screen-pixels
|
||||
const onePixel = this.manager.stage.getScaledPixels(1);
|
||||
const twoPixels = this.manager.stage.getScaledPixels(2);
|
||||
|
||||
this.konva.innerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius,
|
||||
outerRadius: radius + onePixel,
|
||||
});
|
||||
this.konva.outerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius + onePixel,
|
||||
outerRadius: radius + twoPixels,
|
||||
});
|
||||
};
|
||||
|
||||
setVisibility = (visible: boolean) => {
|
||||
this.konva.group.visible(visible);
|
||||
};
|
||||
|
||||
repr = () => {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
config: this.config,
|
||||
};
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.log.debug('Destroying eraser tool preview module');
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.konva.group.destroy();
|
||||
};
|
||||
|
||||
getLoggingContext = () => {
|
||||
return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleABC } from 'features/controlLayers/konva/CanvasModuleABC';
|
||||
import { CANVAS_SCALE_BY } from 'features/controlLayers/konva/constants';
|
||||
import { getPrefixedId, getRectUnion } from 'features/controlLayers/konva/util';
|
||||
import type { CanvasEntityIdentifier, Coordinate, Dimensions, Rect } from 'features/controlLayers/store/types';
|
||||
import type Konva from 'konva';
|
||||
@@ -8,27 +7,52 @@ import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { clamp } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
type CanvasStageModuleConfig = {
|
||||
/**
|
||||
* The minimum (furthest-zoomed-in) scale of the canvas
|
||||
*/
|
||||
MIN_SCALE: number;
|
||||
/**
|
||||
* The maximum (furthest-zoomed-out) scale of the canvas
|
||||
*/
|
||||
MAX_SCALE: number;
|
||||
/**
|
||||
* The factor by which the canvas should be scaled when zooming in/out
|
||||
*/
|
||||
SCALE_FACTOR: number;
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG: CanvasStageModuleConfig = {
|
||||
MIN_SCALE: 0.1,
|
||||
MAX_SCALE: 20,
|
||||
SCALE_FACTOR: 0.999,
|
||||
};
|
||||
|
||||
export class CanvasStageModule extends CanvasModuleABC {
|
||||
readonly type = 'stage';
|
||||
|
||||
static MIN_CANVAS_SCALE = 0.1;
|
||||
static MAX_CANVAS_SCALE = 20;
|
||||
|
||||
id: string;
|
||||
path: string[];
|
||||
konva: { stage: Konva.Stage };
|
||||
manager: CanvasManager;
|
||||
container: HTMLDivElement;
|
||||
log: Logger;
|
||||
config: CanvasStageModuleConfig;
|
||||
|
||||
subscriptions = new Set<() => void>();
|
||||
|
||||
constructor(stage: Konva.Stage, container: HTMLDivElement, manager: CanvasManager) {
|
||||
constructor(
|
||||
stage: Konva.Stage,
|
||||
container: HTMLDivElement,
|
||||
manager: CanvasManager,
|
||||
config?: Partial<CanvasStageModuleConfig>
|
||||
) {
|
||||
super();
|
||||
this.id = getPrefixedId('stage');
|
||||
this.manager = manager;
|
||||
this.path = this.manager.path.concat(this.id);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
this.log.debug('Creating stage module');
|
||||
|
||||
@@ -163,11 +187,7 @@ export class CanvasStageModule extends CanvasModuleABC {
|
||||
*/
|
||||
setScale = (scale: number, center: Coordinate = this.getCenter(true)) => {
|
||||
this.log.trace('Setting scale');
|
||||
const newScale = clamp(
|
||||
Math.round(scale * 100) / 100,
|
||||
CanvasStageModule.MIN_CANVAS_SCALE,
|
||||
CanvasStageModule.MAX_CANVAS_SCALE
|
||||
);
|
||||
const newScale = clamp(Math.round(scale * 100) / 100, this.config.MIN_SCALE, this.config.MAX_SCALE);
|
||||
|
||||
const { x, y } = this.getPosition();
|
||||
const oldScale = this.getScale();
|
||||
@@ -207,7 +227,7 @@ export class CanvasStageModule extends CanvasModuleABC {
|
||||
if (cursorPos) {
|
||||
// When wheeling on trackpad, e.evt.ctrlKey is true - in that case, let's reverse the direction
|
||||
const delta = e.evt.ctrlKey ? -e.evt.deltaY : e.evt.deltaY;
|
||||
const scale = this.manager.stage.getScale() * CANVAS_SCALE_BY ** delta;
|
||||
const scale = this.manager.stage.getScale() * this.config.SCALE_FACTOR ** delta;
|
||||
this.manager.stage.setScale(scale, cursorPos);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { rgbaColorToString, rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { CanvasBrushToolPreview } from 'features/controlLayers/konva/CanvasBrushToolPreview';
|
||||
import { CanvasColorPickerToolPreview } from 'features/controlLayers/konva/CanvasColorPickerToolPreview';
|
||||
import { CanvasEraserToolPreview } from 'features/controlLayers/konva/CanvasEraserToolPreview';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleABC } from 'features/controlLayers/konva/CanvasModuleABC';
|
||||
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
|
||||
import {
|
||||
BRUSH_BORDER_INNER_COLOR,
|
||||
BRUSH_BORDER_OUTER_COLOR,
|
||||
BRUSH_SPACING_TARGET_SCALE,
|
||||
} from 'features/controlLayers/konva/constants';
|
||||
import {
|
||||
alignCoordForTool,
|
||||
calculateNewBrushSizeFromWheelDelta,
|
||||
@@ -31,209 +28,58 @@ import Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
type CanvasToolModuleConfig = {
|
||||
BRUSH_SPACING_TARGET_SCALE: number;
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG: CanvasToolModuleConfig = {
|
||||
BRUSH_SPACING_TARGET_SCALE: 0.1,
|
||||
};
|
||||
|
||||
export class CanvasToolModule extends CanvasModuleABC {
|
||||
readonly type = 'tool';
|
||||
static readonly COLOR_PICKER_RADIUS = 25;
|
||||
static readonly COLOR_PICKER_THICKNESS = 15;
|
||||
static readonly COLOR_PICKER_CROSSHAIR_SPACE = 5;
|
||||
static readonly COLOR_PICKER_CROSSHAIR_INNER_THICKNESS = 1.5;
|
||||
static readonly COLOR_PICKER_CROSSHAIR_OUTER_THICKNESS = 3;
|
||||
static readonly COLOR_PICKER_CROSSHAIR_SIZE = 10;
|
||||
|
||||
id: string;
|
||||
path: string[];
|
||||
parent: CanvasPreviewModule;
|
||||
manager: CanvasManager;
|
||||
log: Logger;
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
config: CanvasToolModuleConfig;
|
||||
|
||||
brushToolPreview: CanvasBrushToolPreview;
|
||||
eraserToolPreview: CanvasEraserToolPreview;
|
||||
colorPickerToolPreview: CanvasColorPickerToolPreview;
|
||||
|
||||
konva: {
|
||||
stage: Konva.Stage;
|
||||
group: Konva.Group;
|
||||
brush: {
|
||||
group: Konva.Group;
|
||||
fillCircle: Konva.Circle;
|
||||
innerBorder: Konva.Ring;
|
||||
outerBorder: Konva.Ring;
|
||||
};
|
||||
eraser: {
|
||||
group: Konva.Group;
|
||||
fillCircle: Konva.Circle;
|
||||
innerBorder: Konva.Ring;
|
||||
outerBorder: Konva.Ring;
|
||||
};
|
||||
colorPicker: {
|
||||
group: Konva.Group;
|
||||
newColor: Konva.Ring;
|
||||
oldColor: Konva.Arc;
|
||||
innerBorder: Konva.Ring;
|
||||
outerBorder: Konva.Ring;
|
||||
crosshairNorthInner: Konva.Line;
|
||||
crosshairNorthOuter: Konva.Line;
|
||||
crosshairEastInner: Konva.Line;
|
||||
crosshairEastOuter: Konva.Line;
|
||||
crosshairSouthInner: Konva.Line;
|
||||
crosshairSouthOuter: Konva.Line;
|
||||
crosshairWestInner: Konva.Line;
|
||||
crosshairWestOuter: Konva.Line;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
|
||||
*/
|
||||
subscriptions: Set<() => void> = new Set();
|
||||
|
||||
constructor(parent: CanvasPreviewModule) {
|
||||
constructor(parent: CanvasPreviewModule, config?: Partial<CanvasToolModuleConfig>) {
|
||||
super();
|
||||
this.id = getPrefixedId(this.type);
|
||||
this.parent = parent;
|
||||
this.manager = this.parent.manager;
|
||||
this.path = this.parent.path.concat(this.id);
|
||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
this.log.debug('Creating tool module');
|
||||
|
||||
this.brushToolPreview = new CanvasBrushToolPreview(this);
|
||||
this.eraserToolPreview = new CanvasEraserToolPreview(this);
|
||||
this.colorPickerToolPreview = new CanvasColorPickerToolPreview(this);
|
||||
|
||||
this.konva = {
|
||||
stage: this.manager.stage.konva.stage,
|
||||
group: new Konva.Group({ name: `${this.type}:group`, listening: false }),
|
||||
brush: {
|
||||
group: new Konva.Group({ name: `${this.type}:brush_group`, listening: false }),
|
||||
fillCircle: new Konva.Circle({
|
||||
name: `${this.type}:brush_fill_circle`,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
innerBorder: new Konva.Ring({
|
||||
name: `${this.type}:brush_inner_border_ring`,
|
||||
listening: false,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: BRUSH_BORDER_INNER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
outerBorder: new Konva.Ring({
|
||||
name: `${this.type}:brush_outer_border_ring`,
|
||||
listening: false,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: BRUSH_BORDER_OUTER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
},
|
||||
eraser: {
|
||||
group: new Konva.Group({ name: `${this.type}:eraser_group`, listening: false }),
|
||||
fillCircle: new Konva.Circle({
|
||||
name: `${this.type}:eraser_fill_circle`,
|
||||
listening: false,
|
||||
strokeEnabled: false,
|
||||
fill: 'white',
|
||||
globalCompositeOperation: 'destination-out',
|
||||
}),
|
||||
innerBorder: new Konva.Ring({
|
||||
name: `${this.type}:eraser_inner_border_ring`,
|
||||
listening: false,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: BRUSH_BORDER_INNER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
outerBorder: new Konva.Ring({
|
||||
name: `${this.type}:eraser_outer_border_ring`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: BRUSH_BORDER_OUTER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
},
|
||||
colorPicker: {
|
||||
group: new Konva.Group({ name: `${this.type}:color_picker_group`, listening: false }),
|
||||
newColor: new Konva.Ring({
|
||||
name: `${this.type}:color_picker_new_color_ring`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
oldColor: new Konva.Arc({
|
||||
name: `${this.type}:color_picker_old_color_arc`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
angle: 180,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
innerBorder: new Konva.Ring({
|
||||
name: `${this.type}:color_picker_inner_border_ring`,
|
||||
listening: false,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: BRUSH_BORDER_INNER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
outerBorder: new Konva.Ring({
|
||||
name: `${this.type}:color_picker_outer_border_ring`,
|
||||
innerRadius: 0,
|
||||
outerRadius: 0,
|
||||
fill: BRUSH_BORDER_OUTER_COLOR,
|
||||
strokeEnabled: false,
|
||||
}),
|
||||
crosshairNorthInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_north1_line`,
|
||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||
}),
|
||||
crosshairNorthOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_north2_line`,
|
||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||
}),
|
||||
crosshairEastInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_east1_line`,
|
||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||
}),
|
||||
crosshairEastOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_east2_line`,
|
||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||
}),
|
||||
crosshairSouthInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_south1_line`,
|
||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||
}),
|
||||
crosshairSouthOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_south2_line`,
|
||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||
}),
|
||||
crosshairWestInner: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_west1_line`,
|
||||
stroke: BRUSH_BORDER_INNER_COLOR,
|
||||
}),
|
||||
crosshairWestOuter: new Konva.Line({
|
||||
name: `${this.type}:color_picker_crosshair_west2_line`,
|
||||
stroke: BRUSH_BORDER_OUTER_COLOR,
|
||||
}),
|
||||
},
|
||||
};
|
||||
this.konva.brush.group.add(this.konva.brush.fillCircle, this.konva.brush.innerBorder, this.konva.brush.outerBorder);
|
||||
this.konva.group.add(this.konva.brush.group);
|
||||
|
||||
this.konva.eraser.group.add(
|
||||
this.konva.eraser.fillCircle,
|
||||
this.konva.eraser.innerBorder,
|
||||
this.konva.eraser.outerBorder
|
||||
);
|
||||
this.konva.group.add(this.konva.eraser.group);
|
||||
|
||||
this.konva.colorPicker.group.add(
|
||||
this.konva.colorPicker.newColor,
|
||||
this.konva.colorPicker.oldColor,
|
||||
this.konva.colorPicker.innerBorder,
|
||||
this.konva.colorPicker.outerBorder,
|
||||
this.konva.colorPicker.crosshairNorthOuter,
|
||||
this.konva.colorPicker.crosshairNorthInner,
|
||||
this.konva.colorPicker.crosshairEastOuter,
|
||||
this.konva.colorPicker.crosshairEastInner,
|
||||
this.konva.colorPicker.crosshairSouthOuter,
|
||||
this.konva.colorPicker.crosshairSouthInner,
|
||||
this.konva.colorPicker.crosshairWestOuter,
|
||||
this.konva.colorPicker.crosshairWestInner
|
||||
);
|
||||
this.konva.group.add(this.konva.colorPicker.group);
|
||||
this.konva.group.add(this.brushToolPreview.konva.group);
|
||||
this.konva.group.add(this.eraserToolPreview.konva.group);
|
||||
this.konva.group.add(this.colorPickerToolPreview.konva.group);
|
||||
|
||||
this.subscriptions.add(this.manager.stateApi.$stageAttrs.listen(this.render));
|
||||
this.subscriptions.add(this.manager.stateApi.$toolState.listen(this.render));
|
||||
@@ -245,9 +91,9 @@ export class CanvasToolModule extends CanvasModuleABC {
|
||||
}
|
||||
|
||||
setToolVisibility = (tool: Tool, isDrawable: boolean) => {
|
||||
this.konva.brush.group.visible(isDrawable && tool === 'brush');
|
||||
this.konva.eraser.group.visible(isDrawable && tool === 'eraser');
|
||||
this.konva.colorPicker.group.visible(tool === 'colorPicker');
|
||||
this.brushToolPreview.setVisibility(isDrawable && tool === 'brush');
|
||||
this.eraserToolPreview.setVisibility(isDrawable && tool === 'eraser');
|
||||
this.colorPickerToolPreview.setVisibility(tool === 'colorPicker');
|
||||
};
|
||||
|
||||
syncCursorStyle = () => {
|
||||
@@ -294,142 +140,6 @@ export class CanvasToolModule extends CanvasModuleABC {
|
||||
}
|
||||
};
|
||||
|
||||
renderBrushTool = (cursorPos: Coordinate) => {
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
const brushPreviewFill = this.manager.stateApi.getBrushPreviewFill();
|
||||
const alignedCursorPos = alignCoordForTool(cursorPos, toolState.brush.width);
|
||||
const onePixel = this.manager.stage.getScaledPixels(1);
|
||||
const twoPixels = this.manager.stage.getScaledPixels(2);
|
||||
const radius = toolState.brush.width / 2;
|
||||
|
||||
// The circle is scaled
|
||||
this.konva.brush.fillCircle.setAttrs({
|
||||
x: alignedCursorPos.x,
|
||||
y: alignedCursorPos.y,
|
||||
radius,
|
||||
fill: rgbaColorToString(brushPreviewFill),
|
||||
});
|
||||
|
||||
// But the borders are in screen-pixels
|
||||
this.konva.brush.innerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius,
|
||||
outerRadius: radius + onePixel,
|
||||
});
|
||||
this.konva.brush.outerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius + onePixel,
|
||||
outerRadius: radius + twoPixels,
|
||||
});
|
||||
};
|
||||
|
||||
renderEraserTool = (cursorPos: Coordinate) => {
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
const alignedCursorPos = alignCoordForTool(cursorPos, toolState.eraser.width);
|
||||
const onePixel = this.manager.stage.getScaledPixels(1);
|
||||
const twoPixels = this.manager.stage.getScaledPixels(2);
|
||||
const radius = toolState.eraser.width / 2;
|
||||
|
||||
// The circle is scaled
|
||||
this.konva.eraser.fillCircle.setAttrs({
|
||||
x: alignedCursorPos.x,
|
||||
y: alignedCursorPos.y,
|
||||
radius,
|
||||
fill: 'white',
|
||||
});
|
||||
|
||||
// But the borders are in screen-pixels
|
||||
this.konva.eraser.innerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius,
|
||||
outerRadius: radius + onePixel,
|
||||
});
|
||||
this.konva.eraser.outerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: radius + onePixel,
|
||||
outerRadius: radius + twoPixels,
|
||||
});
|
||||
};
|
||||
|
||||
renderColorPicker = (cursorPos: Coordinate) => {
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
const colorUnderCursor = this.manager.stateApi.$colorUnderCursor.get();
|
||||
const colorPickerInnerRadius = this.manager.stage.getScaledPixels(CanvasToolModule.COLOR_PICKER_RADIUS);
|
||||
const colorPickerOuterRadius = this.manager.stage.getScaledPixels(
|
||||
CanvasToolModule.COLOR_PICKER_RADIUS + CanvasToolModule.COLOR_PICKER_THICKNESS
|
||||
);
|
||||
const onePixel = this.manager.stage.getScaledPixels(1);
|
||||
const twoPixels = this.manager.stage.getScaledPixels(2);
|
||||
|
||||
this.konva.colorPicker.newColor.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
fill: rgbColorToString(colorUnderCursor),
|
||||
innerRadius: colorPickerInnerRadius,
|
||||
outerRadius: colorPickerOuterRadius,
|
||||
});
|
||||
this.konva.colorPicker.oldColor.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
fill: rgbColorToString(toolState.fill),
|
||||
innerRadius: colorPickerInnerRadius,
|
||||
outerRadius: colorPickerOuterRadius,
|
||||
});
|
||||
this.konva.colorPicker.innerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: colorPickerOuterRadius,
|
||||
outerRadius: colorPickerOuterRadius + onePixel,
|
||||
});
|
||||
this.konva.colorPicker.outerBorder.setAttrs({
|
||||
x: cursorPos.x,
|
||||
y: cursorPos.y,
|
||||
innerRadius: colorPickerOuterRadius + onePixel,
|
||||
outerRadius: colorPickerOuterRadius + twoPixels,
|
||||
});
|
||||
|
||||
const size = this.manager.stage.getScaledPixels(CanvasToolModule.COLOR_PICKER_CROSSHAIR_SIZE);
|
||||
const space = this.manager.stage.getScaledPixels(CanvasToolModule.COLOR_PICKER_CROSSHAIR_SPACE);
|
||||
const innerThickness = this.manager.stage.getScaledPixels(CanvasToolModule.COLOR_PICKER_CROSSHAIR_INNER_THICKNESS);
|
||||
const outerThickness = this.manager.stage.getScaledPixels(CanvasToolModule.COLOR_PICKER_CROSSHAIR_OUTER_THICKNESS);
|
||||
this.konva.colorPicker.crosshairNorthOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x, cursorPos.y - size, cursorPos.x, cursorPos.y - space],
|
||||
});
|
||||
this.konva.colorPicker.crosshairNorthInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x, cursorPos.y - size, cursorPos.x, cursorPos.y - space],
|
||||
});
|
||||
this.konva.colorPicker.crosshairEastOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x + space, cursorPos.y, cursorPos.x + size, cursorPos.y],
|
||||
});
|
||||
this.konva.colorPicker.crosshairEastInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x + space, cursorPos.y, cursorPos.x + size, cursorPos.y],
|
||||
});
|
||||
this.konva.colorPicker.crosshairSouthOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x, cursorPos.y + space, cursorPos.x, cursorPos.y + size],
|
||||
});
|
||||
this.konva.colorPicker.crosshairSouthInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x, cursorPos.y + space, cursorPos.x, cursorPos.y + size],
|
||||
});
|
||||
this.konva.colorPicker.crosshairWestOuter.setAttrs({
|
||||
strokeWidth: outerThickness,
|
||||
points: [cursorPos.x - space, cursorPos.y, cursorPos.x - size, cursorPos.y],
|
||||
});
|
||||
this.konva.colorPicker.crosshairWestInner.setAttrs({
|
||||
strokeWidth: innerThickness,
|
||||
points: [cursorPos.x - space, cursorPos.y, cursorPos.x - size, cursorPos.y],
|
||||
});
|
||||
};
|
||||
|
||||
render = () => {
|
||||
const stage = this.manager.stage;
|
||||
const renderedEntityCount = this.manager.stateApi.getRenderedEntityCount();
|
||||
@@ -455,11 +165,11 @@ export class CanvasToolModule extends CanvasModuleABC {
|
||||
|
||||
// No need to render the brush preview if the cursor position or color is missing
|
||||
if (cursorPos && tool === 'brush') {
|
||||
this.renderBrushTool(cursorPos);
|
||||
this.brushToolPreview.render();
|
||||
} else if (cursorPos && tool === 'eraser') {
|
||||
this.renderEraserTool(cursorPos);
|
||||
this.eraserToolPreview.render();
|
||||
} else if (cursorPos && tool === 'colorPicker') {
|
||||
this.renderColorPicker(cursorPos);
|
||||
this.colorPickerToolPreview.render();
|
||||
}
|
||||
|
||||
this.setToolVisibility(tool, isDrawable);
|
||||
@@ -719,7 +429,7 @@ export class CanvasToolModule extends CanvasModuleABC {
|
||||
if (drawingBuffer) {
|
||||
if (drawingBuffer.type === 'brush_line') {
|
||||
const lastPoint = getLastPointOfLine(drawingBuffer.points);
|
||||
const minDistance = toolState.brush.width * BRUSH_SPACING_TARGET_SCALE;
|
||||
const minDistance = toolState.brush.width * this.config.BRUSH_SPACING_TARGET_SCALE;
|
||||
if (lastPoint && validateCandidatePoint(pos, lastPoint, minDistance)) {
|
||||
const normalizedPoint = offsetCoord(pos, selectedEntity.state.position);
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
|
||||
@@ -756,7 +466,7 @@ export class CanvasToolModule extends CanvasModuleABC {
|
||||
if (drawingBuffer) {
|
||||
if (drawingBuffer.type === 'eraser_line') {
|
||||
const lastPoint = getLastPointOfLine(drawingBuffer.points);
|
||||
const minDistance = toolState.eraser.width * BRUSH_SPACING_TARGET_SCALE;
|
||||
const minDistance = toolState.eraser.width * this.config.BRUSH_SPACING_TARGET_SCALE;
|
||||
if (lastPoint && validateCandidatePoint(pos, lastPoint, minDistance)) {
|
||||
const normalizedPoint = offsetCoord(pos, selectedEntity.state.position);
|
||||
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
|
||||
|
||||
@@ -1,41 +1,6 @@
|
||||
/**
|
||||
* A transparency checker pattern image.
|
||||
* This is invokeai/frontend/web/public/assets/images/transparent_bg.png as a dataURL
|
||||
* This is ./transparent_bg.png as a dataURL
|
||||
*/
|
||||
export const TRANSPARENCY_CHECKER_PATTERN =
|
||||
export const TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAEsmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjIwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMjAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyMCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjMwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIzMDAvMSIKICAgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIKICAgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjEwLjgiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDQtMjNUMDg6MjA6NDcrMTA6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/Pn9pdVgAAAGBaUNDUHNSR0IgSUVDNjE5NjYtMi4xAAAokXWR3yuDURjHP5uJmKghFy6WxpVpqMWNMgm1tGbKr5vt3S+1d3t73y3JrXKrKHHj1wV/AbfKtVJESq53TdywXs9rakv2nJ7zfM73nOfpnOeAPZJRVMPhAzWb18NTAffC4pK7oYiDTjpw4YgqhjYeCgWpaR8P2Kx457Vq1T73rzXHE4YCtkbhMUXT88LTwsG1vGbxrnC7ko7Ghc+F+3W5oPC9pcfKXLQ4VeYvi/VIeALsbcLuVBXHqlhJ66qwvByPmikov/exXuJMZOfnJPaId2MQZooAbmaYZAI/g4zK7MfLEAOyoka+7yd/lpzkKjJrrKOzSoo0efpFLUj1hMSk6AkZGdat/v/tq5EcHipXdwag/sU033qhYQdK26b5eWyapROoe4arbCU/dwQj76JvVzTPIbRuwsV1RYvtweUWdD1pUT36I9WJ25NJeD2DlkVw3ULTcrlnv/ucPkJkQ77qBvYPoE/Ot658AxagZ8FoS/a7AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAL0lEQVQ4jWM8ffo0A25gYmKCR5YJjxxBMKp5ZGhm/P//Px7pM2fO0MrmUc0jQzMAB2EIhZC3pUYAAAAASUVORK5CYII=';
|
||||
|
||||
/**
|
||||
* The inner border color for the brush preview.
|
||||
*/
|
||||
export const BRUSH_BORDER_INNER_COLOR = 'rgba(0,0,0,1)';
|
||||
|
||||
/**
|
||||
* The outer border color for the brush preview.
|
||||
*/
|
||||
export const BRUSH_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)';
|
||||
|
||||
/**
|
||||
* The target spacing of individual points of brush strokes, as a percentage of the brush size.
|
||||
*/
|
||||
export const BRUSH_SPACING_TARGET_SCALE = 0.1;
|
||||
|
||||
/**
|
||||
* Konva wheel zoom exponential scale factor
|
||||
*/
|
||||
export const CANVAS_SCALE_BY = 0.999;
|
||||
|
||||
/**
|
||||
* Minimum (furthest-zoomed-out) scale
|
||||
*/
|
||||
export const MIN_CANVAS_SCALE = 0.1;
|
||||
|
||||
/**
|
||||
* Maximum (furthest-zoomed-in) scale
|
||||
*/
|
||||
export const MAX_CANVAS_SCALE = 20;
|
||||
|
||||
/**
|
||||
* The fine grid size of the canvas
|
||||
*/
|
||||
export const CANVAS_GRID_SIZE_FINE = 8;
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,7 +1,8 @@
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { CANVAS_GRID_SIZE_FINE } from 'features/controlLayers/konva/constants';
|
||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||
|
||||
const CANVAS_GRID_SIZE_FINE = 8;
|
||||
|
||||
/**
|
||||
* Scales the bounding box dimensions to the optimal dimension. The optimal dimensions should be the trained dimension
|
||||
* for the model. For example, 1024 for SDXL or 512 for SD1.5.
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Box, Flex, Image } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useBoolean } from 'common/hooks/useBoolean';
|
||||
import { preventDefault } from 'common/util/stopPropagation';
|
||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
|
||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
|
||||
import { selectComparisonFit } from 'features/gallery/store/gallerySelectors';
|
||||
@@ -79,7 +79,7 @@ export const ImageComparisonHover = memo(({ firstImage, secondImage, containerDi
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
backgroundImage={TRANSPARENCY_CHECKER_PATTERN}
|
||||
backgroundImage={TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL}
|
||||
backgroundRepeat="repeat"
|
||||
opacity={0.2}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Flex, Icon, Image } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { preventDefault } from 'common/util/stopPropagation';
|
||||
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
|
||||
import { TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL } from 'features/controlLayers/konva/patterns/transparency-checkerboard-pattern';
|
||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||
import { ImageComparisonLabel } from 'features/gallery/components/ImageViewer/ImageComparisonLabel';
|
||||
import { selectComparisonFit } from 'features/gallery/store/gallerySelectors';
|
||||
@@ -121,7 +121,7 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerD
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
backgroundImage={TRANSPARENCY_CHECKER_PATTERN}
|
||||
backgroundImage={TRANSPARENCY_CHECKERBOARD_PATTERN_DATAURL}
|
||||
backgroundRepeat="repeat"
|
||||
opacity={0.2}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user