Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix,feat(ui): canvas improvements #7651

Merged
merged 4 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ export class CanvasEntityAdapterInpaintMask extends CanvasEntityAdapterBase<
this.syncOpacity();
}
if (!prevState || this.state.fill !== prevState.fill) {
// On first render, we must force the update
this.renderer.updateCompositingRectFill(!prevState);
// On first render, or when the fill changes, we must force the update
this.renderer.updateCompositingRectFill(true);
}
if (!prevState) {
// On first render, we must force the updates
if (!prevState || this.state.objects !== prevState.objects) {
// On first render, or when the objects change, we must force the update
this.renderer.updateCompositingRectSize(true);
this.renderer.updateCompositingRectPosition(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ export class CanvasEntityAdapterRegionalGuidance extends CanvasEntityAdapterBase
this.syncOpacity();
}
if (!prevState || this.state.fill !== prevState.fill) {
// On first render, we must force the update
this.renderer.updateCompositingRectFill(!prevState);
// On first render, or when the fill changes, we must force the update
this.renderer.updateCompositingRectFill(true);
}
if (!prevState) {
// On first render, we must force the updates
if (!prevState || this.state.objects !== prevState.objects) {
// On first render, or when the objects change, we must force the update
this.renderer.updateCompositingRectSize(true);
this.renderer.updateCompositingRectPosition(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,11 @@ export class CanvasBrushToolModule extends CanvasModuleBase {

let points: number[];

let isShiftDraw = false;

if (e.evt.shiftKey && lastLinePoint) {
// Create a straight line from the last line point
isShiftDraw = true;
points = [
lastLinePoint.x,
lastLinePoint.y,
Expand All @@ -298,15 +301,18 @@ export class CanvasBrushToolModule extends CanvasModuleBase {
points,
strokeWidth: settings.brushWidth,
color: this.manager.stateApi.getCurrentColor(),
clip: this.parent.getClip(selectedEntity.state),
// When shift is held, the line may extend beyond the clip region. No clip for these lines.
clip: isShiftDraw ? null : this.parent.getClip(selectedEntity.state),
});
} else {
const lastLinePoint = getLastPointOfLastLine(selectedEntity.state.objects, 'brush_line');

let points: number[];
let isShiftDraw = false;

if (e.evt.shiftKey && lastLinePoint) {
// Create a straight line from the last line point
isShiftDraw = true;
points = [lastLinePoint.x, lastLinePoint.y, alignedPoint.x, alignedPoint.y];
} else {
// Create a new line with the current point
Expand All @@ -319,7 +325,8 @@ export class CanvasBrushToolModule extends CanvasModuleBase {
points,
strokeWidth: settings.brushWidth,
color: this.manager.stateApi.getCurrentColor(),
clip: this.parent.getClip(selectedEntity.state),
// When shift is held, the line may extend beyond the clip region. No clip for these lines.
clip: isShiftDraw ? null : this.parent.getClip(selectedEntity.state),
});
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
import type { CanvasToolModule } from 'features/controlLayers/konva/CanvasTool/CanvasToolModule';
import { getColorAtCoordinate, getPrefixedId } from 'features/controlLayers/konva/util';
import type { RgbColor } from 'features/controlLayers/store/types';
import type { RgbaColor } from 'features/controlLayers/store/types';
import { RGBA_BLACK } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
Expand Down Expand Up @@ -52,6 +52,39 @@ type CanvasColorPickerToolModuleConfig = {
* The color of the crosshair line borders.
*/
CROSSHAIR_BORDER_COLOR: string;
/**
* The color of the RGBA value text.
*/
TEXT_COLOR: string;
/**
* The padding of the RGBA value text within the background rect.
*/

TEXT_PADDING: number;
/**
* The font size of the RGBA value text.
*/
TEXT_FONT_SIZE: number;
/**
* The color of the RGBA value text background rect.
*/
TEXT_BG_COLOR: string;
/**
* The width of the RGBA value text background rect.
*/
TEXT_BG_WIDTH: number;
/**
* The height of the RGBA value text background rect.
*/
TEXT_BG_HEIGHT: number;
/**
* The corner radius of the RGBA value text background rect.
*/
TEXT_BG_CORNER_RADIUS: number;
/**
* The x offset of the RGBA value text background rect from the color picker ring.
*/
TEXT_BG_X_OFFSET: number;
};

const DEFAULT_CONFIG: CanvasColorPickerToolModuleConfig = {
Expand All @@ -65,6 +98,14 @@ const DEFAULT_CONFIG: CanvasColorPickerToolModuleConfig = {
CROSSHAIR_LINE_LENGTH: 10,
CROSSHAIR_LINE_COLOR: 'rgba(0,0,0,1)',
CROSSHAIR_BORDER_COLOR: 'rgba(255,255,255,0.8)',
TEXT_COLOR: 'rgba(255,255,255,1)',
TEXT_BG_COLOR: 'rgba(0,0,0,0.8)',
TEXT_BG_HEIGHT: 62,
TEXT_BG_WIDTH: 62,
TEXT_BG_CORNER_RADIUS: 7,
TEXT_PADDING: 8,
TEXT_FONT_SIZE: 12,
TEXT_BG_X_OFFSET: 7,
};

/**
Expand All @@ -83,7 +124,7 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
/**
* The color currently under the cursor. Only has a value when the color picker tool is active.
*/
$colorUnderCursor = atom<RgbColor>(RGBA_BLACK);
$colorUnderCursor = atom<RgbaColor>(RGBA_BLACK);

/**
* The Konva objects that make up the color picker tool preview:
Expand All @@ -105,6 +146,9 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
crosshairSouthOuter: Konva.Line;
crosshairWestInner: Konva.Line;
crosshairWestOuter: Konva.Line;
rgbaTextGroup: Konva.Group;
rgbaText: Konva.Text;
rgbaTextBackground: Konva.Rect;
};

constructor(parent: CanvasToolModule) {
Expand Down Expand Up @@ -202,8 +246,28 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
stroke: this.config.CROSSHAIR_BORDER_COLOR,
perfectDrawEnabled: false,
}),
rgbaTextGroup: new Konva.Group({
listening: false,
name: `${this.type}:color_picker_text_group`,
}),
rgbaText: new Konva.Text({
listening: false,
name: `${this.type}:color_picker_text`,
fill: this.config.TEXT_COLOR,
fontFamily: 'monospace',
align: 'left',
fontStyle: 'bold',
verticalAlign: 'middle',
}),
rgbaTextBackground: new Konva.Rect({
listening: false,
name: `${this.type}:color_picker_text_background`,
fill: this.config.TEXT_BG_COLOR,
}),
};

this.konva.rgbaTextGroup.add(this.konva.rgbaTextBackground, this.konva.rgbaText);

this.konva.group.add(
this.konva.ringCandidateColor,
this.konva.ringCurrentColor,
Expand All @@ -216,7 +280,8 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
this.konva.crosshairSouthOuter,
this.konva.crosshairSouthInner,
this.konva.crosshairWestOuter,
this.konva.crosshairWestInner
this.konva.crosshairWestInner,
this.konva.rgbaTextGroup
);
}

Expand All @@ -233,11 +298,6 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
return;
}

if (!this.parent.getCanDraw()) {
this.setVisibility(false);
return;
}

const cursorPos = this.parent.$cursorPos.get();

if (!cursorPos) {
Expand Down Expand Up @@ -283,6 +343,24 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
outerRadius: colorPickerOuterRadius + twoPixels,
});

const textBgWidth = this.manager.stage.unscale(this.config.TEXT_BG_WIDTH);
const textBgHeight = this.manager.stage.unscale(this.config.TEXT_BG_HEIGHT);

this.konva.rgbaTextBackground.setAttrs({
width: textBgWidth,
height: textBgHeight,
cornerRadius: this.manager.stage.unscale(this.config.TEXT_BG_CORNER_RADIUS),
});
this.konva.rgbaText.setAttrs({
padding: this.manager.stage.unscale(this.config.TEXT_PADDING),
fontSize: this.manager.stage.unscale(this.config.TEXT_FONT_SIZE),
text: `R: ${colorUnderCursor.r}\nG: ${colorUnderCursor.g}\nB: ${colorUnderCursor.b}\nA: ${colorUnderCursor.a}`,
});
this.konva.rgbaTextGroup.setAttrs({
x: x + this.manager.stage.unscale(this.config.RING_OUTER_RADIUS + this.config.TEXT_BG_X_OFFSET),
y: y - textBgHeight / 2,
});

const size = this.manager.stage.unscale(this.config.CROSSHAIR_LINE_LENGTH);
const space = this.manager.stage.unscale(this.config.CROSSHAIR_INNER_RADIUS);
const innerThickness = this.manager.stage.unscale(this.config.CROSSHAIR_LINE_THICKNESS);
Expand Down Expand Up @@ -329,11 +407,8 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {

onStagePointerUp = (_e: KonvaEventObject<PointerEvent>) => {
const color = this.$colorUnderCursor.get();
if (color) {
const settings = this.manager.stateApi.getSettings();
// This will update the color but not the alpha value
this.manager.stateApi.setColor({ ...settings.color, ...color });
}
const settings = this.manager.stateApi.getSettings();
this.manager.stateApi.setColor({ ...settings.color, ...color });
};

onStagePointerMove = (_e: KonvaEventObject<PointerEvent>) => {
Expand All @@ -346,7 +421,11 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
return;
}

// Hide the background layer so we can get the color under the cursor without the grid interfering
this.manager.background.konva.layer.visible(false);
const color = getColorAtCoordinate(this.manager.stage.konva.stage, cursorPos.absolute);
this.manager.background.konva.layer.visible(true);

if (color) {
this.$colorUnderCursor.set(color);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
Coordinate,
Tool,
} from 'features/controlLayers/store/types';
import { isRenderableEntityType } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
import { atom } from 'nanostores';
Expand Down Expand Up @@ -177,24 +178,26 @@ export class CanvasToolModule extends CanvasModuleBase {
stage.setCursor('not-allowed');
} else if (tool === 'bbox') {
this.tools.bbox.syncCursorStyle();
} else if (this.manager.stateApi.getRenderedEntityCount() === 0) {
stage.setCursor('not-allowed');
} else if (selectedEntityAdapter?.$isDisabled.get()) {
stage.setCursor('not-allowed');
} else if (selectedEntityAdapter?.$isEntityTypeHidden.get()) {
stage.setCursor('not-allowed');
} else if (selectedEntityAdapter?.$isLocked.get()) {
stage.setCursor('not-allowed');
} else if (tool === 'brush') {
this.tools.brush.syncCursorStyle();
} else if (tool === 'eraser') {
this.tools.eraser.syncCursorStyle();
} else if (tool === 'colorPicker') {
this.tools.colorPicker.syncCursorStyle();
} else if (tool === 'move') {
this.tools.move.syncCursorStyle();
} else if (tool === 'rect') {
this.tools.rect.syncCursorStyle();
} else if (selectedEntityAdapter && isRenderableEntityType(selectedEntityAdapter.entityIdentifier.type)) {
if (selectedEntityAdapter.$isDisabled.get()) {
stage.setCursor('not-allowed');
} else if (selectedEntityAdapter.$isEntityTypeHidden.get()) {
stage.setCursor('not-allowed');
} else if (selectedEntityAdapter.$isLocked.get()) {
stage.setCursor('not-allowed');
} else if (tool === 'brush') {
this.tools.brush.syncCursorStyle();
} else if (tool === 'eraser') {
this.tools.eraser.syncCursorStyle();
} else if (tool === 'move') {
this.tools.move.syncCursorStyle();
} else if (tool === 'rect') {
this.tools.rect.syncCursorStyle();
}
} else if (this.manager.stateApi.getRenderedEntityCount() === 0) {
stage.setCursor('not-allowed');
} else {
stage.setCursor('not-allowed');
}
Expand Down Expand Up @@ -387,15 +390,17 @@ export class CanvasToolModule extends CanvasModuleBase {
try {
this.$lastPointerType.set(e.evt.pointerType);

if (!this.getCanDraw()) {
return;
}

const tool = this.$tool.get();

if (tool === 'colorPicker') {
this.tools.colorPicker.onStagePointerUp(e);
} else if (tool === 'brush') {
}

if (!this.getCanDraw()) {
return;
}

if (tool === 'brush') {
this.tools.brush.onStagePointerUp(e);
} else if (tool === 'eraser') {
this.tools.eraser.onStagePointerUp(e);
Expand All @@ -416,15 +421,17 @@ export class CanvasToolModule extends CanvasModuleBase {
this.$lastPointerType.set(e.evt.pointerType);
this.syncCursorPositions();

if (!this.getCanDraw()) {
return;
}

const tool = this.$tool.get();

if (tool === 'colorPicker') {
this.tools.colorPicker.onStagePointerMove(e);
} else if (tool === 'brush') {
}

if (!this.getCanDraw()) {
return;
}

if (tool === 'brush') {
await this.tools.brush.onStagePointerMove(e);
} else if (tool === 'eraser') {
await this.tools.eraser.onStagePointerMove(e);
Expand Down
10 changes: 5 additions & 5 deletions invokeai/frontend/web/src/features/controlLayers/konva/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
Coordinate,
CoordinateWithPressure,
Rect,
RgbaColor,
} from 'features/controlLayers/store/types';
import type Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
Expand All @@ -15,7 +16,6 @@ import { clamp } from 'lodash-es';
import { customAlphabet } from 'nanoid';
import type { StrokeOptions } from 'perfect-freehand';
import getStroke from 'perfect-freehand';
import type { RgbColor } from 'react-colorful';
import { assert } from 'tsafe';

/**
Expand Down Expand Up @@ -724,7 +724,7 @@ export const getPointerType = (e: KonvaEventObject<PointerEvent>): 'mouse' | 'pe
* @param coord The coordinate to get the color at. This must be the _absolute_ coordinate on the stage.
* @returns The color under the coordinate, or null if there was a problem getting the color.
*/
export const getColorAtCoordinate = (stage: Konva.Stage, coord: Coordinate): RgbColor | null => {
export const getColorAtCoordinate = (stage: Konva.Stage, coord: Coordinate): RgbaColor | null => {
const ctx = stage
.toCanvas({ x: coord.x, y: coord.y, width: 1, height: 1, imageSmoothingEnabled: false })
.getContext('2d');
Expand All @@ -733,13 +733,13 @@ export const getColorAtCoordinate = (stage: Konva.Stage, coord: Coordinate): Rgb
return null;
}

const [r, g, b, _a] = ctx.getImageData(0, 0, 1, 1).data;
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;

if (r === undefined || g === undefined || b === undefined) {
if (r === undefined || g === undefined || b === undefined || a === undefined) {
return null;
}

return { r, g, b };
return { r, g, b, a };
};

export const roundRect = (rect: Rect): Rect => {
Expand Down
Loading