mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds bounding box handles
This commit is contained in:
parent
44599a239f
commit
9e2ce00f7b
@ -1,3 +1,7 @@
|
|||||||
export const roundDownToMultiple = (num: number, multiple: number): number => {
|
export const roundDownToMultiple = (num: number, multiple: number): number => {
|
||||||
return Math.floor(num / multiple) * multiple;
|
return Math.floor(num / multiple) * multiple;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const roundToMultiple = (num: number, multiple: number): number => {
|
||||||
|
return Math.round(num / multiple) * multiple;
|
||||||
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// lib
|
// lib
|
||||||
import {
|
import {
|
||||||
KeyboardEvent,
|
|
||||||
MutableRefObject,
|
MutableRefObject,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -20,7 +19,6 @@ import {
|
|||||||
setBoundingBoxCoordinate,
|
setBoundingBoxCoordinate,
|
||||||
setCursorPosition,
|
setCursorPosition,
|
||||||
setIsMovingBoundingBox,
|
setIsMovingBoundingBox,
|
||||||
setTool,
|
|
||||||
} from './inpaintingSlice';
|
} from './inpaintingSlice';
|
||||||
import { inpaintingCanvasSelector } from './inpaintingSliceSelectors';
|
import { inpaintingCanvasSelector } from './inpaintingSliceSelectors';
|
||||||
|
|
||||||
@ -32,7 +30,9 @@ import Cacher from './components/Cacher';
|
|||||||
import { Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import getScaledCursorPosition from './util/getScaledCursorPosition';
|
import getScaledCursorPosition from './util/getScaledCursorPosition';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import InpaintingBoundingBoxPreview from './components/InpaintingBoundingBoxPreview';
|
import InpaintingBoundingBoxPreview, {
|
||||||
|
InpaintingBoundingBoxPreviewOverlay,
|
||||||
|
} from './components/InpaintingBoundingBoxPreview';
|
||||||
import { KonvaEventObject } from 'konva/lib/Node';
|
import { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import KeyboardEventManager from './components/KeyboardEventManager';
|
import KeyboardEventManager from './components/KeyboardEventManager';
|
||||||
|
|
||||||
@ -50,13 +50,14 @@ const InpaintingCanvas = () => {
|
|||||||
shouldInvertMask,
|
shouldInvertMask,
|
||||||
shouldShowMask,
|
shouldShowMask,
|
||||||
shouldShowCheckboardTransparency,
|
shouldShowCheckboardTransparency,
|
||||||
maskOpacity,
|
maskColor,
|
||||||
imageToInpaint,
|
imageToInpaint,
|
||||||
isMovingBoundingBox,
|
isMovingBoundingBox,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
canvasDimensions,
|
canvasDimensions,
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinate,
|
||||||
stageScale,
|
stageScale,
|
||||||
|
shouldShowBoundingBoxFill,
|
||||||
} = useAppSelector(inpaintingCanvasSelector);
|
} = useAppSelector(inpaintingCanvasSelector);
|
||||||
|
|
||||||
// set the closure'd refs
|
// set the closure'd refs
|
||||||
@ -248,7 +249,7 @@ const InpaintingCanvas = () => {
|
|||||||
opacity={
|
opacity={
|
||||||
shouldShowCheckboardTransparency || shouldInvertMask
|
shouldShowCheckboardTransparency || shouldInvertMask
|
||||||
? 1
|
? 1
|
||||||
: maskOpacity
|
: maskColor.a
|
||||||
}
|
}
|
||||||
ref={maskLayerRef}
|
ref={maskLayerRef}
|
||||||
>
|
>
|
||||||
@ -270,8 +271,11 @@ const InpaintingCanvas = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Layer>
|
</Layer>
|
||||||
<Layer name={'preview-layer'} listening={false}>
|
<Layer name={'preview-layer'}>
|
||||||
<InpaintingCanvasBrushPreviewOutline />
|
<InpaintingCanvasBrushPreviewOutline />
|
||||||
|
{shouldShowBoundingBoxFill && (
|
||||||
|
<InpaintingBoundingBoxPreviewOverlay />
|
||||||
|
)}
|
||||||
<InpaintingBoundingBoxPreview />
|
<InpaintingBoundingBoxPreview />
|
||||||
</Layer>
|
</Layer>
|
||||||
</>
|
</>
|
||||||
@ -284,6 +288,4 @@ const InpaintingCanvas = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
export default InpaintingCanvas;
|
export default InpaintingCanvas;
|
||||||
|
@ -6,32 +6,29 @@ import {
|
|||||||
FaPalette,
|
FaPalette,
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaRedo,
|
FaRedo,
|
||||||
|
FaTint,
|
||||||
|
FaTintSlash,
|
||||||
FaUndo,
|
FaUndo,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { BiHide, BiShow } from 'react-icons/bi';
|
import { BiHide, BiShow } from 'react-icons/bi';
|
||||||
import { VscSplitHorizontal } from 'react-icons/vsc';
|
import { VscSplitHorizontal } from 'react-icons/vsc';
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../../app/store';
|
import { useAppDispatch, useAppSelector } from '../../../app/store';
|
||||||
import IAIIconButton from '../../../common/components/IAIIconButton';
|
import IAIIconButton from '../../../common/components/IAIIconButton';
|
||||||
import {
|
import {
|
||||||
clearMask,
|
clearMask,
|
||||||
redo,
|
redo,
|
||||||
setMaskColor,
|
setMaskColor,
|
||||||
setBrushSize,
|
setBrushSize,
|
||||||
setMaskOpacity,
|
|
||||||
setShouldShowBrushPreview,
|
setShouldShowBrushPreview,
|
||||||
setTool,
|
setTool,
|
||||||
undo,
|
undo,
|
||||||
setShouldShowMask,
|
setShouldShowMask,
|
||||||
setShouldInvertMask,
|
setShouldInvertMask,
|
||||||
setNeedsRepaint,
|
setNeedsRepaint,
|
||||||
|
setShouldShowBoundingBoxFill,
|
||||||
} from './inpaintingSlice';
|
} from './inpaintingSlice';
|
||||||
|
|
||||||
import { tabMap } from '../InvokeTabs';
|
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
|
||||||
import {
|
|
||||||
MdInvertColors,
|
|
||||||
MdInvertColorsOff,
|
|
||||||
MdOutlineCloseFullscreen,
|
|
||||||
} from 'react-icons/md';
|
|
||||||
import IAISlider from '../../../common/components/IAISlider';
|
import IAISlider from '../../../common/components/IAISlider';
|
||||||
import IAINumberInput from '../../../common/components/IAINumberInput';
|
import IAINumberInput from '../../../common/components/IAINumberInput';
|
||||||
import { inpaintingControlsSelector } from './inpaintingSliceSelectors';
|
import { inpaintingControlsSelector } from './inpaintingSliceSelectors';
|
||||||
@ -39,14 +36,12 @@ import IAIPopover from '../../../common/components/IAIPopover';
|
|||||||
import IAIColorPicker from '../../../common/components/IAIColorPicker';
|
import IAIColorPicker from '../../../common/components/IAIColorPicker';
|
||||||
import { RgbaColor } from 'react-colorful';
|
import { RgbaColor } from 'react-colorful';
|
||||||
import { setShowDualDisplay } from '../../options/optionsSlice';
|
import { setShowDualDisplay } from '../../options/optionsSlice';
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
const InpaintingControls = () => {
|
const InpaintingControls = () => {
|
||||||
const {
|
const {
|
||||||
tool,
|
tool,
|
||||||
brushSize,
|
brushSize,
|
||||||
maskColor,
|
maskColor,
|
||||||
maskOpacity,
|
|
||||||
shouldInvertMask,
|
shouldInvertMask,
|
||||||
shouldShowMask,
|
shouldShowMask,
|
||||||
canUndo,
|
canUndo,
|
||||||
@ -54,12 +49,17 @@ const InpaintingControls = () => {
|
|||||||
isMaskEmpty,
|
isMaskEmpty,
|
||||||
activeTabName,
|
activeTabName,
|
||||||
showDualDisplay,
|
showDualDisplay,
|
||||||
|
shouldShowBoundingBoxFill,
|
||||||
} = useAppSelector(inpaintingControlsSelector);
|
} = useAppSelector(inpaintingControlsSelector);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
// Hotkeys
|
/**
|
||||||
|
* Hotkeys
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Decrease brush size
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'[',
|
'[',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -76,6 +76,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldShowMask, brushSize]
|
[activeTabName, shouldShowMask, brushSize]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Increase brush size
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
']',
|
']',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -88,30 +89,39 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldShowMask, brushSize]
|
[activeTabName, shouldShowMask, brushSize]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Decrease mask opacity
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+[',
|
'shift+[',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleChangeMaskOpacity(Math.max(maskOpacity - 0.05, 0));
|
handleChangeMaskColor({
|
||||||
|
...maskColor,
|
||||||
|
a: Math.max(maskColor.a - 0.05, 0),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
},
|
},
|
||||||
[activeTabName, shouldShowMask, maskOpacity]
|
[activeTabName, shouldShowMask, maskColor.a]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Increase mask opacity
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+]',
|
'shift+]',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleChangeMaskOpacity(Math.min(maskOpacity + 0.05, 100));
|
handleChangeMaskColor({
|
||||||
|
...maskColor,
|
||||||
|
a: Math.min(maskColor.a + 0.05, 100),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
enabled: activeTabName === 'inpainting' && shouldShowMask,
|
||||||
},
|
},
|
||||||
[activeTabName, shouldShowMask, maskOpacity]
|
[activeTabName, shouldShowMask, maskColor.a]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set tool to eraser
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'e',
|
'e',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -125,6 +135,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldShowMask]
|
[activeTabName, shouldShowMask]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set tool to brush
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'b',
|
'b',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -137,6 +148,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldShowMask]
|
[activeTabName, shouldShowMask]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Undo
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'cmd+z, control+z',
|
'cmd+z, control+z',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -149,6 +161,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldShowMask, canUndo]
|
[activeTabName, shouldShowMask, canUndo]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Redo
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'cmd+shift+z, control+shift+z, control+y, cmd+y',
|
'cmd+shift+z, control+shift+z, control+y, cmd+y',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -161,6 +174,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldShowMask, canRedo]
|
[activeTabName, shouldShowMask, canRedo]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Show/hide mask
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'h',
|
'h',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -173,6 +187,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldShowMask]
|
[activeTabName, shouldShowMask]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Invert mask
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+m',
|
'shift+m',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -185,6 +200,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, shouldInvertMask, shouldShowMask]
|
[activeTabName, shouldInvertMask, shouldShowMask]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Clear mask
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+c',
|
'shift+c',
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
@ -203,6 +219,7 @@ const InpaintingControls = () => {
|
|||||||
[activeTabName, isMaskEmpty, shouldShowMask]
|
[activeTabName, isMaskEmpty, shouldShowMask]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Toggle split view
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'shift+j',
|
'shift+j',
|
||||||
() => {
|
() => {
|
||||||
@ -224,10 +241,6 @@ const InpaintingControls = () => {
|
|||||||
dispatch(setBrushSize(v));
|
dispatch(setBrushSize(v));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeMaskOpacity = (v: number) => {
|
|
||||||
dispatch(setMaskOpacity(v));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleShouldShowMask = () =>
|
const handleToggleShouldShowMask = () =>
|
||||||
dispatch(setShouldShowMask(!shouldShowMask));
|
dispatch(setShouldShowMask(!shouldShowMask));
|
||||||
|
|
||||||
@ -242,10 +255,8 @@ const InpaintingControls = () => {
|
|||||||
dispatch(setShouldShowBrushPreview(false));
|
dispatch(setShouldShowBrushPreview(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeBrushColor = (newColor: RgbaColor) => {
|
const handleChangeMaskColor = (newColor: RgbaColor) => {
|
||||||
const { r, g, b, a: maskOpacity } = newColor;
|
dispatch(setMaskColor(newColor));
|
||||||
dispatch(setMaskColor({ r, g, b }));
|
|
||||||
dispatch(setMaskOpacity(maskOpacity));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUndo = () => dispatch(undo());
|
const handleUndo = () => dispatch(undo());
|
||||||
@ -257,6 +268,10 @@ const InpaintingControls = () => {
|
|||||||
dispatch(setNeedsRepaint(true));
|
dispatch(setNeedsRepaint(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChangeShouldShowBoundingBoxFill = () => {
|
||||||
|
dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inpainting-settings">
|
<div className="inpainting-settings">
|
||||||
<div className="inpainting-buttons">
|
<div className="inpainting-buttons">
|
||||||
@ -320,8 +335,8 @@ const InpaintingControls = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<IAIColorPicker
|
<IAIColorPicker
|
||||||
color={{ ...maskColor, a: maskOpacity }}
|
color={maskColor}
|
||||||
onChange={handleChangeBrushColor}
|
onChange={handleChangeMaskColor}
|
||||||
/>
|
/>
|
||||||
</IAIPopover>
|
</IAIPopover>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
@ -375,6 +390,13 @@ const InpaintingControls = () => {
|
|||||||
data-selected={showDualDisplay}
|
data-selected={showDualDisplay}
|
||||||
onClick={handleDualDisplay}
|
onClick={handleDualDisplay}
|
||||||
/>
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label="Darken Outside Bounding Box (xxx)"
|
||||||
|
tooltip="Darken Outside Bounding Box (xxx)"
|
||||||
|
icon={shouldShowBoundingBoxFill ? <FaTint /> : <FaTintSlash />}
|
||||||
|
data-selected={shouldShowBoundingBoxFill}
|
||||||
|
onClick={handleChangeShouldShowBoundingBoxFill}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { Group, Rect } from 'react-konva';
|
import { Group, Rect, Transformer } from 'react-konva';
|
||||||
import { RootState, useAppSelector } from '../../../../app/store';
|
import {
|
||||||
import { InpaintingState } from '../inpaintingSlice';
|
RootState,
|
||||||
|
useAppDispatch,
|
||||||
|
useAppSelector,
|
||||||
|
} from '../../../../app/store';
|
||||||
|
import { roundToMultiple } from '../../../../common/util/roundDownToMultiple';
|
||||||
|
import {
|
||||||
|
InpaintingState,
|
||||||
|
setBoundingBoxCoordinate,
|
||||||
|
setBoundingBoxDimensions,
|
||||||
|
} from '../inpaintingSlice';
|
||||||
import { rgbaColorToString } from '../util/colorToString';
|
import { rgbaColorToString } from '../util/colorToString';
|
||||||
import { DASH_WIDTH, MARCHING_ANTS_SPEED } from '../util/constants';
|
import {
|
||||||
|
DASH_WIDTH,
|
||||||
|
// MARCHING_ANTS_SPEED,
|
||||||
|
TRANSFORMER_ANCHOR_SIZE,
|
||||||
|
} from '../util/constants';
|
||||||
|
|
||||||
const boundingBoxPreviewSelector = createSelector(
|
const boundingBoxPreviewSelector = createSelector(
|
||||||
(state: RootState) => state.inpainting,
|
(state: RootState) => state.inpainting,
|
||||||
@ -18,14 +31,18 @@ const boundingBoxPreviewSelector = createSelector(
|
|||||||
boundingBoxPreviewFill,
|
boundingBoxPreviewFill,
|
||||||
canvasDimensions,
|
canvasDimensions,
|
||||||
stageScale,
|
stageScale,
|
||||||
|
imageToInpaint,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
return {
|
return {
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinate,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
boundingBoxPreviewFillString: rgbaColorToString(boundingBoxPreviewFill),
|
boundingBoxPreviewFillString: rgbaColorToString(boundingBoxPreviewFill),
|
||||||
canvasDimensions,
|
canvasDimensions,
|
||||||
dash: DASH_WIDTH / stageScale, // scale dash lengths
|
stageScale,
|
||||||
|
imageToInpaint,
|
||||||
|
dash: DASH_WIDTH / stageScale, // scale dash lengths
|
||||||
strokeWidth: 1 / stageScale, // scale stroke thickness
|
strokeWidth: 1 / stageScale, // scale stroke thickness
|
||||||
|
anchorSize: TRANSFORMER_ANCHOR_SIZE,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -38,7 +55,7 @@ const boundingBoxPreviewSelector = createSelector(
|
|||||||
/**
|
/**
|
||||||
* Shades the area around the mask.
|
* Shades the area around the mask.
|
||||||
*/
|
*/
|
||||||
const InpaintingBoundingBoxPreviewOverlay = () => {
|
export const InpaintingBoundingBoxPreviewOverlay = () => {
|
||||||
const {
|
const {
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinate,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
@ -68,122 +85,228 @@ const InpaintingBoundingBoxPreviewOverlay = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Draws marching ants around the mask.
|
// * Draws marching ants around the mask. Unused.
|
||||||
*/
|
// */
|
||||||
const InpaintingBoundingBoxPreviewMarchingAnts = () => {
|
// const _InpaintingBoundingBoxPreviewMarchingAnts = () => {
|
||||||
const { boundingBoxCoordinate, boundingBoxDimensions } = useAppSelector(
|
// const { boundingBoxCoordinate, boundingBoxDimensions } = useAppSelector(
|
||||||
boundingBoxPreviewSelector
|
// boundingBoxPreviewSelector
|
||||||
);
|
// );
|
||||||
|
|
||||||
const blackStrokeRectRef = useRef<Konva.Rect>(null);
|
// const blackStrokeRectRef = useRef<Konva.Rect>(null);
|
||||||
const whiteStrokeRectRef = useRef<Konva.Rect>(null);
|
// const whiteStrokeRectRef = useRef<Konva.Rect>(null);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const blackStrokeRect = blackStrokeRectRef.current;
|
||||||
|
// const whiteStrokeRect = whiteStrokeRectRef.current;
|
||||||
|
|
||||||
|
// const anim = new Konva.Animation((frame) => {
|
||||||
|
// if (!frame) return;
|
||||||
|
// blackStrokeRect?.dashOffset(
|
||||||
|
// -1 * (Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16)
|
||||||
|
// );
|
||||||
|
// whiteStrokeRect?.dashOffset(
|
||||||
|
// -1 * ((Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16) + 4)
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// anim.start();
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// anim.stop();
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <Group>
|
||||||
|
// <Rect
|
||||||
|
// x={boundingBoxCoordinate.x}
|
||||||
|
// y={boundingBoxCoordinate.y}
|
||||||
|
// width={boundingBoxDimensions.width}
|
||||||
|
// height={boundingBoxDimensions.height}
|
||||||
|
// stroke={'black'}
|
||||||
|
// strokeWidth={1}
|
||||||
|
// dash={[4, 4]}
|
||||||
|
// ref={blackStrokeRectRef}
|
||||||
|
// listening={false}
|
||||||
|
// />
|
||||||
|
// <Rect
|
||||||
|
// x={boundingBoxCoordinate.x}
|
||||||
|
// y={boundingBoxCoordinate.y}
|
||||||
|
// width={boundingBoxDimensions.width}
|
||||||
|
// height={boundingBoxDimensions.height}
|
||||||
|
// stroke={'white'}
|
||||||
|
// dash={[4, 4]}
|
||||||
|
// strokeWidth={1}
|
||||||
|
// ref={whiteStrokeRectRef}
|
||||||
|
// listening={false}
|
||||||
|
// />
|
||||||
|
// </Group>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
const InpaintingBoundingBoxPreview = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const {
|
||||||
|
boundingBoxCoordinate,
|
||||||
|
boundingBoxDimensions,
|
||||||
|
strokeWidth,
|
||||||
|
anchorSize,
|
||||||
|
stageScale,
|
||||||
|
imageToInpaint,
|
||||||
|
} = useAppSelector(boundingBoxPreviewSelector);
|
||||||
|
|
||||||
|
const transformerRef = useRef<Konva.Transformer>(null);
|
||||||
|
const shapeRef = useRef<Konva.Rect>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const blackStrokeRect = blackStrokeRectRef.current;
|
if (!transformerRef.current || !shapeRef.current) return;
|
||||||
const whiteStrokeRect = whiteStrokeRectRef.current;
|
transformerRef.current.nodes([shapeRef.current]);
|
||||||
|
transformerRef.current.getLayer()?.batchDraw();
|
||||||
const anim = new Konva.Animation((frame) => {
|
|
||||||
if (!frame) return;
|
|
||||||
blackStrokeRect?.dashOffset(
|
|
||||||
-1 * (Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16)
|
|
||||||
);
|
|
||||||
whiteStrokeRect?.dashOffset(
|
|
||||||
-1 * ((Math.floor(frame.time / MARCHING_ANTS_SPEED) % 16) + 4)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
anim.start();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
anim.stop();
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<>
|
||||||
<Rect
|
|
||||||
x={boundingBoxCoordinate.x}
|
|
||||||
y={boundingBoxCoordinate.y}
|
|
||||||
width={boundingBoxDimensions.width}
|
|
||||||
height={boundingBoxDimensions.height}
|
|
||||||
stroke={'black'}
|
|
||||||
strokeWidth={1}
|
|
||||||
dash={[4, 4]}
|
|
||||||
ref={blackStrokeRectRef}
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
<Rect
|
<Rect
|
||||||
x={boundingBoxCoordinate.x}
|
x={boundingBoxCoordinate.x}
|
||||||
y={boundingBoxCoordinate.y}
|
y={boundingBoxCoordinate.y}
|
||||||
width={boundingBoxDimensions.width}
|
width={boundingBoxDimensions.width}
|
||||||
height={boundingBoxDimensions.height}
|
height={boundingBoxDimensions.height}
|
||||||
|
ref={shapeRef}
|
||||||
stroke={'white'}
|
stroke={'white'}
|
||||||
dash={[4, 4]}
|
|
||||||
strokeWidth={1}
|
|
||||||
ref={whiteStrokeRectRef}
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws non-marching ants around the mask.
|
|
||||||
*/
|
|
||||||
const InpaintingBoundingBoxPreviewAnts = () => {
|
|
||||||
const { boundingBoxCoordinate, boundingBoxDimensions, dash, strokeWidth } =
|
|
||||||
useAppSelector(boundingBoxPreviewSelector);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Group>
|
|
||||||
<Rect
|
|
||||||
x={boundingBoxCoordinate.x}
|
|
||||||
y={boundingBoxCoordinate.y}
|
|
||||||
width={boundingBoxDimensions.width}
|
|
||||||
height={boundingBoxDimensions.height}
|
|
||||||
stroke={'black'}
|
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
dash={[dash, dash]}
|
|
||||||
dashOffset={0}
|
|
||||||
listening={false}
|
listening={false}
|
||||||
|
onTransform={() => {
|
||||||
|
/**
|
||||||
|
* The Konva Transformer changes the object's anchor point and scale factor,
|
||||||
|
* not its width and height. We need to un-scale the width and height before
|
||||||
|
* setting the values.
|
||||||
|
*/
|
||||||
|
if (!shapeRef.current) return;
|
||||||
|
|
||||||
|
const rect = shapeRef.current;
|
||||||
|
|
||||||
|
const scaleX = rect.scaleX();
|
||||||
|
const scaleY = rect.scaleY();
|
||||||
|
|
||||||
|
// undo the scaling
|
||||||
|
const width = Math.round(rect.width() * scaleX);
|
||||||
|
const height = Math.round(rect.height() * scaleY);
|
||||||
|
|
||||||
|
const x = Math.round(rect.x());
|
||||||
|
const y = Math.round(rect.y());
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setBoundingBoxDimensions({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setBoundingBoxCoordinate({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset the scale now that the coords/dimensions have been un-scaled
|
||||||
|
rect.scaleX(1);
|
||||||
|
rect.scaleY(1);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Rect
|
<Transformer
|
||||||
x={boundingBoxCoordinate.x}
|
ref={transformerRef}
|
||||||
y={boundingBoxCoordinate.y}
|
rotateEnabled={false}
|
||||||
width={boundingBoxDimensions.width}
|
anchorSize={anchorSize}
|
||||||
height={boundingBoxDimensions.height}
|
anchorStroke={'rgb(42,42,42)'}
|
||||||
stroke={'white'}
|
borderEnabled={true}
|
||||||
dash={[dash, dash]}
|
borderStroke={'black'}
|
||||||
strokeWidth={strokeWidth}
|
borderDash={[DASH_WIDTH, DASH_WIDTH]}
|
||||||
dashOffset={dash}
|
anchorCornerRadius={3}
|
||||||
listening={false}
|
ignoreStroke={true}
|
||||||
|
keepRatio={false}
|
||||||
|
flipEnabled={false}
|
||||||
|
anchorDragBoundFunc={(
|
||||||
|
oldPos: Vector2d, // old absolute position of anchor point
|
||||||
|
newPos: Vector2d, // new absolute position (potentially) of anchor point
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_event: MouseEvent
|
||||||
|
) => {
|
||||||
|
/**
|
||||||
|
* Konva does not transform with width or height. It transforms the anchor point
|
||||||
|
* and scale factor. This is then sent to the shape's onTransform listeners.
|
||||||
|
*
|
||||||
|
* We need to snap the new width to steps of 64 without also snapping the
|
||||||
|
* coordinates of the bounding box to steps of 64. But because the whole
|
||||||
|
* stage is scaled, our actual desired step is actually 64 * the stage scale.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get the scaled step
|
||||||
|
const scaledStep = 64 * stageScale;
|
||||||
|
|
||||||
|
// Difference of the old coords from the nearest multiple the scaled step
|
||||||
|
const offsetX = oldPos.x % scaledStep;
|
||||||
|
const offsetY = oldPos.y % scaledStep;
|
||||||
|
|
||||||
|
// Round new position to the nearest multiple of the scaled step
|
||||||
|
const closestX = roundToMultiple(newPos.x, scaledStep) + offsetX;
|
||||||
|
const closestY = roundToMultiple(newPos.y, scaledStep) + offsetY;
|
||||||
|
|
||||||
|
// the difference between the old coord and new
|
||||||
|
const diffX = Math.abs(newPos.x - closestX);
|
||||||
|
const diffY = Math.abs(newPos.y - closestY);
|
||||||
|
|
||||||
|
// if the difference is less than the scaled step, we want to snap
|
||||||
|
const didSnapX = diffX < scaledStep;
|
||||||
|
const didSnapY = diffY < scaledStep;
|
||||||
|
|
||||||
|
// We may not change anything, stash the old position
|
||||||
|
let newCoordinate = { ...oldPos };
|
||||||
|
|
||||||
|
// Set the new coords based on what snapped
|
||||||
|
if (didSnapX && !didSnapY) {
|
||||||
|
newCoordinate = {
|
||||||
|
x: closestX,
|
||||||
|
y: oldPos.y,
|
||||||
|
};
|
||||||
|
} else if (!didSnapX && didSnapY) {
|
||||||
|
newCoordinate = {
|
||||||
|
x: oldPos.x,
|
||||||
|
y: closestY,
|
||||||
|
};
|
||||||
|
} else if (didSnapX && didSnapY) {
|
||||||
|
newCoordinate = {
|
||||||
|
x: closestX,
|
||||||
|
y: closestY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCoordinate;
|
||||||
|
}}
|
||||||
|
boundBoxFunc={(oldBoundBox, newBoundBox) => {
|
||||||
|
/**
|
||||||
|
* The transformer uses this callback to limit valid transformations.
|
||||||
|
* Unlike anchorDragBoundFunc, it does get a width and height, so
|
||||||
|
* the logic to constrain the size of the bounding box is very simple.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!imageToInpaint) return oldBoundBox;
|
||||||
|
|
||||||
|
if (
|
||||||
|
newBoundBox.width + newBoundBox.x > imageToInpaint.width ||
|
||||||
|
newBoundBox.height + newBoundBox.y > imageToInpaint.height ||
|
||||||
|
newBoundBox.x < 0 ||
|
||||||
|
newBoundBox.y < 0
|
||||||
|
) {
|
||||||
|
return oldBoundBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBoundBox;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const boundingBoxPreviewTypeSelector = createSelector(
|
|
||||||
(state: RootState) => state.inpainting,
|
|
||||||
(inpainting: InpaintingState) => inpainting.boundingBoxPreviewType
|
|
||||||
);
|
|
||||||
|
|
||||||
const InpaintingBoundingBoxPreview = () => {
|
|
||||||
const boundingBoxPreviewType = useAppSelector(boundingBoxPreviewTypeSelector);
|
|
||||||
|
|
||||||
switch (boundingBoxPreviewType) {
|
|
||||||
case 'overlay': {
|
|
||||||
return <InpaintingBoundingBoxPreviewOverlay />;
|
|
||||||
}
|
|
||||||
case 'ants': {
|
|
||||||
return <InpaintingBoundingBoxPreviewAnts />;
|
|
||||||
}
|
|
||||||
case 'marchingAnts': {
|
|
||||||
return <InpaintingBoundingBoxPreviewMarchingAnts />;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InpaintingBoundingBoxPreview;
|
export default InpaintingBoundingBoxPreview;
|
||||||
|
@ -3,7 +3,7 @@ import _ from 'lodash';
|
|||||||
import { Circle } from 'react-konva';
|
import { Circle } from 'react-konva';
|
||||||
import { RootState, useAppSelector } from '../../../../app/store';
|
import { RootState, useAppSelector } from '../../../../app/store';
|
||||||
import { InpaintingState } from '../inpaintingSlice';
|
import { InpaintingState } from '../inpaintingSlice';
|
||||||
import { rgbColorToString } from '../util/colorToString';
|
import { rgbaColorToRgbString } from '../util/colorToString';
|
||||||
|
|
||||||
const inpaintingCanvasBrushPreviewSelector = createSelector(
|
const inpaintingCanvasBrushPreviewSelector = createSelector(
|
||||||
(state: RootState) => state.inpainting,
|
(state: RootState) => state.inpainting,
|
||||||
@ -23,7 +23,7 @@ const inpaintingCanvasBrushPreviewSelector = createSelector(
|
|||||||
height,
|
height,
|
||||||
shouldShowBrushPreview,
|
shouldShowBrushPreview,
|
||||||
brushSize,
|
brushSize,
|
||||||
maskColorString: rgbColorToString(maskColor),
|
maskColorString: rgbaColorToRgbString(maskColor),
|
||||||
tool,
|
tool,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Line } from 'react-konva';
|
import { Line } from 'react-konva';
|
||||||
import { RootState, useAppSelector } from '../../../../app/store';
|
import { useAppSelector } from '../../../../app/store';
|
||||||
|
import { inpaintingCanvasLinesSelector } from '../inpaintingSliceSelectors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the lines which comprise the mask.
|
* Draws the lines which comprise the mask.
|
||||||
@ -7,11 +8,9 @@ import { RootState, useAppSelector } from '../../../../app/store';
|
|||||||
* Uses globalCompositeOperation to handle the brush and eraser tools.
|
* Uses globalCompositeOperation to handle the brush and eraser tools.
|
||||||
*/
|
*/
|
||||||
const InpaintingCanvasLines = () => {
|
const InpaintingCanvasLines = () => {
|
||||||
const { lines, maskColor } = useAppSelector(
|
const { lines, maskColorString } = useAppSelector(
|
||||||
(state: RootState) => state.inpainting
|
inpaintingCanvasLinesSelector
|
||||||
);
|
);
|
||||||
const { r, g, b } = maskColor;
|
|
||||||
const maskColorString = `rgb(${r},${g},${b})`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -39,24 +39,10 @@ const KeyboardEventManager = () => {
|
|||||||
|
|
||||||
const isFirstEvent = useRef<boolean>(true);
|
const isFirstEvent = useRef<boolean>(true);
|
||||||
const wasLastEventOverCanvas = useRef<boolean>(false);
|
const wasLastEventOverCanvas = useRef<boolean>(false);
|
||||||
|
const lastEvent = useRef<KeyboardEvent | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (e: KeyboardEvent) => {
|
const listener = (e: KeyboardEvent) => {
|
||||||
if (!isCursorOnCanvas) {
|
|
||||||
wasLastEventOverCanvas.current = false;
|
|
||||||
|
|
||||||
if (isFirstEvent.current) {
|
|
||||||
isFirstEvent.current = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFirstEvent.current) {
|
|
||||||
wasLastEventOverCanvas.current = true;
|
|
||||||
isFirstEvent.current = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!['Alt', ' '].includes(e.key) ||
|
!['Alt', ' '].includes(e.key) ||
|
||||||
activeTabName !== 'inpainting' ||
|
activeTabName !== 'inpainting' ||
|
||||||
@ -66,8 +52,27 @@ const KeyboardEventManager = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasLastEventOverCanvas.current) {
|
// cursor is NOT over canvas
|
||||||
|
if (!isCursorOnCanvas) {
|
||||||
|
if (!lastEvent.current) {
|
||||||
|
lastEvent.current = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasLastEventOverCanvas.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cursor is over canvas
|
||||||
|
|
||||||
|
// if this is the first event
|
||||||
|
if (!lastEvent.current) {
|
||||||
wasLastEventOverCanvas.current = true;
|
wasLastEventOverCanvas.current = true;
|
||||||
|
lastEvent.current = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasLastEventOverCanvas.current && e.type === 'keyup') {
|
||||||
|
wasLastEventOverCanvas.current = true;
|
||||||
|
lastEvent.current = e;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +88,11 @@ const KeyboardEventManager = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastEvent.current = e;
|
||||||
|
wasLastEventOverCanvas.current = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('adding listeners');
|
|
||||||
document.addEventListener('keydown', listener);
|
document.addEventListener('keydown', listener);
|
||||||
document.addEventListener('keyup', listener);
|
document.addEventListener('keyup', listener);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { Vector2d } from 'konva/lib/types';
|
import { Vector2d } from 'konva/lib/types';
|
||||||
import { RgbaColor, RgbColor } from 'react-colorful';
|
import { RgbaColor } from 'react-colorful';
|
||||||
import * as InvokeAI from '../../../app/invokeai';
|
import * as InvokeAI from '../../../app/invokeai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { roundDownToMultiple } from '../../../common/util/roundDownToMultiple';
|
import { roundDownToMultiple } from '../../../common/util/roundDownToMultiple';
|
||||||
@ -31,15 +31,15 @@ export type BoundingBoxPreviewType = 'overlay' | 'ants' | 'marchingAnts';
|
|||||||
export interface InpaintingState {
|
export interface InpaintingState {
|
||||||
tool: 'brush' | 'eraser';
|
tool: 'brush' | 'eraser';
|
||||||
brushSize: number;
|
brushSize: number;
|
||||||
maskColor: RgbColor;
|
maskColor: RgbaColor;
|
||||||
maskOpacity: number;
|
|
||||||
cursorPosition: Vector2d | null;
|
cursorPosition: Vector2d | null;
|
||||||
canvasDimensions: Dimensions;
|
canvasDimensions: Dimensions;
|
||||||
boundingBoxDimensions: Dimensions;
|
boundingBoxDimensions: Dimensions;
|
||||||
boundingBoxCoordinate: Vector2d;
|
boundingBoxCoordinate: Vector2d;
|
||||||
isMovingBoundingBox: boolean;
|
isMovingBoundingBox: boolean;
|
||||||
boundingBoxPreviewFill: RgbaColor;
|
boundingBoxPreviewFill: RgbaColor;
|
||||||
boundingBoxPreviewType: BoundingBoxPreviewType;
|
shouldShowBoundingBoxFill: boolean;
|
||||||
|
isBoundingBoxTransforming: boolean;
|
||||||
lines: MaskLine[];
|
lines: MaskLine[];
|
||||||
pastLines: MaskLine[][];
|
pastLines: MaskLine[][];
|
||||||
futureLines: MaskLine[][];
|
futureLines: MaskLine[][];
|
||||||
@ -50,18 +50,19 @@ export interface InpaintingState {
|
|||||||
imageToInpaint?: InvokeAI.Image;
|
imageToInpaint?: InvokeAI.Image;
|
||||||
needsRepaint: boolean;
|
needsRepaint: boolean;
|
||||||
stageScale: number;
|
stageScale: number;
|
||||||
|
isDrawing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialInpaintingState: InpaintingState = {
|
const initialInpaintingState: InpaintingState = {
|
||||||
tool: 'brush',
|
tool: 'brush',
|
||||||
brushSize: 50,
|
brushSize: 50,
|
||||||
maskColor: { r: 255, g: 90, b: 90 },
|
maskColor: { r: 255, g: 90, b: 90, a: 0.5 },
|
||||||
maskOpacity: 0.5,
|
|
||||||
canvasDimensions: { width: 0, height: 0 },
|
canvasDimensions: { width: 0, height: 0 },
|
||||||
boundingBoxDimensions: { width: 64, height: 64 },
|
boundingBoxDimensions: { width: 64, height: 64 },
|
||||||
boundingBoxCoordinate: { x: 0, y: 0 },
|
boundingBoxCoordinate: { x: 0, y: 0 },
|
||||||
boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.5 },
|
boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.7 },
|
||||||
boundingBoxPreviewType: 'ants',
|
shouldShowBoundingBoxFill: false,
|
||||||
|
isBoundingBoxTransforming: false,
|
||||||
cursorPosition: null,
|
cursorPosition: null,
|
||||||
lines: [],
|
lines: [],
|
||||||
pastLines: [],
|
pastLines: [],
|
||||||
@ -72,6 +73,7 @@ const initialInpaintingState: InpaintingState = {
|
|||||||
shouldShowBrushPreview: false,
|
shouldShowBrushPreview: false,
|
||||||
isMovingBoundingBox: false,
|
isMovingBoundingBox: false,
|
||||||
needsRepaint: false,
|
needsRepaint: false,
|
||||||
|
isDrawing: false,
|
||||||
stageScale: 1,
|
stageScale: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -142,12 +144,10 @@ export const inpaintingSlice = createSlice({
|
|||||||
setShouldShowBrushPreview: (state, action: PayloadAction<boolean>) => {
|
setShouldShowBrushPreview: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldShowBrushPreview = action.payload;
|
state.shouldShowBrushPreview = action.payload;
|
||||||
},
|
},
|
||||||
setMaskColor: (state, action: PayloadAction<RgbColor>) => {
|
setMaskColor: (state, action: PayloadAction<RgbaColor>) => {
|
||||||
state.maskColor = action.payload;
|
state.maskColor = action.payload;
|
||||||
},
|
},
|
||||||
setMaskOpacity: (state, action: PayloadAction<number>) => {
|
// },
|
||||||
state.maskOpacity = action.payload;
|
|
||||||
},
|
|
||||||
setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => {
|
setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => {
|
||||||
state.cursorPosition = action.payload;
|
state.cursorPosition = action.payload;
|
||||||
},
|
},
|
||||||
@ -215,34 +215,67 @@ export const inpaintingSlice = createSlice({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
setBoundingBoxDimensions: (state, action: PayloadAction<Dimensions>) => {
|
setBoundingBoxDimensions: (state, action: PayloadAction<Dimensions>) => {
|
||||||
const { width: boundingBoxWidth, height: boundingBoxHeight } =
|
state.boundingBoxDimensions = action.payload;
|
||||||
action.payload;
|
// const { width: boundingBoxWidth, height: boundingBoxHeight } =
|
||||||
const { x: boundingBoxX, y: boundingBoxY } = state.boundingBoxCoordinate;
|
// action.payload;
|
||||||
const { width: canvasWidth, height: canvasHeight } =
|
// const { x: boundingBoxX, y: boundingBoxY } = state.boundingBoxCoordinate;
|
||||||
state.canvasDimensions;
|
// const { width: canvasWidth, height: canvasHeight } =
|
||||||
|
// state.canvasDimensions;
|
||||||
|
|
||||||
const overflowX = boundingBoxX + boundingBoxWidth - canvasWidth;
|
// const roundedCanvasWidth = roundDownToMultiple(canvasWidth, 64);
|
||||||
const overflowY = boundingBoxY + boundingBoxHeight - canvasHeight;
|
// const roundedCanvasHeight = roundDownToMultiple(canvasHeight, 64);
|
||||||
|
// const roundedBoundingBoxWidth = roundDownToMultiple(boundingBoxWidth, 64);
|
||||||
|
// const roundedBoundingBoxHeight = roundDownToMultiple(
|
||||||
|
// boundingBoxHeight,
|
||||||
|
// 64
|
||||||
|
// );
|
||||||
|
|
||||||
const newBoundingBoxX = roundDownToMultiple(
|
// const overflowX = boundingBoxX + boundingBoxWidth - canvasWidth;
|
||||||
overflowX > 0 ? boundingBoxX - overflowX : boundingBoxX,
|
// const overflowY = boundingBoxY + boundingBoxHeight - canvasHeight;
|
||||||
64
|
|
||||||
);
|
|
||||||
|
|
||||||
const newBoundingBoxY = roundDownToMultiple(
|
// const newBoundingBoxWidth = _.clamp(
|
||||||
overflowY > 0 ? boundingBoxY - overflowY : boundingBoxY,
|
// roundedBoundingBoxWidth,
|
||||||
64
|
// 64,
|
||||||
);
|
// roundedCanvasWidth
|
||||||
|
// );
|
||||||
|
|
||||||
state.boundingBoxDimensions = {
|
// const newBoundingBoxHeight = _.clamp(
|
||||||
width: roundDownToMultiple(boundingBoxWidth, 64),
|
// roundedBoundingBoxHeight,
|
||||||
height: roundDownToMultiple(boundingBoxHeight, 64),
|
// 64,
|
||||||
};
|
// roundedCanvasHeight
|
||||||
|
// );
|
||||||
|
|
||||||
state.boundingBoxCoordinate = {
|
// const overflowCorrectedX =
|
||||||
x: newBoundingBoxX,
|
// overflowX > 0 ? boundingBoxX - overflowX : boundingBoxX;
|
||||||
y: newBoundingBoxY,
|
|
||||||
};
|
// const overflowCorrectedY =
|
||||||
|
// overflowY > 0 ? boundingBoxY - overflowY : boundingBoxY;
|
||||||
|
|
||||||
|
// const clampedX = _.clamp(
|
||||||
|
// overflowCorrectedX,
|
||||||
|
// 64,
|
||||||
|
// roundedCanvasWidth - newBoundingBoxWidth
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const clampedY = _.clamp(
|
||||||
|
// overflowCorrectedY,
|
||||||
|
// 64,
|
||||||
|
// roundedCanvasHeight - newBoundingBoxHeight
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const newBoundingBoxX = roundDownToMultiple(clampedX, 64);
|
||||||
|
|
||||||
|
// const newBoundingBoxY = roundDownToMultiple(clampedY, 64);
|
||||||
|
|
||||||
|
// state.boundingBoxDimensions = {
|
||||||
|
// width: newBoundingBoxWidth,
|
||||||
|
// height: newBoundingBoxHeight,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// state.boundingBoxCoordinate = {
|
||||||
|
// x: newBoundingBoxX,
|
||||||
|
// y: newBoundingBoxY,
|
||||||
|
// };
|
||||||
},
|
},
|
||||||
setBoundingBoxCoordinate: (state, action: PayloadAction<Vector2d>) => {
|
setBoundingBoxCoordinate: (state, action: PayloadAction<Vector2d>) => {
|
||||||
state.boundingBoxCoordinate = action.payload;
|
state.boundingBoxCoordinate = action.payload;
|
||||||
@ -256,12 +289,6 @@ export const inpaintingSlice = createSlice({
|
|||||||
setBoundingBoxPreviewFill: (state, action: PayloadAction<RgbaColor>) => {
|
setBoundingBoxPreviewFill: (state, action: PayloadAction<RgbaColor>) => {
|
||||||
state.boundingBoxPreviewFill = action.payload;
|
state.boundingBoxPreviewFill = action.payload;
|
||||||
},
|
},
|
||||||
setBoundingBoxPreviewType: (
|
|
||||||
state,
|
|
||||||
action: PayloadAction<BoundingBoxPreviewType>
|
|
||||||
) => {
|
|
||||||
state.boundingBoxPreviewType = action.payload;
|
|
||||||
},
|
|
||||||
setNeedsRepaint: (state, action: PayloadAction<boolean>) => {
|
setNeedsRepaint: (state, action: PayloadAction<boolean>) => {
|
||||||
state.needsRepaint = action.payload;
|
state.needsRepaint = action.payload;
|
||||||
},
|
},
|
||||||
@ -269,6 +296,15 @@ export const inpaintingSlice = createSlice({
|
|||||||
state.stageScale = action.payload;
|
state.stageScale = action.payload;
|
||||||
state.needsRepaint = false;
|
state.needsRepaint = false;
|
||||||
},
|
},
|
||||||
|
setShouldShowBoundingBoxFill: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.shouldShowBoundingBoxFill = action.payload;
|
||||||
|
},
|
||||||
|
setIsBoundingBoxTransforming: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isBoundingBoxTransforming = action.payload;
|
||||||
|
},
|
||||||
|
setIsDrawing: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isDrawing = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -283,7 +319,6 @@ export const {
|
|||||||
setShouldShowBrushPreview,
|
setShouldShowBrushPreview,
|
||||||
setMaskColor,
|
setMaskColor,
|
||||||
clearMask,
|
clearMask,
|
||||||
setMaskOpacity,
|
|
||||||
undo,
|
undo,
|
||||||
redo,
|
redo,
|
||||||
setCursorPosition,
|
setCursorPosition,
|
||||||
@ -293,11 +328,13 @@ export const {
|
|||||||
setBoundingBoxCoordinate,
|
setBoundingBoxCoordinate,
|
||||||
setIsMovingBoundingBox,
|
setIsMovingBoundingBox,
|
||||||
setBoundingBoxPreviewFill,
|
setBoundingBoxPreviewFill,
|
||||||
setBoundingBoxPreviewType,
|
|
||||||
setNeedsRepaint,
|
setNeedsRepaint,
|
||||||
setStageScale,
|
setStageScale,
|
||||||
toggleTool,
|
toggleTool,
|
||||||
toggleIsMovingBoundingBox,
|
toggleIsMovingBoundingBox,
|
||||||
|
setShouldShowBoundingBoxFill,
|
||||||
|
setIsBoundingBoxTransforming,
|
||||||
|
setIsDrawing,
|
||||||
} = inpaintingSlice.actions;
|
} = inpaintingSlice.actions;
|
||||||
|
|
||||||
export default inpaintingSlice.reducer;
|
export default inpaintingSlice.reducer;
|
||||||
|
@ -4,6 +4,18 @@ import { RootState } from '../../../app/store';
|
|||||||
import { OptionsState } from '../../options/optionsSlice';
|
import { OptionsState } from '../../options/optionsSlice';
|
||||||
import { tabMap } from '../InvokeTabs';
|
import { tabMap } from '../InvokeTabs';
|
||||||
import { InpaintingState } from './inpaintingSlice';
|
import { InpaintingState } from './inpaintingSlice';
|
||||||
|
import { rgbaColorToRgbString } from './util/colorToString';
|
||||||
|
|
||||||
|
export const inpaintingCanvasLinesSelector = createSelector(
|
||||||
|
(state: RootState) => state.inpainting,
|
||||||
|
(inpainting: InpaintingState) => {
|
||||||
|
const { lines, maskColor } = inpainting;
|
||||||
|
return {
|
||||||
|
lines,
|
||||||
|
maskColorString: rgbaColorToRgbString(maskColor),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const inpaintingControlsSelector = createSelector(
|
export const inpaintingControlsSelector = createSelector(
|
||||||
[(state: RootState) => state.inpainting, (state: RootState) => state.options],
|
[(state: RootState) => state.inpainting, (state: RootState) => state.options],
|
||||||
@ -12,7 +24,6 @@ export const inpaintingControlsSelector = createSelector(
|
|||||||
tool,
|
tool,
|
||||||
brushSize,
|
brushSize,
|
||||||
maskColor,
|
maskColor,
|
||||||
maskOpacity,
|
|
||||||
shouldInvertMask,
|
shouldInvertMask,
|
||||||
shouldShowMask,
|
shouldShowMask,
|
||||||
shouldShowCheckboardTransparency,
|
shouldShowCheckboardTransparency,
|
||||||
@ -20,6 +31,7 @@ export const inpaintingControlsSelector = createSelector(
|
|||||||
pastLines,
|
pastLines,
|
||||||
futureLines,
|
futureLines,
|
||||||
isMovingBoundingBox,
|
isMovingBoundingBox,
|
||||||
|
shouldShowBoundingBoxFill,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
|
|
||||||
const { activeTab, showDualDisplay } = options;
|
const { activeTab, showDualDisplay } = options;
|
||||||
@ -28,7 +40,6 @@ export const inpaintingControlsSelector = createSelector(
|
|||||||
tool,
|
tool,
|
||||||
brushSize,
|
brushSize,
|
||||||
maskColor,
|
maskColor,
|
||||||
maskOpacity,
|
|
||||||
shouldInvertMask,
|
shouldInvertMask,
|
||||||
shouldShowMask,
|
shouldShowMask,
|
||||||
shouldShowCheckboardTransparency,
|
shouldShowCheckboardTransparency,
|
||||||
@ -38,6 +49,7 @@ export const inpaintingControlsSelector = createSelector(
|
|||||||
isMovingBoundingBox,
|
isMovingBoundingBox,
|
||||||
activeTabName: tabMap[activeTab],
|
activeTabName: tabMap[activeTab],
|
||||||
showDualDisplay,
|
showDualDisplay,
|
||||||
|
shouldShowBoundingBoxFill,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -58,13 +70,13 @@ export const inpaintingCanvasSelector = createSelector(
|
|||||||
shouldShowMask,
|
shouldShowMask,
|
||||||
shouldShowCheckboardTransparency,
|
shouldShowCheckboardTransparency,
|
||||||
shouldShowBrushPreview,
|
shouldShowBrushPreview,
|
||||||
maskOpacity,
|
|
||||||
imageToInpaint,
|
imageToInpaint,
|
||||||
isMovingBoundingBox,
|
isMovingBoundingBox,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
canvasDimensions,
|
canvasDimensions,
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinate,
|
||||||
stageScale,
|
stageScale,
|
||||||
|
shouldShowBoundingBoxFill,
|
||||||
} = inpainting;
|
} = inpainting;
|
||||||
return {
|
return {
|
||||||
tool,
|
tool,
|
||||||
@ -74,13 +86,13 @@ export const inpaintingCanvasSelector = createSelector(
|
|||||||
shouldShowMask,
|
shouldShowMask,
|
||||||
shouldShowCheckboardTransparency,
|
shouldShowCheckboardTransparency,
|
||||||
shouldShowBrushPreview,
|
shouldShowBrushPreview,
|
||||||
maskOpacity,
|
|
||||||
imageToInpaint,
|
imageToInpaint,
|
||||||
isMovingBoundingBox,
|
isMovingBoundingBox,
|
||||||
boundingBoxDimensions,
|
boundingBoxDimensions,
|
||||||
canvasDimensions,
|
canvasDimensions,
|
||||||
boundingBoxCoordinate,
|
boundingBoxCoordinate,
|
||||||
stageScale,
|
stageScale,
|
||||||
|
shouldShowBoundingBoxFill,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,11 @@ export const rgbaColorToString = (color: RgbaColor): string => {
|
|||||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const rgbaColorToRgbString = (color: RgbaColor): string => {
|
||||||
|
const { r, g, b } = color;
|
||||||
|
return `rgba(${r}, ${g}, ${b})`;
|
||||||
|
};
|
||||||
|
|
||||||
export const rgbColorToString = (color: RgbColor): string => {
|
export const rgbColorToString = (color: RgbColor): string => {
|
||||||
const { r, g, b } = color;
|
const { r, g, b } = color;
|
||||||
return `rgba(${r}, ${g}, ${b})`;
|
return `rgba(${r}, ${g}, ${b})`;
|
||||||
|
@ -3,3 +3,8 @@ export const DASH_WIDTH = 4;
|
|||||||
|
|
||||||
// speed of marching ants (lower is faster)
|
// speed of marching ants (lower is faster)
|
||||||
export const MARCHING_ANTS_SPEED = 30;
|
export const MARCHING_ANTS_SPEED = 30;
|
||||||
|
|
||||||
|
// bounding box anchor size
|
||||||
|
export const TRANSFORMER_ANCHOR_SIZE = 10;
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user