mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds color picker
This commit is contained in:
parent
d44112c209
commit
9f1c1cf2e6
@ -7,7 +7,7 @@ import {
|
|||||||
isStagingSelector,
|
isStagingSelector,
|
||||||
} from 'features/canvas/store/canvasSelectors';
|
} from 'features/canvas/store/canvasSelectors';
|
||||||
import IAICanvasMaskLines from './IAICanvasMaskLines';
|
import IAICanvasMaskLines from './IAICanvasMaskLines';
|
||||||
import IAICanvasBrushPreview from './IAICanvasBrushPreview';
|
import IAICanvasToolPreview from './IAICanvasToolPreview';
|
||||||
import { Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
|
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
|
||||||
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
|
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
|
||||||
@ -183,7 +183,7 @@ const IAICanvas = () => {
|
|||||||
</Layer>
|
</Layer>
|
||||||
<Layer id="preview" imageSmoothingEnabled={false}>
|
<Layer id="preview" imageSmoothingEnabled={false}>
|
||||||
{!isStaging && (
|
{!isStaging && (
|
||||||
<IAICanvasBrushPreview
|
<IAICanvasToolPreview
|
||||||
visible={tool !== 'move'}
|
visible={tool !== 'move'}
|
||||||
listening={false}
|
listening={false}
|
||||||
/>
|
/>
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { GroupConfig } from 'konva/lib/Group';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { Circle, Group } from 'react-konva';
|
|
||||||
import { useAppSelector } from 'app/store';
|
|
||||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
|
||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
|
||||||
|
|
||||||
const canvasBrushPreviewSelector = createSelector(
|
|
||||||
canvasSelector,
|
|
||||||
(canvas) => {
|
|
||||||
const {
|
|
||||||
cursorPosition,
|
|
||||||
stageDimensions: { width, height },
|
|
||||||
brushSize,
|
|
||||||
maskColor,
|
|
||||||
brushColor,
|
|
||||||
tool,
|
|
||||||
layer,
|
|
||||||
shouldShowBrush,
|
|
||||||
isMovingBoundingBox,
|
|
||||||
isTransformingBoundingBox,
|
|
||||||
stageScale,
|
|
||||||
} = canvas;
|
|
||||||
|
|
||||||
return {
|
|
||||||
cursorPosition,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
radius: brushSize / 2,
|
|
||||||
brushColorString: rgbaColorToString(
|
|
||||||
layer === 'mask' ? { ...maskColor, a: 0.5 } : brushColor
|
|
||||||
),
|
|
||||||
tool,
|
|
||||||
shouldShowBrush,
|
|
||||||
shouldDrawBrushPreview:
|
|
||||||
!(
|
|
||||||
isMovingBoundingBox ||
|
|
||||||
isTransformingBoundingBox ||
|
|
||||||
!cursorPosition
|
|
||||||
) && shouldShowBrush,
|
|
||||||
strokeWidth: 1.5 / stageScale,
|
|
||||||
dotRadius: 1.5 / stageScale,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
memoizeOptions: {
|
|
||||||
resultEqualityCheck: _.isEqual,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a black circle around the canvas brush preview.
|
|
||||||
*/
|
|
||||||
const IAICanvasBrushPreview = (props: GroupConfig) => {
|
|
||||||
const { ...rest } = props;
|
|
||||||
const {
|
|
||||||
cursorPosition,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
radius,
|
|
||||||
brushColorString,
|
|
||||||
tool,
|
|
||||||
shouldDrawBrushPreview,
|
|
||||||
dotRadius,
|
|
||||||
strokeWidth,
|
|
||||||
} = useAppSelector(canvasBrushPreviewSelector);
|
|
||||||
|
|
||||||
if (!shouldDrawBrushPreview) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Group listening={false} {...rest}>
|
|
||||||
<Circle
|
|
||||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
|
||||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
|
||||||
radius={radius}
|
|
||||||
fill={brushColorString}
|
|
||||||
listening={false}
|
|
||||||
globalCompositeOperation={
|
|
||||||
tool === 'eraser' ? 'destination-out' : 'source-over'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Circle
|
|
||||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
|
||||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
|
||||||
radius={radius}
|
|
||||||
stroke={'rgba(255,255,255,0.4)'}
|
|
||||||
strokeWidth={strokeWidth * 2}
|
|
||||||
strokeEnabled={true}
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
<Circle
|
|
||||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
|
||||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
|
||||||
radius={radius}
|
|
||||||
stroke={'rgba(0,0,0,1)'}
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
strokeEnabled={true}
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
<Circle
|
|
||||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
|
||||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
|
||||||
radius={dotRadius * 2}
|
|
||||||
fill={'rgba(255,255,255,0.4)'}
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
<Circle
|
|
||||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
|
||||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
|
||||||
radius={dotRadius}
|
|
||||||
fill={'rgba(0,0,0,1)'}
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IAICanvasBrushPreview;
|
|
185
frontend/src/features/canvas/components/IAICanvasToolPreview.tsx
Normal file
185
frontend/src/features/canvas/components/IAICanvasToolPreview.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { GroupConfig } from 'konva/lib/Group';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { Circle, Group, Rect } from 'react-konva';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
|
import { COLOR_PICKER_SIZE } from '../util/constants';
|
||||||
|
|
||||||
|
const canvasBrushPreviewSelector = createSelector(
|
||||||
|
canvasSelector,
|
||||||
|
(canvas) => {
|
||||||
|
const {
|
||||||
|
cursorPosition,
|
||||||
|
stageDimensions: { width, height },
|
||||||
|
brushSize,
|
||||||
|
colorPickerColor,
|
||||||
|
maskColor,
|
||||||
|
brushColor,
|
||||||
|
tool,
|
||||||
|
layer,
|
||||||
|
shouldShowBrush,
|
||||||
|
isMovingBoundingBox,
|
||||||
|
isTransformingBoundingBox,
|
||||||
|
stageScale,
|
||||||
|
} = canvas;
|
||||||
|
|
||||||
|
let fill = '';
|
||||||
|
|
||||||
|
if (layer === 'mask') {
|
||||||
|
fill = rgbaColorToString({ ...maskColor, a: 0.5 });
|
||||||
|
} else if (tool === 'colorPicker') {
|
||||||
|
fill = rgbaColorToString(colorPickerColor);
|
||||||
|
} else {
|
||||||
|
fill = rgbaColorToString(brushColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cursorPosition,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
radius: brushSize / 2,
|
||||||
|
colorPickerSize: COLOR_PICKER_SIZE / stageScale,
|
||||||
|
colorPickerOffset: COLOR_PICKER_SIZE / 2 / stageScale,
|
||||||
|
colorPickerCornerRadius: COLOR_PICKER_SIZE / 5 / stageScale,
|
||||||
|
brushColorString: fill,
|
||||||
|
tool,
|
||||||
|
shouldShowBrush,
|
||||||
|
shouldDrawBrushPreview:
|
||||||
|
!(
|
||||||
|
isMovingBoundingBox ||
|
||||||
|
isTransformingBoundingBox ||
|
||||||
|
!cursorPosition
|
||||||
|
) && shouldShowBrush,
|
||||||
|
strokeWidth: 1.5 / stageScale,
|
||||||
|
dotRadius: 1.5 / stageScale,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: _.isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a black circle around the canvas brush preview.
|
||||||
|
*/
|
||||||
|
const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||||
|
const { ...rest } = props;
|
||||||
|
const {
|
||||||
|
cursorPosition,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
radius,
|
||||||
|
brushColorString,
|
||||||
|
tool,
|
||||||
|
shouldDrawBrushPreview,
|
||||||
|
dotRadius,
|
||||||
|
strokeWidth,
|
||||||
|
colorPickerSize,
|
||||||
|
colorPickerOffset,
|
||||||
|
colorPickerCornerRadius,
|
||||||
|
} = useAppSelector(canvasBrushPreviewSelector);
|
||||||
|
|
||||||
|
if (!shouldDrawBrushPreview) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group listening={false} {...rest}>
|
||||||
|
{tool === 'colorPicker' ? (
|
||||||
|
<>
|
||||||
|
<Rect
|
||||||
|
x={
|
||||||
|
cursorPosition ? cursorPosition.x - colorPickerOffset : width / 2
|
||||||
|
}
|
||||||
|
y={
|
||||||
|
cursorPosition ? cursorPosition.y - colorPickerOffset : height / 2
|
||||||
|
}
|
||||||
|
width={colorPickerSize}
|
||||||
|
height={colorPickerSize}
|
||||||
|
fill={brushColorString}
|
||||||
|
cornerRadius={colorPickerCornerRadius}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
<Rect
|
||||||
|
x={
|
||||||
|
cursorPosition ? cursorPosition.x - colorPickerOffset : width / 2
|
||||||
|
}
|
||||||
|
y={
|
||||||
|
cursorPosition ? cursorPosition.y - colorPickerOffset : height / 2
|
||||||
|
}
|
||||||
|
width={colorPickerSize}
|
||||||
|
height={colorPickerSize}
|
||||||
|
cornerRadius={colorPickerCornerRadius}
|
||||||
|
stroke={'rgba(255,255,255,0.4)'}
|
||||||
|
strokeWidth={strokeWidth * 2}
|
||||||
|
strokeEnabled={true}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
<Rect
|
||||||
|
x={
|
||||||
|
cursorPosition ? cursorPosition.x - colorPickerOffset : width / 2
|
||||||
|
}
|
||||||
|
y={
|
||||||
|
cursorPosition ? cursorPosition.y - colorPickerOffset : height / 2
|
||||||
|
}
|
||||||
|
width={colorPickerSize}
|
||||||
|
height={colorPickerSize}
|
||||||
|
cornerRadius={colorPickerCornerRadius}
|
||||||
|
stroke={'rgba(0,0,0,1)'}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeEnabled={true}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Circle
|
||||||
|
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||||
|
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||||
|
radius={radius}
|
||||||
|
fill={brushColorString}
|
||||||
|
globalCompositeOperation={
|
||||||
|
tool === 'eraser' ? 'destination-out' : 'source-over'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Circle
|
||||||
|
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||||
|
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||||
|
radius={radius}
|
||||||
|
stroke={'rgba(255,255,255,0.4)'}
|
||||||
|
strokeWidth={strokeWidth * 2}
|
||||||
|
strokeEnabled={true}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
<Circle
|
||||||
|
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||||
|
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||||
|
radius={radius}
|
||||||
|
stroke={'rgba(0,0,0,1)'}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
strokeEnabled={true}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Circle
|
||||||
|
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||||
|
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||||
|
radius={dotRadius * 2}
|
||||||
|
fill={'rgba(255,255,255,0.4)'}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
<Circle
|
||||||
|
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||||
|
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||||
|
radius={dotRadius}
|
||||||
|
fill={'rgba(0,0,0,1)'}
|
||||||
|
listening={false}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAICanvasToolPreview;
|
@ -8,7 +8,12 @@ import {
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { FaEraser, FaPaintBrush, FaSlidersH } from 'react-icons/fa';
|
import {
|
||||||
|
FaEraser,
|
||||||
|
FaEyeDropper,
|
||||||
|
FaPaintBrush,
|
||||||
|
FaSlidersH,
|
||||||
|
} from 'react-icons/fa';
|
||||||
import {
|
import {
|
||||||
canvasSelector,
|
canvasSelector,
|
||||||
isStagingSelector,
|
isStagingSelector,
|
||||||
@ -68,6 +73,18 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
[tool]
|
[tool]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useHotkeys(
|
||||||
|
['c'],
|
||||||
|
() => {
|
||||||
|
handleSelectColorPickerTool();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: () => true,
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
[tool]
|
||||||
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['['],
|
['['],
|
||||||
() => {
|
() => {
|
||||||
@ -94,6 +111,7 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
|
|
||||||
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
||||||
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
||||||
|
const handleSelectColorPickerTool = () => dispatch(setTool('colorPicker'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
@ -111,7 +129,15 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
icon={<FaEraser />}
|
icon={<FaEraser />}
|
||||||
data-selected={tool === 'eraser' && !isStaging}
|
data-selected={tool === 'eraser' && !isStaging}
|
||||||
isDisabled={isStaging}
|
isDisabled={isStaging}
|
||||||
onClick={() => dispatch(setTool('eraser'))}
|
onClick={handleSelectEraserTool}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Color Picker (C)"
|
||||||
|
tooltip="Color Picker (C)"
|
||||||
|
icon={<FaEyeDropper />}
|
||||||
|
data-selected={tool === 'colorPicker' && !isStaging}
|
||||||
|
isDisabled={isStaging}
|
||||||
|
onClick={handleSelectColorPickerTool}
|
||||||
/>
|
/>
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
|
@ -5,13 +5,19 @@ import Konva from 'konva';
|
|||||||
import { KonvaEventObject } from 'konva/lib/Node';
|
import { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { MutableRefObject, useCallback } from 'react';
|
import { MutableRefObject, useCallback } from 'react';
|
||||||
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import {
|
||||||
|
canvasSelector,
|
||||||
|
isStagingSelector,
|
||||||
|
} from 'features/canvas/store/canvasSelectors';
|
||||||
import {
|
import {
|
||||||
addLine,
|
addLine,
|
||||||
|
commitColorPickerColor,
|
||||||
setIsDrawing,
|
setIsDrawing,
|
||||||
setIsMovingStage,
|
setIsMovingStage,
|
||||||
|
setTool,
|
||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import getScaledCursorPosition from '../util/getScaledCursorPosition';
|
import getScaledCursorPosition from '../util/getScaledCursorPosition';
|
||||||
|
import useColorPicker from './useColorUnderCursor';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[activeTabNameSelector, canvasSelector, isStagingSelector],
|
[activeTabNameSelector, canvasSelector, isStagingSelector],
|
||||||
@ -29,6 +35,7 @@ const selector = createSelector(
|
|||||||
const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { tool, isStaging } = useAppSelector(selector);
|
const { tool, isStaging } = useAppSelector(selector);
|
||||||
|
const { commitColorUnderCursor } = useColorPicker();
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
(e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||||
@ -41,6 +48,11 @@ const useCanvasMouseDown = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tool === 'colorPicker') {
|
||||||
|
commitColorUnderCursor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||||
|
|
||||||
if (!scaledCursorPosition) return;
|
if (!scaledCursorPosition) return;
|
||||||
|
@ -5,12 +5,16 @@ import Konva from 'konva';
|
|||||||
import { Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { MutableRefObject, useCallback } from 'react';
|
import { MutableRefObject, useCallback } from 'react';
|
||||||
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import {
|
||||||
|
canvasSelector,
|
||||||
|
isStagingSelector,
|
||||||
|
} from 'features/canvas/store/canvasSelectors';
|
||||||
import {
|
import {
|
||||||
addPointToCurrentLine,
|
addPointToCurrentLine,
|
||||||
setCursorPosition,
|
setCursorPosition,
|
||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import getScaledCursorPosition from '../util/getScaledCursorPosition';
|
import getScaledCursorPosition from '../util/getScaledCursorPosition';
|
||||||
|
import useColorPicker from './useColorUnderCursor';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[activeTabNameSelector, canvasSelector, isStagingSelector],
|
[activeTabNameSelector, canvasSelector, isStagingSelector],
|
||||||
@ -33,6 +37,7 @@ const useCanvasMouseMove = (
|
|||||||
) => {
|
) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { isDrawing, tool, isStaging } = useAppSelector(selector);
|
const { isDrawing, tool, isStaging } = useAppSelector(selector);
|
||||||
|
const { updateColorUnderCursor } = useColorPicker();
|
||||||
|
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
if (!stageRef.current) return;
|
if (!stageRef.current) return;
|
||||||
@ -45,6 +50,11 @@ const useCanvasMouseMove = (
|
|||||||
|
|
||||||
lastCursorPositionRef.current = scaledCursorPosition;
|
lastCursorPositionRef.current = scaledCursorPosition;
|
||||||
|
|
||||||
|
if (tool === 'colorPicker') {
|
||||||
|
updateColorUnderCursor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isDrawing || tool === 'move' || isStaging) return;
|
if (!isDrawing || tool === 'move' || isStaging) return;
|
||||||
|
|
||||||
didMouseMoveRef.current = true;
|
didMouseMoveRef.current = true;
|
||||||
@ -59,6 +69,7 @@ const useCanvasMouseMove = (
|
|||||||
lastCursorPositionRef,
|
lastCursorPositionRef,
|
||||||
stageRef,
|
stageRef,
|
||||||
tool,
|
tool,
|
||||||
|
updateColorUnderCursor,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
45
frontend/src/features/canvas/hooks/useColorUnderCursor.ts
Normal file
45
frontend/src/features/canvas/hooks/useColorUnderCursor.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useAppDispatch } from 'app/store';
|
||||||
|
import Konva from 'konva';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import {
|
||||||
|
commitColorPickerColor,
|
||||||
|
setColorPickerColor,
|
||||||
|
} from '../store/canvasSlice';
|
||||||
|
import {
|
||||||
|
getCanvasBaseLayer,
|
||||||
|
getCanvasStage,
|
||||||
|
} from '../util/konvaInstanceProvider';
|
||||||
|
|
||||||
|
const useColorPicker = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const canvasBaseLayer = getCanvasBaseLayer();
|
||||||
|
const stage = getCanvasStage();
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateColorUnderCursor: () => {
|
||||||
|
if (!stage || !canvasBaseLayer) return;
|
||||||
|
|
||||||
|
const position = stage.getPointerPosition();
|
||||||
|
|
||||||
|
if (!position) return;
|
||||||
|
|
||||||
|
const pixelRatio = Konva.pixelRatio;
|
||||||
|
|
||||||
|
const [r, g, b, a] = canvasBaseLayer
|
||||||
|
.getContext()
|
||||||
|
.getImageData(
|
||||||
|
position.x * pixelRatio,
|
||||||
|
position.y * pixelRatio,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
).data;
|
||||||
|
|
||||||
|
dispatch(setColorPickerColor({ r, g, b, a }));
|
||||||
|
},
|
||||||
|
commitColorUnderCursor: () => {
|
||||||
|
dispatch(commitColorPickerColor());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useColorPicker;
|
@ -44,6 +44,7 @@ const initialCanvasState: CanvasState = {
|
|||||||
brushColor: { r: 90, g: 90, b: 255, a: 1 },
|
brushColor: { r: 90, g: 90, b: 255, a: 1 },
|
||||||
brushSize: 50,
|
brushSize: 50,
|
||||||
canvasContainerDimensions: { width: 0, height: 0 },
|
canvasContainerDimensions: { width: 0, height: 0 },
|
||||||
|
colorPickerColor: { r: 90, g: 90, b: 255, a: 1 },
|
||||||
cursorPosition: null,
|
cursorPosition: null,
|
||||||
doesCanvasNeedScaling: false,
|
doesCanvasNeedScaling: false,
|
||||||
futureLayerStates: [],
|
futureLayerStates: [],
|
||||||
@ -345,7 +346,7 @@ export const canvasSlice = createSlice({
|
|||||||
addLine: (state, action: PayloadAction<number[]>) => {
|
addLine: (state, action: PayloadAction<number[]>) => {
|
||||||
const { tool, layer, brushColor, brushSize } = state;
|
const { tool, layer, brushColor, brushSize } = state;
|
||||||
|
|
||||||
if (tool === 'move') return;
|
if (tool === 'move' || tool === 'colorPicker') return;
|
||||||
|
|
||||||
const newStrokeWidth = brushSize / 2;
|
const newStrokeWidth = brushSize / 2;
|
||||||
|
|
||||||
@ -683,6 +684,13 @@ export const canvasSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
state.shouldCropToBoundingBoxOnSave = action.payload;
|
state.shouldCropToBoundingBoxOnSave = action.payload;
|
||||||
},
|
},
|
||||||
|
setColorPickerColor: (state, action: PayloadAction<RgbaColor>) => {
|
||||||
|
state.colorPickerColor = action.payload;
|
||||||
|
},
|
||||||
|
commitColorPickerColor: (state) => {
|
||||||
|
state.brushColor = state.colorPickerColor;
|
||||||
|
state.tool = 'brush';
|
||||||
|
},
|
||||||
setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => {
|
setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => {
|
||||||
state.pastLayerStates.push({
|
state.pastLayerStates.push({
|
||||||
...state.layerState,
|
...state.layerState,
|
||||||
@ -710,6 +718,8 @@ export const {
|
|||||||
addLine,
|
addLine,
|
||||||
addPointToCurrentLine,
|
addPointToCurrentLine,
|
||||||
clearMask,
|
clearMask,
|
||||||
|
commitColorPickerColor,
|
||||||
|
setColorPickerColor,
|
||||||
commitStagingAreaImage,
|
commitStagingAreaImage,
|
||||||
discardStagedImages,
|
discardStagedImages,
|
||||||
fitBoundingBoxToStage,
|
fitBoundingBoxToStage,
|
||||||
|
@ -13,7 +13,7 @@ export type CanvasLayer = typeof LAYER_NAMES[number];
|
|||||||
|
|
||||||
export type CanvasDrawingTool = 'brush' | 'eraser';
|
export type CanvasDrawingTool = 'brush' | 'eraser';
|
||||||
|
|
||||||
export type CanvasTool = CanvasDrawingTool | 'move';
|
export type CanvasTool = CanvasDrawingTool | 'move' | 'colorPicker';
|
||||||
|
|
||||||
export type Dimensions = {
|
export type Dimensions = {
|
||||||
width: number;
|
width: number;
|
||||||
@ -81,6 +81,7 @@ export interface CanvasState {
|
|||||||
brushColor: RgbaColor;
|
brushColor: RgbaColor;
|
||||||
brushSize: number;
|
brushSize: number;
|
||||||
canvasContainerDimensions: Dimensions;
|
canvasContainerDimensions: Dimensions;
|
||||||
|
colorPickerColor: RgbaColor,
|
||||||
cursorPosition: Vector2d | null;
|
cursorPosition: Vector2d | null;
|
||||||
doesCanvasNeedScaling: boolean;
|
doesCanvasNeedScaling: boolean;
|
||||||
futureLayerStates: CanvasLayerState[];
|
futureLayerStates: CanvasLayerState[];
|
||||||
|
@ -12,3 +12,5 @@ export const MAX_CANVAS_SCALE = 20;
|
|||||||
|
|
||||||
// padding given to initial image/bounding box when stage view is reset
|
// padding given to initial image/bounding box when stage view is reset
|
||||||
export const STAGE_PADDING_PERCENTAGE = 0.95;
|
export const STAGE_PADDING_PERCENTAGE = 0.95;
|
||||||
|
|
||||||
|
export const COLOR_PICKER_SIZE = 60;
|
||||||
|
@ -140,22 +140,22 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
|||||||
const unifiedCanvasHotkeys = [
|
const unifiedCanvasHotkeys = [
|
||||||
{
|
{
|
||||||
title: 'Select Brush',
|
title: 'Select Brush',
|
||||||
desc: 'Selects the inpainting brush',
|
desc: 'Selects the canvas brush',
|
||||||
hotkey: 'B',
|
hotkey: 'B',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Select Eraser',
|
title: 'Select Eraser',
|
||||||
desc: 'Selects the inpainting eraser',
|
desc: 'Selects the canvas eraser',
|
||||||
hotkey: 'E',
|
hotkey: 'E',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Decrease Brush Size',
|
title: 'Decrease Brush Size',
|
||||||
desc: 'Decreases the size of the inpainting brush/eraser',
|
desc: 'Decreases the size of the canvas brush/eraser',
|
||||||
hotkey: '[',
|
hotkey: '[',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Increase Brush Size',
|
title: 'Increase Brush Size',
|
||||||
desc: 'Increases the size of the inpainting brush/eraser',
|
desc: 'Increases the size of the canvas brush/eraser',
|
||||||
hotkey: ']',
|
hotkey: ']',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -163,6 +163,11 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
|||||||
desc: 'Allows canvas navigation',
|
desc: 'Allows canvas navigation',
|
||||||
hotkey: 'V',
|
hotkey: 'V',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Select Color Picker',
|
||||||
|
desc: 'Selects the canvas color picker',
|
||||||
|
hotkey: 'C',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Quick Toggle Move',
|
title: 'Quick Toggle Move',
|
||||||
desc: 'Temporarily toggles Move mode',
|
desc: 'Temporarily toggles Move mode',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user