Skip to content

Commit e1f2b23

Browse files
feat(ui): color picker improvements
- Support transparency w/ color picker. To do this, we need to hide the bg layer before sampling. In testing, this has a negligible performance impact. - Add an RGBA value readout next to the color picker ring.
1 parent 2c5b019 commit e1f2b23

File tree

2 files changed

+97
-13
lines changed

2 files changed

+97
-13
lines changed

invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool/CanvasColorPickerToolModule.ts

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
33
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
44
import type { CanvasToolModule } from 'features/controlLayers/konva/CanvasTool/CanvasToolModule';
55
import { getColorAtCoordinate, getPrefixedId } from 'features/controlLayers/konva/util';
6-
import type { RgbColor } from 'features/controlLayers/store/types';
6+
import type { RgbaColor } from 'features/controlLayers/store/types';
77
import { RGBA_BLACK } from 'features/controlLayers/store/types';
88
import Konva from 'konva';
99
import type { KonvaEventObject } from 'konva/lib/Node';
@@ -52,6 +52,39 @@ type CanvasColorPickerToolModuleConfig = {
5252
* The color of the crosshair line borders.
5353
*/
5454
CROSSHAIR_BORDER_COLOR: string;
55+
/**
56+
* The color of the RGBA value text.
57+
*/
58+
TEXT_COLOR: string;
59+
/**
60+
* The padding of the RGBA value text within the background rect.
61+
*/
62+
63+
TEXT_PADDING: number;
64+
/**
65+
* The font size of the RGBA value text.
66+
*/
67+
TEXT_FONT_SIZE: number;
68+
/**
69+
* The color of the RGBA value text background rect.
70+
*/
71+
TEXT_BG_COLOR: string;
72+
/**
73+
* The width of the RGBA value text background rect.
74+
*/
75+
TEXT_BG_WIDTH: number;
76+
/**
77+
* The height of the RGBA value text background rect.
78+
*/
79+
TEXT_BG_HEIGHT: number;
80+
/**
81+
* The corner radius of the RGBA value text background rect.
82+
*/
83+
TEXT_BG_CORNER_RADIUS: number;
84+
/**
85+
* The x offset of the RGBA value text background rect from the color picker ring.
86+
*/
87+
TEXT_BG_X_OFFSET: number;
5588
};
5689

5790
const DEFAULT_CONFIG: CanvasColorPickerToolModuleConfig = {
@@ -65,6 +98,14 @@ const DEFAULT_CONFIG: CanvasColorPickerToolModuleConfig = {
6598
CROSSHAIR_LINE_LENGTH: 10,
6699
CROSSHAIR_LINE_COLOR: 'rgba(0,0,0,1)',
67100
CROSSHAIR_BORDER_COLOR: 'rgba(255,255,255,0.8)',
101+
TEXT_COLOR: 'rgba(255,255,255,1)',
102+
TEXT_BG_COLOR: 'rgba(0,0,0,0.8)',
103+
TEXT_BG_HEIGHT: 62,
104+
TEXT_BG_WIDTH: 62,
105+
TEXT_BG_CORNER_RADIUS: 7,
106+
TEXT_PADDING: 8,
107+
TEXT_FONT_SIZE: 12,
108+
TEXT_BG_X_OFFSET: 7,
68109
};
69110

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

88129
/**
89130
* The Konva objects that make up the color picker tool preview:
@@ -105,6 +146,9 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
105146
crosshairSouthOuter: Konva.Line;
106147
crosshairWestInner: Konva.Line;
107148
crosshairWestOuter: Konva.Line;
149+
rgbaTextGroup: Konva.Group;
150+
rgbaText: Konva.Text;
151+
rgbaTextBackground: Konva.Rect;
108152
};
109153

110154
constructor(parent: CanvasToolModule) {
@@ -202,8 +246,28 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
202246
stroke: this.config.CROSSHAIR_BORDER_COLOR,
203247
perfectDrawEnabled: false,
204248
}),
249+
rgbaTextGroup: new Konva.Group({
250+
listening: false,
251+
name: `${this.type}:color_picker_text_group`,
252+
}),
253+
rgbaText: new Konva.Text({
254+
listening: false,
255+
name: `${this.type}:color_picker_text`,
256+
fill: this.config.TEXT_COLOR,
257+
fontFamily: 'monospace',
258+
align: 'left',
259+
fontStyle: 'bold',
260+
verticalAlign: 'middle',
261+
}),
262+
rgbaTextBackground: new Konva.Rect({
263+
listening: false,
264+
name: `${this.type}:color_picker_text_background`,
265+
fill: this.config.TEXT_BG_COLOR,
266+
}),
205267
};
206268

269+
this.konva.rgbaTextGroup.add(this.konva.rgbaTextBackground, this.konva.rgbaText);
270+
207271
this.konva.group.add(
208272
this.konva.ringCandidateColor,
209273
this.konva.ringCurrentColor,
@@ -216,7 +280,8 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
216280
this.konva.crosshairSouthOuter,
217281
this.konva.crosshairSouthInner,
218282
this.konva.crosshairWestOuter,
219-
this.konva.crosshairWestInner
283+
this.konva.crosshairWestInner,
284+
this.konva.rgbaTextGroup
220285
);
221286
}
222287

@@ -278,6 +343,24 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
278343
outerRadius: colorPickerOuterRadius + twoPixels,
279344
});
280345

346+
const textBgWidth = this.manager.stage.unscale(this.config.TEXT_BG_WIDTH);
347+
const textBgHeight = this.manager.stage.unscale(this.config.TEXT_BG_HEIGHT);
348+
349+
this.konva.rgbaTextBackground.setAttrs({
350+
width: textBgWidth,
351+
height: textBgHeight,
352+
cornerRadius: this.manager.stage.unscale(this.config.TEXT_BG_CORNER_RADIUS),
353+
});
354+
this.konva.rgbaText.setAttrs({
355+
padding: this.manager.stage.unscale(this.config.TEXT_PADDING),
356+
fontSize: this.manager.stage.unscale(this.config.TEXT_FONT_SIZE),
357+
text: `R: ${colorUnderCursor.r}\nG: ${colorUnderCursor.g}\nB: ${colorUnderCursor.b}\nA: ${colorUnderCursor.a}`,
358+
});
359+
this.konva.rgbaTextGroup.setAttrs({
360+
x: x + this.manager.stage.unscale(this.config.RING_OUTER_RADIUS + this.config.TEXT_BG_X_OFFSET),
361+
y: y - textBgHeight / 2,
362+
});
363+
281364
const size = this.manager.stage.unscale(this.config.CROSSHAIR_LINE_LENGTH);
282365
const space = this.manager.stage.unscale(this.config.CROSSHAIR_INNER_RADIUS);
283366
const innerThickness = this.manager.stage.unscale(this.config.CROSSHAIR_LINE_THICKNESS);
@@ -324,11 +407,8 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
324407

325408
onStagePointerUp = (_e: KonvaEventObject<PointerEvent>) => {
326409
const color = this.$colorUnderCursor.get();
327-
if (color) {
328-
const settings = this.manager.stateApi.getSettings();
329-
// This will update the color but not the alpha value
330-
this.manager.stateApi.setColor({ ...settings.color, ...color });
331-
}
410+
const settings = this.manager.stateApi.getSettings();
411+
this.manager.stateApi.setColor({ ...settings.color, ...color });
332412
};
333413

334414
onStagePointerMove = (_e: KonvaEventObject<PointerEvent>) => {
@@ -341,7 +421,11 @@ export class CanvasColorPickerToolModule extends CanvasModuleBase {
341421
return;
342422
}
343423

424+
// Hide the background layer so we can get the color under the cursor without the grid interfering
425+
this.manager.background.konva.layer.visible(false);
344426
const color = getColorAtCoordinate(this.manager.stage.konva.stage, cursorPos.absolute);
427+
this.manager.background.konva.layer.visible(true);
428+
345429
if (color) {
346430
this.$colorUnderCursor.set(color);
347431
}

invokeai/frontend/web/src/features/controlLayers/konva/util.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
Coordinate,
88
CoordinateWithPressure,
99
Rect,
10+
RgbaColor,
1011
} from 'features/controlLayers/store/types';
1112
import type Konva from 'konva';
1213
import type { KonvaEventObject } from 'konva/lib/Node';
@@ -15,7 +16,6 @@ import { clamp } from 'lodash-es';
1516
import { customAlphabet } from 'nanoid';
1617
import type { StrokeOptions } from 'perfect-freehand';
1718
import getStroke from 'perfect-freehand';
18-
import type { RgbColor } from 'react-colorful';
1919
import { assert } from 'tsafe';
2020

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

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

738-
if (r === undefined || g === undefined || b === undefined) {
738+
if (r === undefined || g === undefined || b === undefined || a === undefined) {
739739
return null;
740740
}
741741

742-
return { r, g, b };
742+
return { r, g, b, a };
743743
};
744744

745745
export const roundRect = (rect: Rect): Rect => {

0 commit comments

Comments
 (0)