mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-06 08:05:03 -05:00
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.
This commit is contained in:
@@ -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';
|
||||
@@ -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 = {
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -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:
|
||||
@@ -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) {
|
||||
@@ -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,
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -278,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);
|
||||
@@ -324,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>) => {
|
||||
@@ -341,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);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
@@ -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');
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user