mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Web UI 2.2 bugfixes (#1572)
* Fixes bug preventing multiple images from being generated * Fixes valid seam strength value range * Update Delete Alert Text Indicates to the user that images are not permanently deleted. * Fixes left/right arrows not working on gallery * Fixes initial image on load erroneously set to a user uploaded image Should be a result gallery image. * Lightbox Fixes - Lightbox is now a button in the current image buttons - Lightbox is also now available in the gallery context menu - Lightbox zoom issues fixed - Lightbox has a fade in animation. * Fix image display wrapper in current preview not overflow bounds * Revert "Fix image display wrapper in current preview not overflow bounds" This reverts commit 5511c82714dbf1d1999d64e8bc357bafa34ddf37. * Change Staging Area discard icon from Bin to X * Expose Snap Threshold and Move Snap Settings to BBox Panel * Changes img2img strength default to 0.75 * Fixes drawing triggering when mouse enters canvas w/ button down When we only supported inpainting and no zoom, this was useful. It allowed the cursor to leave the canvas (which was easy to do given the limited canvas dimensions) and without losing the "I am drawing" state. With a zoomable canvas this is no longer as useful. Additionally, we have more popovers and tools (like the color pickers) which result in unexpected brush strokes. This fixes that issue. * Revert "Expose Snap Threshold and Move Snap Settings to BBox Panel" We will handle this a bit differently - by allowing the grid origin to be moved. I will dig in at some point. This reverts commit 33c92ecf4da724c2f17d9d91c7ea31a43a2f6deb. * Adds Limit Strokes to Box * Adds fill bounding box button * Adds erase bounding box button * Changes Staging area discard icon to match others * Fixes right click breaking move tool * Fixes brush preview visibility issue with "darken outside box" * Fixes history bugs with addFillRect, addEraseRect, and other actions * Adds missing `key` * Fixes postprocessing being applied to canvas generations * Fixes bbox not getting scaled in various situations * Fixes staging area show image toggle not resetting on accept/discard * Locks down canvas while generating/staging * Fixes move tool breaking when canvas loses focus during move/transform * Hides cursor when restrict strokes is on and mouse outside bbox * Lints * Builds fresh bundle * Fix overlapping hotkey for Fill Bounding Box * Build Fresh Bundle * Fixes bug with mask and bbox overlay * Builds fresh bundle Co-authored-by: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
This commit is contained in:
parent
9fae65ed69
commit
5e81f51f6a
1
frontend/dist/assets/index.c609c0c8.css
vendored
Normal file
1
frontend/dist/assets/index.c609c0c8.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.f999e69e.css
vendored
1
frontend/dist/assets/index.f999e69e.css
vendored
File diff suppressed because one or more lines are too long
623
frontend/dist/assets/index.faf4c870.js
vendored
Normal file
623
frontend/dist/assets/index.faf4c870.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
||||
<script type="module" crossorigin src="./assets/index.f1b8da92.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.f999e69e.css">
|
||||
<script type="module" crossorigin src="./assets/index.faf4c870.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.c609c0c8.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -61,17 +61,17 @@ const makeSocketIOListeners = (
|
||||
dispatch(requestSystemConfig());
|
||||
const gallery: GalleryState = getState().gallery;
|
||||
|
||||
if (gallery.categories.user.latest_mtime) {
|
||||
dispatch(requestNewImages('user'));
|
||||
} else {
|
||||
dispatch(requestImages('user'));
|
||||
}
|
||||
|
||||
if (gallery.categories.result.latest_mtime) {
|
||||
dispatch(requestNewImages('result'));
|
||||
} else {
|
||||
dispatch(requestImages('result'));
|
||||
}
|
||||
|
||||
if (gallery.categories.user.latest_mtime) {
|
||||
dispatch(requestNewImages('user'));
|
||||
} else {
|
||||
dispatch(requestImages('user'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -81,8 +81,7 @@ export const frontendToBackendParameters = (
|
||||
|
||||
const generationParameters: { [k: string]: any } = {
|
||||
prompt,
|
||||
iterations:
|
||||
shouldRandomizeSeed || shouldGenerateVariations ? iterations : 1,
|
||||
iterations,
|
||||
steps,
|
||||
cfg_scale: cfgScale,
|
||||
threshold,
|
||||
@ -98,6 +97,9 @@ export const frontendToBackendParameters = (
|
||||
init_mask: '',
|
||||
};
|
||||
|
||||
let esrganParameters: false | { [k: string]: any } = false;
|
||||
let facetoolParameters: false | { [k: string]: any } = false;
|
||||
|
||||
generationParameters.seed = shouldRandomizeSeed
|
||||
? randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)
|
||||
: seed;
|
||||
@ -106,6 +108,23 @@ export const frontendToBackendParameters = (
|
||||
if (['txt2img', 'img2img'].includes(generationMode)) {
|
||||
generationParameters.seamless = seamless;
|
||||
generationParameters.hires_fix = hiresFix;
|
||||
|
||||
if (shouldRunESRGAN) {
|
||||
esrganParameters = {
|
||||
level: upscalingLevel,
|
||||
strength: upscalingStrength,
|
||||
};
|
||||
}
|
||||
|
||||
if (shouldRunFacetool) {
|
||||
facetoolParameters = {
|
||||
type: facetoolType,
|
||||
strength: facetoolStrength,
|
||||
};
|
||||
if (facetoolType === 'codeformer') {
|
||||
facetoolParameters.codeformer_fidelity = codeformerFidelity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// img2img exclusive parameters
|
||||
@ -209,26 +228,6 @@ export const frontendToBackendParameters = (
|
||||
generationParameters.variation_amount = 0;
|
||||
}
|
||||
|
||||
let esrganParameters: false | { [k: string]: any } = false;
|
||||
let facetoolParameters: false | { [k: string]: any } = false;
|
||||
|
||||
if (shouldRunESRGAN) {
|
||||
esrganParameters = {
|
||||
level: upscalingLevel,
|
||||
strength: upscalingStrength,
|
||||
};
|
||||
}
|
||||
|
||||
if (shouldRunFacetool) {
|
||||
facetoolParameters = {
|
||||
type: facetoolType,
|
||||
strength: facetoolStrength,
|
||||
};
|
||||
if (facetoolType === 'codeformer') {
|
||||
facetoolParameters.codeformer_fidelity = codeformerFidelity;
|
||||
}
|
||||
}
|
||||
|
||||
if (enableImageDebugging) {
|
||||
generationParameters.enable_image_debugging = enableImageDebugging;
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { useAppDispatch } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import IAIAlertDialog from 'common/components/IAIAlertDialog';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
import { isStagingSelector } from '../store/canvasSelectors';
|
||||
|
||||
const ClearCanvasHistoryButtonModal = () => {
|
||||
const isStaging = useAppSelector(isStagingSelector);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return (
|
||||
@ -13,7 +15,7 @@ const ClearCanvasHistoryButtonModal = () => {
|
||||
acceptCallback={() => dispatch(clearCanvasHistory())}
|
||||
acceptButtonText={'Clear History'}
|
||||
triggerComponent={
|
||||
<IAIButton size={'sm'} leftIcon={<FaTrash />}>
|
||||
<IAIButton size={'sm'} leftIcon={<FaTrash />} isDisabled={isStaging}>
|
||||
Clear Canvas History
|
||||
</IAIButton>
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import useCanvasWheel from '../hooks/useCanvasZoom';
|
||||
import useCanvasMouseDown from '../hooks/useCanvasMouseDown';
|
||||
import useCanvasMouseUp from '../hooks/useCanvasMouseUp';
|
||||
import useCanvasMouseMove from '../hooks/useCanvasMouseMove';
|
||||
import useCanvasMouseEnter from '../hooks/useCanvasMouseEnter';
|
||||
import useCanvasMouseOut from '../hooks/useCanvasMouseOut';
|
||||
import useCanvasDragMove from '../hooks/useCanvasDragMove';
|
||||
import IAICanvasObjectRenderer from './IAICanvasObjectRenderer';
|
||||
@ -31,6 +30,8 @@ import {
|
||||
setCanvasBaseLayer,
|
||||
setCanvasStage,
|
||||
} from '../util/konvaInstanceProvider';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import IAICanvasBoundingBoxOverlay from './IAICanvasBoundingBoxOverlay';
|
||||
|
||||
const selector = createSelector(
|
||||
[canvasSelector, isStagingSelector],
|
||||
@ -48,9 +49,10 @@ const selector = createSelector(
|
||||
isMovingStage,
|
||||
shouldShowIntermediates,
|
||||
shouldShowGrid,
|
||||
shouldRestrictStrokesToBox,
|
||||
} = canvas;
|
||||
|
||||
let stageCursor: string | undefined = '';
|
||||
let stageCursor: string | undefined = 'none';
|
||||
|
||||
if (tool === 'move' || isStaging) {
|
||||
if (isMovingStage) {
|
||||
@ -60,10 +62,8 @@ const selector = createSelector(
|
||||
}
|
||||
} else if (isTransformingBoundingBox) {
|
||||
stageCursor = undefined;
|
||||
} else if (isMouseOverBoundingBox) {
|
||||
stageCursor = 'move';
|
||||
} else {
|
||||
stageCursor = 'none';
|
||||
} else if (shouldRestrictStrokesToBox && !isMouseOverBoundingBox) {
|
||||
stageCursor = 'default';
|
||||
}
|
||||
|
||||
return {
|
||||
@ -129,7 +129,6 @@ const IAICanvas = () => {
|
||||
didMouseMoveRef,
|
||||
lastCursorPositionRef
|
||||
);
|
||||
const handleMouseEnter = useCanvasMouseEnter(stageRef);
|
||||
const handleMouseOut = useCanvasMouseOut();
|
||||
const { handleDragStart, handleDragMove, handleDragEnd } =
|
||||
useCanvasDragMove();
|
||||
@ -153,16 +152,16 @@ const IAICanvas = () => {
|
||||
onTouchMove={handleMouseMove}
|
||||
onTouchEnd={handleMouseUp}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseOut}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseOut={handleMouseOut}
|
||||
onMouseUp={handleMouseUp}
|
||||
onDragStart={handleDragStart}
|
||||
onDragMove={handleDragMove}
|
||||
onDragEnd={handleDragEnd}
|
||||
onContextMenu={(e: KonvaEventObject<MouseEvent>) =>
|
||||
e.evt.preventDefault()
|
||||
}
|
||||
onWheel={handleWheel}
|
||||
listening={(tool === 'move' || isStaging) && !isModifyingBoundingBox}
|
||||
draggable={(tool === 'move' || isStaging) && !isModifyingBoundingBox}
|
||||
>
|
||||
<Layer id={'grid'} visible={shouldShowGrid}>
|
||||
@ -181,6 +180,9 @@ const IAICanvas = () => {
|
||||
<IAICanvasMaskLines visible={true} listening={false} />
|
||||
<IAICanvasMaskCompositer listening={false} />
|
||||
</Layer>
|
||||
<Layer>
|
||||
<IAICanvasBoundingBoxOverlay />
|
||||
</Layer>
|
||||
<Layer id="preview" imageSmoothingEnabled={false}>
|
||||
{!isStaging && (
|
||||
<IAICanvasToolPreview
|
||||
|
@ -0,0 +1,69 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store';
|
||||
import _ from 'lodash';
|
||||
import { Group, Rect } from 'react-konva';
|
||||
import { canvasSelector } from '../store/canvasSelectors';
|
||||
|
||||
const selector = createSelector(
|
||||
canvasSelector,
|
||||
(canvas) => {
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
shouldDarkenOutsideBoundingBox,
|
||||
stageCoordinates,
|
||||
} = canvas;
|
||||
|
||||
return {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
shouldDarkenOutsideBoundingBox,
|
||||
stageCoordinates,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: _.isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
const IAICanvasBoundingBoxOverlay = () => {
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
shouldDarkenOutsideBoundingBox,
|
||||
stageCoordinates,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
} = useAppSelector(selector);
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<Rect
|
||||
offsetX={stageCoordinates.x / stageScale}
|
||||
offsetY={stageCoordinates.y / stageScale}
|
||||
height={stageDimensions.height / stageScale}
|
||||
width={stageDimensions.width / stageScale}
|
||||
fill={'rgba(0,0,0,0.4)'}
|
||||
listening={false}
|
||||
visible={shouldDarkenOutsideBoundingBox}
|
||||
/>
|
||||
<Rect
|
||||
x={boundingBoxCoordinates.x}
|
||||
y={boundingBoxCoordinates.y}
|
||||
width={boundingBoxDimensions.width}
|
||||
height={boundingBoxDimensions.height}
|
||||
fill={'rgb(255,255,255)'}
|
||||
listening={false}
|
||||
visible={shouldDarkenOutsideBoundingBox}
|
||||
globalCompositeOperation={'destination-out'}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAICanvasBoundingBoxOverlay;
|
@ -1,8 +1,13 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store';
|
||||
import _ from 'lodash';
|
||||
import { Group, Line } from 'react-konva';
|
||||
import { isCanvasBaseImage, isCanvasBaseLine } from '../store/canvasTypes';
|
||||
import { Group, Line, Rect } from 'react-konva';
|
||||
import {
|
||||
isCanvasBaseImage,
|
||||
isCanvasBaseLine,
|
||||
isCanvasEraseRect,
|
||||
isCanvasFillRect,
|
||||
} from '../store/canvasTypes';
|
||||
import IAICanvasImage from './IAICanvasImage';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
@ -37,7 +42,7 @@ const IAICanvasObjectRenderer = () => {
|
||||
<IAICanvasImage key={i} x={obj.x} y={obj.y} url={obj.image.url} />
|
||||
);
|
||||
} else if (isCanvasBaseLine(obj)) {
|
||||
return (
|
||||
const line = (
|
||||
<Line
|
||||
key={i}
|
||||
points={obj.points}
|
||||
@ -53,6 +58,44 @@ const IAICanvasObjectRenderer = () => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
if (obj.clip) {
|
||||
return (
|
||||
<Group
|
||||
key={i}
|
||||
clipX={obj.clip.x}
|
||||
clipY={obj.clip.y}
|
||||
clipWidth={obj.clip.width}
|
||||
clipHeight={obj.clip.height}
|
||||
>
|
||||
{line}
|
||||
</Group>
|
||||
);
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
} else if (isCanvasFillRect(obj)) {
|
||||
return (
|
||||
<Rect
|
||||
key={i}
|
||||
x={obj.x}
|
||||
y={obj.y}
|
||||
width={obj.width}
|
||||
height={obj.height}
|
||||
fill={rgbaColorToString(obj.color)}
|
||||
/>
|
||||
);
|
||||
} else if (isCanvasEraseRect(obj)) {
|
||||
return (
|
||||
<Rect
|
||||
key={i}
|
||||
x={obj.x}
|
||||
y={obj.y}
|
||||
width={obj.width}
|
||||
height={obj.height}
|
||||
fill={'rgb(255, 255, 255)'}
|
||||
globalCompositeOperation={'destination-out'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Group>
|
||||
|
@ -15,6 +15,8 @@ const selector = createSelector(
|
||||
},
|
||||
shouldShowStagingImage,
|
||||
shouldShowStagingOutline,
|
||||
boundingBoxCoordinates: { x, y },
|
||||
boundingBoxDimensions: { width, height },
|
||||
} = canvas;
|
||||
|
||||
return {
|
||||
@ -24,6 +26,10 @@ const selector = createSelector(
|
||||
isOnLastImage: selectedImageIndex === images.length - 1,
|
||||
shouldShowStagingImage,
|
||||
shouldShowStagingOutline,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -41,19 +47,17 @@ const IAICanvasStagingArea = (props: Props) => {
|
||||
currentStagingAreaImage,
|
||||
shouldShowStagingImage,
|
||||
shouldShowStagingOutline,
|
||||
} = useAppSelector(selector);
|
||||
|
||||
if (!currentStagingAreaImage) return null;
|
||||
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
image: { width, height, url },
|
||||
} = currentStagingAreaImage;
|
||||
width,
|
||||
height,
|
||||
} = useAppSelector(selector);
|
||||
|
||||
return (
|
||||
<Group {...rest}>
|
||||
{shouldShowStagingImage && <IAICanvasImage url={url} x={x} y={y} />}
|
||||
{shouldShowStagingImage && currentStagingAreaImage && (
|
||||
<IAICanvasImage url={currentStagingAreaImage.image.url} x={x} y={y} />
|
||||
)}
|
||||
{shouldShowStagingOutline && (
|
||||
<Group>
|
||||
<Rect
|
||||
@ -62,7 +66,7 @@ const IAICanvasStagingArea = (props: Props) => {
|
||||
width={width}
|
||||
height={height}
|
||||
strokeWidth={1}
|
||||
stroke={'black'}
|
||||
stroke={'white'}
|
||||
strokeScaleEnabled={false}
|
||||
/>
|
||||
<Rect
|
||||
@ -72,7 +76,7 @@ const IAICanvasStagingArea = (props: Props) => {
|
||||
height={height}
|
||||
dash={[4, 4]}
|
||||
strokeWidth={1}
|
||||
stroke={'white'}
|
||||
stroke={'black'}
|
||||
strokeScaleEnabled={false}
|
||||
/>
|
||||
</Group>
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
FaCheck,
|
||||
FaEye,
|
||||
FaEyeSlash,
|
||||
FaPlus,
|
||||
FaSave,
|
||||
FaTrash,
|
||||
} from 'react-icons/fa';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
@ -62,11 +62,11 @@ const IAICanvasStagingAreaToolbar = () => {
|
||||
} = useAppSelector(selector);
|
||||
|
||||
const handleMouseOver = useCallback(() => {
|
||||
dispatch(setShouldShowStagingOutline(false));
|
||||
dispatch(setShouldShowStagingOutline(true));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleMouseOut = useCallback(() => {
|
||||
dispatch(setShouldShowStagingOutline(true));
|
||||
dispatch(setShouldShowStagingOutline(false));
|
||||
}, [dispatch]);
|
||||
|
||||
useHotkeys(
|
||||
@ -167,10 +167,11 @@ const IAICanvasStagingAreaToolbar = () => {
|
||||
<IAIIconButton
|
||||
tooltip="Discard All"
|
||||
aria-label="Discard All"
|
||||
icon={<FaTrash />}
|
||||
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
|
||||
onClick={() => dispatch(discardStagedImages())}
|
||||
data-selected={true}
|
||||
style={{ backgroundColor: 'var(--btn-delete-image)' }}
|
||||
fontSize={20}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
|
@ -15,7 +15,6 @@ const canvasBrushPreviewSelector = createSelector(
|
||||
(canvas) => {
|
||||
const {
|
||||
cursorPosition,
|
||||
stageDimensions: { width, height },
|
||||
brushSize,
|
||||
colorPickerColor,
|
||||
maskColor,
|
||||
@ -26,12 +25,64 @@ const canvasBrushPreviewSelector = createSelector(
|
||||
isMovingBoundingBox,
|
||||
isTransformingBoundingBox,
|
||||
stageScale,
|
||||
stageDimensions,
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
shouldRestrictStrokesToBox,
|
||||
} = canvas;
|
||||
|
||||
const clip = shouldRestrictStrokesToBox
|
||||
? {
|
||||
clipX: boundingBoxCoordinates.x,
|
||||
clipY: boundingBoxCoordinates.y,
|
||||
clipWidth: boundingBoxDimensions.width,
|
||||
clipHeight: boundingBoxDimensions.height,
|
||||
}
|
||||
: {};
|
||||
|
||||
// // big brain time; this is the *inverse* of the clip that is needed for shouldRestrictStrokesToBox
|
||||
// // it took some fiddling to work out, so I am leaving it here in case it is needed for something else...
|
||||
// const clipFunc = shouldRestrictStrokesToBox
|
||||
// ? (ctx: SceneContext) => {
|
||||
// console.log(
|
||||
// stageCoordinates.x / stageScale,
|
||||
// stageCoordinates.y / stageScale,
|
||||
// stageDimensions.height / stageScale,
|
||||
// stageDimensions.width / stageScale
|
||||
// );
|
||||
// ctx.fillStyle = 'red';
|
||||
// ctx.rect(
|
||||
// -stageCoordinates.x / stageScale,
|
||||
// -stageCoordinates.y / stageScale,
|
||||
// stageDimensions.width / stageScale,
|
||||
// stageCoordinates.y / stageScale + boundingBoxCoordinates.y
|
||||
// );
|
||||
// ctx.rect(
|
||||
// -stageCoordinates.x / stageScale,
|
||||
// boundingBoxCoordinates.y + boundingBoxDimensions.height,
|
||||
// stageDimensions.width / stageScale,
|
||||
// stageDimensions.height / stageScale
|
||||
// );
|
||||
// ctx.rect(
|
||||
// -stageCoordinates.x / stageScale,
|
||||
// -stageCoordinates.y / stageScale,
|
||||
// stageCoordinates.x / stageScale + boundingBoxCoordinates.x,
|
||||
// stageDimensions.height / stageScale
|
||||
// );
|
||||
// ctx.rect(
|
||||
// boundingBoxCoordinates.x + boundingBoxDimensions.width,
|
||||
// -stageCoordinates.y / stageScale,
|
||||
// stageDimensions.width / stageScale -
|
||||
// (boundingBoxCoordinates.x + boundingBoxDimensions.width),
|
||||
// stageDimensions.height / stageScale
|
||||
// );
|
||||
// }
|
||||
// : undefined;
|
||||
|
||||
return {
|
||||
cursorPosition,
|
||||
width,
|
||||
height,
|
||||
brushX: cursorPosition ? cursorPosition.x : stageDimensions.width / 2,
|
||||
brushY: cursorPosition ? cursorPosition.y : stageDimensions.height / 2,
|
||||
radius: brushSize / 2,
|
||||
colorPickerOuterRadius: COLOR_PICKER_SIZE / stageScale,
|
||||
colorPickerInnerRadius:
|
||||
@ -50,6 +101,7 @@ const canvasBrushPreviewSelector = createSelector(
|
||||
) && shouldShowBrush,
|
||||
strokeWidth: 1.5 / stageScale,
|
||||
dotRadius: 1.5 / stageScale,
|
||||
clip,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -65,9 +117,8 @@ const canvasBrushPreviewSelector = createSelector(
|
||||
const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
const { ...rest } = props;
|
||||
const {
|
||||
cursorPosition,
|
||||
width,
|
||||
height,
|
||||
brushX,
|
||||
brushY,
|
||||
radius,
|
||||
maskColorString,
|
||||
tool,
|
||||
@ -79,25 +130,26 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
colorPickerColorString,
|
||||
colorPickerInnerRadius,
|
||||
colorPickerOuterRadius,
|
||||
clip,
|
||||
} = useAppSelector(canvasBrushPreviewSelector);
|
||||
|
||||
if (!shouldDrawBrushPreview) return null;
|
||||
|
||||
return (
|
||||
<Group listening={false} {...rest}>
|
||||
<Group listening={false} {...clip} {...rest}>
|
||||
{tool === 'colorPicker' ? (
|
||||
<>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={colorPickerOuterRadius}
|
||||
stroke={brushColorString}
|
||||
strokeWidth={COLOR_PICKER_STROKE_RADIUS}
|
||||
strokeScaleEnabled={false}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={colorPickerInnerRadius}
|
||||
stroke={colorPickerColorString}
|
||||
strokeWidth={COLOR_PICKER_STROKE_RADIUS}
|
||||
@ -107,17 +159,17 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
) : (
|
||||
<>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={radius}
|
||||
fill={layer === 'mask' ? maskColorString : brushColorString}
|
||||
globalCompositeOperation={
|
||||
tool === 'eraser' ? 'destination-out' : 'source-over'
|
||||
tool === 'eraser' ? 'destination-out' : 'source-out'
|
||||
}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={radius}
|
||||
stroke={'rgba(255,255,255,0.4)'}
|
||||
strokeWidth={strokeWidth * 2}
|
||||
@ -125,8 +177,8 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={radius}
|
||||
stroke={'rgba(0,0,0,1)'}
|
||||
strokeWidth={strokeWidth}
|
||||
@ -136,15 +188,15 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
</>
|
||||
)}
|
||||
<Circle
|
||||
x={cursorPosition ? cursorPosition.x : width / 2}
|
||||
y={cursorPosition ? cursorPosition.y : height / 2}
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
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}
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={dotRadius}
|
||||
fill={'rgba(0,0,0,1)'}
|
||||
listening={false}
|
||||
|
@ -3,7 +3,7 @@ import Konva from 'konva';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Group, Rect, Transformer } from 'react-konva';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import {
|
||||
@ -26,15 +26,11 @@ const boundingBoxPreviewSelector = createSelector(
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
isDrawing,
|
||||
isTransformingBoundingBox,
|
||||
isMovingBoundingBox,
|
||||
isMouseOverBoundingBox,
|
||||
shouldDarkenOutsideBoundingBox,
|
||||
tool,
|
||||
stageCoordinates,
|
||||
shouldSnapToGrid,
|
||||
} = canvas;
|
||||
|
||||
@ -42,16 +38,11 @@ const boundingBoxPreviewSelector = createSelector(
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
isDrawing,
|
||||
isMouseOverBoundingBox,
|
||||
shouldDarkenOutsideBoundingBox,
|
||||
isMovingBoundingBox,
|
||||
isTransformingBoundingBox,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
shouldSnapToGrid,
|
||||
tool,
|
||||
stageCoordinates,
|
||||
boundingBoxStrokeWidth: (isMouseOverBoundingBox ? 8 : 1) / stageScale,
|
||||
hitStrokeWidth: 20 / stageScale,
|
||||
};
|
||||
},
|
||||
@ -72,22 +63,20 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
isDrawing,
|
||||
isMouseOverBoundingBox,
|
||||
shouldDarkenOutsideBoundingBox,
|
||||
isMovingBoundingBox,
|
||||
isTransformingBoundingBox,
|
||||
stageCoordinates,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
shouldSnapToGrid,
|
||||
tool,
|
||||
boundingBoxStrokeWidth,
|
||||
hitStrokeWidth,
|
||||
} = useAppSelector(boundingBoxPreviewSelector);
|
||||
|
||||
const transformerRef = useRef<Konva.Transformer>(null);
|
||||
const shapeRef = useRef<Konva.Rect>(null);
|
||||
|
||||
const [isMouseOverBoundingBoxOutline, setIsMouseOverBoundingBoxOutline] =
|
||||
useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!transformerRef.current || !shapeRef.current) return;
|
||||
transformerRef.current.nodes([shapeRef.current]);
|
||||
@ -205,7 +194,9 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
|
||||
const handleEndedTransforming = () => {
|
||||
dispatch(setIsTransformingBoundingBox(false));
|
||||
dispatch(setIsMovingBoundingBox(false));
|
||||
dispatch(setIsMouseOverBoundingBox(false));
|
||||
setIsMouseOverBoundingBoxOutline(false);
|
||||
};
|
||||
|
||||
const handleStartedMoving = () => {
|
||||
@ -216,38 +207,38 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
dispatch(setIsTransformingBoundingBox(false));
|
||||
dispatch(setIsMovingBoundingBox(false));
|
||||
dispatch(setIsMouseOverBoundingBox(false));
|
||||
setIsMouseOverBoundingBoxOutline(false);
|
||||
};
|
||||
|
||||
const handleMouseOver = () => {
|
||||
dispatch(setIsMouseOverBoundingBox(true));
|
||||
setIsMouseOverBoundingBoxOutline(true);
|
||||
};
|
||||
|
||||
const handleMouseOut = () => {
|
||||
!isTransformingBoundingBox &&
|
||||
!isMovingBoundingBox &&
|
||||
dispatch(setIsMouseOverBoundingBox(false));
|
||||
setIsMouseOverBoundingBoxOutline(false);
|
||||
};
|
||||
|
||||
const handleMouseEnterBoundingBox = () => {
|
||||
dispatch(setIsMouseOverBoundingBox(true));
|
||||
};
|
||||
|
||||
const handleMouseLeaveBoundingBox = () => {
|
||||
dispatch(setIsMouseOverBoundingBox(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<Group {...rest}>
|
||||
<Rect
|
||||
offsetX={stageCoordinates.x / stageScale}
|
||||
offsetY={stageCoordinates.y / stageScale}
|
||||
height={stageDimensions.height / stageScale}
|
||||
width={stageDimensions.width / stageScale}
|
||||
fill={'rgba(0,0,0,0.4)'}
|
||||
listening={false}
|
||||
visible={shouldDarkenOutsideBoundingBox}
|
||||
/>
|
||||
<Rect
|
||||
height={boundingBoxDimensions.height}
|
||||
width={boundingBoxDimensions.width}
|
||||
x={boundingBoxCoordinates.x}
|
||||
y={boundingBoxCoordinates.y}
|
||||
width={boundingBoxDimensions.width}
|
||||
height={boundingBoxDimensions.height}
|
||||
fill={'rgb(255,255,255)'}
|
||||
listening={false}
|
||||
visible={shouldDarkenOutsideBoundingBox}
|
||||
globalCompositeOperation={'destination-out'}
|
||||
onMouseEnter={handleMouseEnterBoundingBox}
|
||||
onMouseOver={handleMouseEnterBoundingBox}
|
||||
onMouseLeave={handleMouseLeaveBoundingBox}
|
||||
onMouseOut={handleMouseLeaveBoundingBox}
|
||||
/>
|
||||
<Rect
|
||||
draggable={true}
|
||||
@ -255,17 +246,21 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
height={boundingBoxDimensions.height}
|
||||
hitStrokeWidth={hitStrokeWidth}
|
||||
listening={!isDrawing && tool === 'move'}
|
||||
onDragStart={handleStartedMoving}
|
||||
onDragEnd={handleEndedModifying}
|
||||
onDragMove={handleOnDragMove}
|
||||
onMouseDown={handleStartedMoving}
|
||||
onMouseOut={handleMouseOut}
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseEnter={handleMouseOver}
|
||||
onMouseUp={handleEndedModifying}
|
||||
onTransform={handleOnTransform}
|
||||
onTransformEnd={handleEndedTransforming}
|
||||
ref={shapeRef}
|
||||
stroke={isMouseOverBoundingBox ? 'rgba(255,255,255,0.7)' : 'white'}
|
||||
strokeWidth={boundingBoxStrokeWidth}
|
||||
stroke={
|
||||
isMouseOverBoundingBoxOutline ? 'rgba(255,255,255,0.7)' : 'white'
|
||||
}
|
||||
strokeWidth={(isMouseOverBoundingBoxOutline ? 8 : 1) / stageScale}
|
||||
width={boundingBoxDimensions.width}
|
||||
x={boundingBoxCoordinates.x}
|
||||
y={boundingBoxCoordinates.y}
|
||||
@ -285,6 +280,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
ignoreStroke={true}
|
||||
keepRatio={false}
|
||||
listening={!isDrawing && tool === 'move'}
|
||||
onDragStart={handleStartedMoving}
|
||||
onDragEnd={handleEndedModifying}
|
||||
onMouseDown={handleStartedTransforming}
|
||||
onMouseUp={handleEndedTransforming}
|
||||
|
@ -15,13 +15,16 @@ import IAIPopover from 'common/components/IAIPopover';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
|
||||
export const selector = createSelector(
|
||||
[canvasSelector],
|
||||
(canvas) => {
|
||||
[canvasSelector, isStagingSelector],
|
||||
(canvas, isStaging) => {
|
||||
const { maskColor, layer, isMaskEnabled, shouldPreserveMaskedArea } =
|
||||
canvas;
|
||||
|
||||
@ -31,6 +34,7 @@ export const selector = createSelector(
|
||||
maskColorString: rgbaColorToString(maskColor),
|
||||
isMaskEnabled,
|
||||
shouldPreserveMaskedArea,
|
||||
isStaging,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -41,8 +45,13 @@ export const selector = createSelector(
|
||||
);
|
||||
const IAICanvasMaskOptions = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { layer, maskColor, isMaskEnabled, shouldPreserveMaskedArea } =
|
||||
useAppSelector(selector);
|
||||
const {
|
||||
layer,
|
||||
maskColor,
|
||||
isMaskEnabled,
|
||||
shouldPreserveMaskedArea,
|
||||
isStaging,
|
||||
} = useAppSelector(selector);
|
||||
|
||||
useHotkeys(
|
||||
['q'],
|
||||
@ -50,7 +59,7 @@ const IAICanvasMaskOptions = () => {
|
||||
handleToggleMaskLayer();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[layer]
|
||||
@ -62,7 +71,7 @@ const IAICanvasMaskOptions = () => {
|
||||
handleClearMask();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[]
|
||||
@ -74,7 +83,7 @@ const IAICanvasMaskOptions = () => {
|
||||
handleToggleEnableMask();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[isMaskEnabled]
|
||||
@ -103,6 +112,7 @@ const IAICanvasMaskOptions = () => {
|
||||
? { backgroundColor: 'var(--accent-color)' }
|
||||
: { backgroundColor: 'var(--btn-base-color)' }
|
||||
}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
}
|
||||
|
@ -8,14 +8,15 @@ import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { redo } from 'features/canvas/store/canvasSlice';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
|
||||
const canvasRedoSelector = createSelector(
|
||||
[canvasSelector, activeTabNameSelector],
|
||||
(canvas, activeTabName) => {
|
||||
[canvasSelector, activeTabNameSelector, systemSelector],
|
||||
(canvas, activeTabName, system) => {
|
||||
const { futureLayerStates } = canvas;
|
||||
|
||||
return {
|
||||
canRedo: futureLayerStates.length > 0,
|
||||
canRedo: futureLayerStates.length > 0 && !system.isProcessing,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
setShouldAutoSave,
|
||||
setShouldCropToBoundingBoxOnSave,
|
||||
setShouldDarkenOutsideBoundingBox,
|
||||
setShouldRestrictStrokesToBox,
|
||||
setShouldShowCanvasDebugInfo,
|
||||
setShouldShowGrid,
|
||||
setShouldShowIntermediates,
|
||||
@ -32,6 +33,7 @@ export const canvasControlsSelector = createSelector(
|
||||
shouldShowGrid,
|
||||
shouldShowIntermediates,
|
||||
shouldSnapToGrid,
|
||||
shouldRestrictStrokesToBox,
|
||||
} = canvas;
|
||||
|
||||
return {
|
||||
@ -42,6 +44,7 @@ export const canvasControlsSelector = createSelector(
|
||||
shouldShowGrid,
|
||||
shouldShowIntermediates,
|
||||
shouldSnapToGrid,
|
||||
shouldRestrictStrokesToBox,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -61,6 +64,7 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
shouldShowGrid,
|
||||
shouldShowIntermediates,
|
||||
shouldSnapToGrid,
|
||||
shouldRestrictStrokesToBox,
|
||||
} = useAppSelector(canvasControlsSelector);
|
||||
|
||||
useHotkeys(
|
||||
@ -126,6 +130,13 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
label="Limit Strokes to Box"
|
||||
isChecked={shouldRestrictStrokesToBox}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
label="Show Canvas Debug Info"
|
||||
isChecked={shouldShowCanvasDebugInfo}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import {
|
||||
addEraseRect,
|
||||
addFillRect,
|
||||
setBrushColor,
|
||||
setBrushSize,
|
||||
setTool,
|
||||
@ -11,7 +13,9 @@ import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import {
|
||||
FaEraser,
|
||||
FaEyeDropper,
|
||||
FaFillDrip,
|
||||
FaPaintBrush,
|
||||
FaPlus,
|
||||
FaSlidersH,
|
||||
} from 'react-icons/fa';
|
||||
import {
|
||||
@ -55,7 +59,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
handleSelectBrushTool();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[]
|
||||
@ -67,7 +71,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
handleSelectEraserTool();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[tool]
|
||||
@ -79,19 +83,41 @@ const IAICanvasToolChooserOptions = () => {
|
||||
handleSelectColorPickerTool();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[tool]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['shift+f'],
|
||||
() => {
|
||||
handleFillRect();
|
||||
},
|
||||
{
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
}
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['delete', 'backspace'],
|
||||
() => {
|
||||
handleEraseBoundingBox();
|
||||
},
|
||||
{
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
}
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
['BracketLeft'],
|
||||
() => {
|
||||
dispatch(setBrushSize(Math.max(brushSize - 5, 5)));
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[brushSize]
|
||||
@ -103,7 +129,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
dispatch(setBrushSize(Math.min(brushSize + 5, 500)));
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[brushSize]
|
||||
@ -120,7 +146,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
);
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[brushColor]
|
||||
@ -137,7 +163,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
);
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[brushColor]
|
||||
@ -146,6 +172,8 @@ const IAICanvasToolChooserOptions = () => {
|
||||
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
||||
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
||||
const handleSelectColorPickerTool = () => dispatch(setTool('colorPicker'));
|
||||
const handleFillRect = () => dispatch(addFillRect());
|
||||
const handleEraseBoundingBox = () => dispatch(addEraseRect());
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
@ -165,6 +193,20 @@ const IAICanvasToolChooserOptions = () => {
|
||||
isDisabled={isStaging}
|
||||
onClick={handleSelectEraserTool}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Fill Bounding Box (Shift+F)"
|
||||
tooltip="Fill Bounding Box (Shift+F)"
|
||||
icon={<FaFillDrip />}
|
||||
isDisabled={isStaging}
|
||||
onClick={handleFillRect}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Erase Bounding Box Area (Delete/Backspace)"
|
||||
tooltip="Erase Bounding Box Area (Delete/Backspace)"
|
||||
icon={<FaPlus style={{ transform: 'rotate(45deg)' }} />}
|
||||
isDisabled={isStaging}
|
||||
onClick={handleEraseBoundingBox}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Color Picker (C)"
|
||||
tooltip="Color Picker (C)"
|
||||
|
@ -85,7 +85,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
handleSelectMoveTool();
|
||||
},
|
||||
{
|
||||
enabled: () => true,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[]
|
||||
@ -109,7 +109,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
handleMergeVisible();
|
||||
},
|
||||
{
|
||||
enabled: () => !isProcessing,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing]
|
||||
@ -121,7 +121,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
handleSaveToGallery();
|
||||
},
|
||||
{
|
||||
enabled: () => !isProcessing,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing]
|
||||
@ -133,7 +133,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
handleCopyImageToClipboard();
|
||||
},
|
||||
{
|
||||
enabled: () => !isProcessing,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing]
|
||||
@ -145,7 +145,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
handleDownloadAsImage();
|
||||
},
|
||||
{
|
||||
enabled: () => !isProcessing,
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[canvasBaseLayer, isProcessing]
|
||||
@ -226,6 +226,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
value={layer}
|
||||
validValues={LAYER_NAMES_DICT}
|
||||
onChange={handleChangeLayer}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
|
||||
<IAICanvasMaskOptions />
|
||||
@ -253,28 +254,28 @@ const IAICanvasOutpaintingControls = () => {
|
||||
tooltip="Merge Visible (Shift+M)"
|
||||
icon={<FaLayerGroup />}
|
||||
onClick={handleMergeVisible}
|
||||
isDisabled={isProcessing}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Save to Gallery (Shift+S)"
|
||||
tooltip="Save to Gallery (Shift+S)"
|
||||
icon={<FaSave />}
|
||||
onClick={handleSaveToGallery}
|
||||
isDisabled={isProcessing}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Copy to Clipboard (Cmd/Ctrl+C)"
|
||||
tooltip="Copy to Clipboard (Cmd/Ctrl+C)"
|
||||
icon={<FaCopy />}
|
||||
onClick={handleCopyImageToClipboard}
|
||||
isDisabled={isProcessing}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Download as Image (Shift+D)"
|
||||
tooltip="Download as Image (Shift+D)"
|
||||
icon={<FaDownload />}
|
||||
onClick={handleDownloadAsImage}
|
||||
isDisabled={isProcessing}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup isAttached>
|
||||
@ -288,6 +289,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
tooltip="Upload"
|
||||
icon={<FaUpload />}
|
||||
onClick={openUploader}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
<IAIIconButton
|
||||
aria-label="Clear Canvas"
|
||||
@ -295,6 +297,7 @@ const IAICanvasOutpaintingControls = () => {
|
||||
icon={<FaTrash />}
|
||||
onClick={handleResetCanvas}
|
||||
style={{ backgroundColor: 'var(--btn-delete-image)' }}
|
||||
isDisabled={isStaging}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup isAttached>
|
||||
|
@ -8,14 +8,15 @@ import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import _ from 'lodash';
|
||||
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||
import { undo } from 'features/canvas/store/canvasSlice';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
|
||||
const canvasUndoSelector = createSelector(
|
||||
[canvasSelector, activeTabNameSelector],
|
||||
(canvas, activeTabName) => {
|
||||
[canvasSelector, activeTabNameSelector, systemSelector],
|
||||
(canvas, activeTabName, system) => {
|
||||
const { pastLayerStates } = canvas;
|
||||
|
||||
return {
|
||||
canUndo: pastLayerStates.length > 0,
|
||||
canUndo: pastLayerStates.length > 0 && !system.isProcessing,
|
||||
activeTabName,
|
||||
};
|
||||
},
|
||||
|
@ -3,7 +3,10 @@ import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import _ from 'lodash';
|
||||
import { useCallback } from 'react';
|
||||
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
setIsMovingStage,
|
||||
setStageCoordinates,
|
||||
@ -12,10 +15,11 @@ import {
|
||||
const selector = createSelector(
|
||||
[canvasSelector, isStagingSelector],
|
||||
(canvas, isStaging) => {
|
||||
const { tool } = canvas;
|
||||
const { tool, isMovingBoundingBox } = canvas;
|
||||
return {
|
||||
tool,
|
||||
isStaging,
|
||||
isMovingBoundingBox,
|
||||
};
|
||||
},
|
||||
{ memoizeOptions: { resultEqualityCheck: _.isEqual } }
|
||||
@ -23,29 +27,29 @@ const selector = createSelector(
|
||||
|
||||
const useCanvasDrag = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { tool, isStaging } = useAppSelector(selector);
|
||||
const { tool, isStaging, isMovingBoundingBox } = useAppSelector(selector);
|
||||
|
||||
return {
|
||||
handleDragStart: useCallback(() => {
|
||||
if (!(tool === 'move' || isStaging)) return;
|
||||
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) return;
|
||||
dispatch(setIsMovingStage(true));
|
||||
}, [dispatch, isStaging, tool]),
|
||||
}, [dispatch, isMovingBoundingBox, isStaging, tool]),
|
||||
|
||||
handleDragMove: useCallback(
|
||||
(e: KonvaEventObject<MouseEvent>) => {
|
||||
if (!(tool === 'move' || isStaging)) return;
|
||||
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) return;
|
||||
|
||||
const newCoordinates = { x: e.target.x(), y: e.target.y() };
|
||||
|
||||
dispatch(setStageCoordinates(newCoordinates));
|
||||
},
|
||||
[dispatch, isStaging, tool]
|
||||
[dispatch, isMovingBoundingBox, isStaging, tool]
|
||||
),
|
||||
|
||||
handleDragEnd: useCallback(() => {
|
||||
if (!(tool === 'move' || isStaging)) return;
|
||||
if (!((tool === 'move' || isStaging) && !isMovingBoundingBox)) return;
|
||||
dispatch(setIsMovingStage(false));
|
||||
}, [dispatch, isStaging, tool]),
|
||||
}, [dispatch, isMovingBoundingBox, isStaging, tool]),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -9,13 +9,16 @@ import {
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import { useRef } from 'react';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
canvasSelector,
|
||||
isStagingSelector,
|
||||
} from 'features/canvas/store/canvasSelectors';
|
||||
import { CanvasTool } from '../store/canvasTypes';
|
||||
import { getCanvasStage } from '../util/konvaInstanceProvider';
|
||||
|
||||
const selector = createSelector(
|
||||
[canvasSelector, activeTabNameSelector],
|
||||
(canvas, activeTabName) => {
|
||||
[canvasSelector, activeTabNameSelector, isStagingSelector],
|
||||
(canvas, activeTabName, isStaging) => {
|
||||
const {
|
||||
cursorPosition,
|
||||
shouldLockBoundingBox,
|
||||
@ -29,6 +32,7 @@ const selector = createSelector(
|
||||
shouldLockBoundingBox,
|
||||
shouldShowBoundingBox,
|
||||
tool,
|
||||
isStaging,
|
||||
};
|
||||
},
|
||||
{
|
||||
@ -40,7 +44,7 @@ const selector = createSelector(
|
||||
|
||||
const useInpaintingCanvasHotkeys = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { activeTabName, shouldShowBoundingBox, tool } =
|
||||
const { activeTabName, shouldShowBoundingBox, tool, isStaging } =
|
||||
useAppSelector(selector);
|
||||
|
||||
const previousToolRef = useRef<CanvasTool | null>(null);
|
||||
@ -64,6 +68,7 @@ const useInpaintingCanvasHotkeys = () => {
|
||||
dispatch(setShouldShowBoundingBox(!shouldShowBoundingBox));
|
||||
},
|
||||
{
|
||||
enabled: () => !isStaging,
|
||||
preventDefault: true,
|
||||
},
|
||||
[activeTabName, shouldShowBoundingBox]
|
||||
|
@ -1,53 +0,0 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||
import Konva from 'konva';
|
||||
import { KonvaEventObject } from 'konva/lib/Node';
|
||||
import _ from 'lodash';
|
||||
import { MutableRefObject, useCallback } from 'react';
|
||||
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
addLine,
|
||||
setIsDrawing,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import getScaledCursorPosition from '../util/getScaledCursorPosition';
|
||||
|
||||
const selector = createSelector(
|
||||
[activeTabNameSelector, canvasSelector, isStagingSelector],
|
||||
(activeTabName, canvas, isStaging) => {
|
||||
const { tool } = canvas;
|
||||
return {
|
||||
tool,
|
||||
activeTabName,
|
||||
isStaging,
|
||||
};
|
||||
},
|
||||
{ memoizeOptions: { resultEqualityCheck: _.isEqual } }
|
||||
);
|
||||
|
||||
const useCanvasMouseEnter = (
|
||||
stageRef: MutableRefObject<Konva.Stage | null>
|
||||
) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { tool, isStaging } = useAppSelector(selector);
|
||||
|
||||
return useCallback(
|
||||
(e: KonvaEventObject<MouseEvent>) => {
|
||||
if (e.evt.buttons !== 1) return;
|
||||
|
||||
if (!stageRef.current) return;
|
||||
|
||||
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
|
||||
|
||||
if (!scaledCursorPosition || tool === 'move' || isStaging) return;
|
||||
|
||||
dispatch(setIsDrawing(true));
|
||||
|
||||
// Add a new line starting from the current cursor position.
|
||||
dispatch(addLine([scaledCursorPosition.x, scaledCursorPosition.y]));
|
||||
},
|
||||
[stageRef, tool, isStaging, dispatch]
|
||||
);
|
||||
};
|
||||
|
||||
export default useCanvasMouseEnter;
|
@ -1,13 +1,12 @@
|
||||
import { useAppDispatch } from 'app/store';
|
||||
import { useCallback } from 'react';
|
||||
import { setCursorPosition, setIsDrawing } from 'features/canvas/store/canvasSlice';
|
||||
import { mouseLeftCanvas } from 'features/canvas/store/canvasSlice';
|
||||
|
||||
const useCanvasMouseOut = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return useCallback(() => {
|
||||
dispatch(setCursorPosition(null));
|
||||
dispatch(setIsDrawing(false));
|
||||
dispatch(mouseLeftCanvas());
|
||||
}, [dispatch]);
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,17 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store';
|
||||
import { activeTabNameSelector } from 'features/options/store/optionsSelectors';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes';
|
||||
|
||||
export const canvasSelector = (state: RootState): CanvasState => state.canvas;
|
||||
|
||||
export const isStagingSelector = (state: RootState): boolean =>
|
||||
state.canvas.layerState.stagingArea.images.length > 0;
|
||||
export const isStagingSelector = createSelector(
|
||||
[canvasSelector, activeTabNameSelector, systemSelector],
|
||||
(canvas, activeTabName, system) =>
|
||||
canvas.layerState.stagingArea.images.length > 0 ||
|
||||
(activeTabName === 'unifiedCanvas' && system.isProcessing)
|
||||
);
|
||||
|
||||
export const initialCanvasImageSelector = (
|
||||
state: RootState
|
||||
|
@ -12,12 +12,15 @@ import calculateCoordinates from '../util/calculateCoordinates';
|
||||
import calculateScale from '../util/calculateScale';
|
||||
import { STAGE_PADDING_PERCENTAGE } from '../util/constants';
|
||||
import floorCoordinates from '../util/floorCoordinates';
|
||||
import getScaledBoundingBoxDimensions from '../util/getScaledBoundingBoxDimensions';
|
||||
import roundDimensionsTo64 from '../util/roundDimensionsTo64';
|
||||
import {
|
||||
BoundingBoxScale,
|
||||
CanvasImage,
|
||||
CanvasLayer,
|
||||
CanvasLayerState,
|
||||
CanvasBaseLine,
|
||||
CanvasMaskLine,
|
||||
CanvasState,
|
||||
CanvasTool,
|
||||
Dimensions,
|
||||
@ -29,10 +32,6 @@ import {
|
||||
export const initialLayerState: CanvasLayerState = {
|
||||
objects: [],
|
||||
stagingArea: {
|
||||
x: -1,
|
||||
y: -1,
|
||||
width: -1,
|
||||
height: -1,
|
||||
images: [],
|
||||
selectedImageIndex: -1,
|
||||
},
|
||||
@ -72,6 +71,7 @@ const initialCanvasState: CanvasState = {
|
||||
shouldDarkenOutsideBoundingBox: false,
|
||||
shouldLockBoundingBox: false,
|
||||
shouldPreserveMaskedArea: false,
|
||||
shouldRestrictStrokesToBox: true,
|
||||
shouldShowBoundingBox: true,
|
||||
shouldShowBrush: true,
|
||||
shouldShowBrushPreview: false,
|
||||
@ -122,7 +122,7 @@ export const canvasSlice = createSlice({
|
||||
state.brushSize = action.payload;
|
||||
},
|
||||
clearMask: (state) => {
|
||||
state.pastLayerStates.push({ ...state.layerState });
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
state.layerState.objects = state.layerState.objects.filter(
|
||||
(obj) => !isCanvasMaskLine(obj)
|
||||
);
|
||||
@ -177,10 +177,17 @@ export const canvasSlice = createSlice({
|
||||
),
|
||||
};
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
state.pastLayerStates.push(state.layerState);
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
|
||||
state.layerState = {
|
||||
...initialLayerState,
|
||||
@ -220,65 +227,13 @@ export const canvasSlice = createSlice({
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.doesCanvasNeedScaling = true;
|
||||
},
|
||||
setStageDimensions: (state, action: PayloadAction<Dimensions>) => {
|
||||
state.stageDimensions = action.payload;
|
||||
|
||||
const { width: canvasWidth, height: canvasHeight } = action.payload;
|
||||
|
||||
const { width: boundingBoxWidth, height: boundingBoxHeight } =
|
||||
state.boundingBoxDimensions;
|
||||
|
||||
const newBoundingBoxWidth = roundDownToMultiple(
|
||||
_.clamp(boundingBoxWidth, 64, canvasWidth / state.stageScale),
|
||||
64
|
||||
);
|
||||
const newBoundingBoxHeight = roundDownToMultiple(
|
||||
_.clamp(boundingBoxHeight, 64, canvasHeight / state.stageScale),
|
||||
64
|
||||
);
|
||||
|
||||
state.boundingBoxDimensions = {
|
||||
width: newBoundingBoxWidth,
|
||||
height: newBoundingBoxHeight,
|
||||
};
|
||||
},
|
||||
setBoundingBoxDimensions: (state, action: PayloadAction<Dimensions>) => {
|
||||
const newDimensions = roundDimensionsTo64(action.payload);
|
||||
state.boundingBoxDimensions = newDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const { width, height } = newDimensions;
|
||||
const newScaledDimensions = { width, height };
|
||||
const targetArea = 512 * 512;
|
||||
const aspectRatio = width / height;
|
||||
let currentArea = width * height;
|
||||
let maxDimension = 448;
|
||||
while (currentArea < targetArea) {
|
||||
maxDimension += 64;
|
||||
if (width === height) {
|
||||
newScaledDimensions.width = 512;
|
||||
newScaledDimensions.height = 512;
|
||||
break;
|
||||
} else {
|
||||
if (aspectRatio > 1) {
|
||||
newScaledDimensions.width = maxDimension;
|
||||
newScaledDimensions.height = roundToMultiple(
|
||||
maxDimension / aspectRatio,
|
||||
64
|
||||
);
|
||||
} else if (aspectRatio < 1) {
|
||||
newScaledDimensions.height = maxDimension;
|
||||
newScaledDimensions.width = roundToMultiple(
|
||||
maxDimension * aspectRatio,
|
||||
64
|
||||
);
|
||||
}
|
||||
currentArea =
|
||||
newScaledDimensions.width * newScaledDimensions.height;
|
||||
}
|
||||
}
|
||||
|
||||
state.scaledBoundingBoxDimensions = newScaledDimensions;
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(newDimensions);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
},
|
||||
setBoundingBoxCoordinates: (state, action: PayloadAction<Vector2d>) => {
|
||||
@ -381,9 +336,49 @@ export const canvasSlice = createSlice({
|
||||
|
||||
state.futureLayerStates = [];
|
||||
state.shouldShowStagingOutline = true;
|
||||
state.shouldShowStagingOutline = true;
|
||||
},
|
||||
addFillRect: (state) => {
|
||||
const { boundingBoxCoordinates, boundingBoxDimensions, brushColor } =
|
||||
state;
|
||||
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
|
||||
if (state.pastLayerStates.length > state.maxHistory) {
|
||||
state.pastLayerStates.shift();
|
||||
}
|
||||
|
||||
state.layerState.objects.push({
|
||||
kind: 'fillRect',
|
||||
layer: 'base',
|
||||
...boundingBoxCoordinates,
|
||||
...boundingBoxDimensions,
|
||||
color: brushColor,
|
||||
});
|
||||
|
||||
state.futureLayerStates = [];
|
||||
},
|
||||
addEraseRect: (state) => {
|
||||
const { boundingBoxCoordinates, boundingBoxDimensions } = state;
|
||||
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
|
||||
if (state.pastLayerStates.length > state.maxHistory) {
|
||||
state.pastLayerStates.shift();
|
||||
}
|
||||
|
||||
state.layerState.objects.push({
|
||||
kind: 'eraseRect',
|
||||
layer: 'base',
|
||||
...boundingBoxCoordinates,
|
||||
...boundingBoxDimensions,
|
||||
});
|
||||
|
||||
state.futureLayerStates = [];
|
||||
},
|
||||
addLine: (state, action: PayloadAction<number[]>) => {
|
||||
const { tool, layer, brushColor, brushSize } = state;
|
||||
const { tool, layer, brushColor, brushSize, shouldRestrictStrokesToBox } =
|
||||
state;
|
||||
|
||||
if (tool === 'move' || tool === 'colorPicker') return;
|
||||
|
||||
@ -393,20 +388,29 @@ export const canvasSlice = createSlice({
|
||||
const newColor =
|
||||
layer === 'base' && tool === 'brush' ? { color: brushColor } : {};
|
||||
|
||||
state.pastLayerStates.push(state.layerState);
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
|
||||
if (state.pastLayerStates.length > state.maxHistory) {
|
||||
state.pastLayerStates.shift();
|
||||
}
|
||||
|
||||
state.layerState.objects.push({
|
||||
const newLine: CanvasMaskLine | CanvasBaseLine = {
|
||||
kind: 'line',
|
||||
layer,
|
||||
tool,
|
||||
strokeWidth: newStrokeWidth,
|
||||
points: action.payload,
|
||||
...newColor,
|
||||
});
|
||||
};
|
||||
|
||||
if (shouldRestrictStrokesToBox) {
|
||||
newLine.clip = {
|
||||
...state.boundingBoxCoordinates,
|
||||
...state.boundingBoxDimensions,
|
||||
};
|
||||
}
|
||||
|
||||
state.layerState.objects.push(newLine);
|
||||
|
||||
state.futureLayerStates = [];
|
||||
},
|
||||
@ -422,7 +426,7 @@ export const canvasSlice = createSlice({
|
||||
|
||||
if (!targetState) return;
|
||||
|
||||
state.futureLayerStates.unshift(state.layerState);
|
||||
state.futureLayerStates.unshift(_.cloneDeep(state.layerState));
|
||||
|
||||
if (state.futureLayerStates.length > state.maxHistory) {
|
||||
state.futureLayerStates.pop();
|
||||
@ -435,7 +439,7 @@ export const canvasSlice = createSlice({
|
||||
|
||||
if (!targetState) return;
|
||||
|
||||
state.pastLayerStates.push(state.layerState);
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
|
||||
if (state.pastLayerStates.length > state.maxHistory) {
|
||||
state.pastLayerStates.shift();
|
||||
@ -459,7 +463,7 @@ export const canvasSlice = createSlice({
|
||||
state.shouldShowIntermediates = action.payload;
|
||||
},
|
||||
resetCanvas: (state) => {
|
||||
state.pastLayerStates.push(state.layerState);
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
|
||||
state.layerState = initialLayerState;
|
||||
state.futureLayerStates = [];
|
||||
@ -501,11 +505,21 @@ export const canvasSlice = createSlice({
|
||||
newScale
|
||||
);
|
||||
|
||||
const newBoundingBoxDimensions = { width: 512, height: 512 };
|
||||
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.stageDimensions = newStageDimensions;
|
||||
state.boundingBoxCoordinates = { x: 0, y: 0 };
|
||||
state.boundingBoxDimensions = { width: 512, height: 512 };
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -568,11 +582,20 @@ export const canvasSlice = createSlice({
|
||||
newScale
|
||||
);
|
||||
|
||||
const newBoundingBoxDimensions = { width: 512, height: 512 };
|
||||
|
||||
state.stageScale = newScale;
|
||||
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.boundingBoxCoordinates = { x: 0, y: 0 };
|
||||
state.boundingBoxDimensions = { width: 512, height: 512 };
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
}
|
||||
},
|
||||
resetCanvasView: (
|
||||
@ -628,10 +651,19 @@ export const canvasSlice = createSlice({
|
||||
newScale
|
||||
);
|
||||
|
||||
const newBoundingBoxDimensions = { width: 512, height: 512 };
|
||||
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.boundingBoxCoordinates = { x: 0, y: 0 };
|
||||
state.boundingBoxDimensions = { width: 512, height: 512 };
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
}
|
||||
},
|
||||
nextStagingAreaImage: (state) => {
|
||||
@ -708,6 +740,13 @@ export const canvasSlice = createSlice({
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
}
|
||||
},
|
||||
setBoundingBoxScaleMethod: (
|
||||
@ -715,6 +754,13 @@ export const canvasSlice = createSlice({
|
||||
action: PayloadAction<BoundingBoxScale>
|
||||
) => {
|
||||
state.boundingBoxScaleMethod = action.payload;
|
||||
|
||||
if (action.payload === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
state.boundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
},
|
||||
setScaledBoundingBoxDimensions: (
|
||||
state,
|
||||
@ -731,6 +777,9 @@ export const canvasSlice = createSlice({
|
||||
setShouldShowCanvasDebugInfo: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldShowCanvasDebugInfo = action.payload;
|
||||
},
|
||||
setShouldRestrictStrokesToBox: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldRestrictStrokesToBox = action.payload;
|
||||
},
|
||||
setShouldCropToBoundingBoxOnSave: (
|
||||
state,
|
||||
action: PayloadAction<boolean>
|
||||
@ -748,9 +797,7 @@ export const canvasSlice = createSlice({
|
||||
state.tool = 'brush';
|
||||
},
|
||||
setMergedCanvas: (state, action: PayloadAction<CanvasImage>) => {
|
||||
state.pastLayerStates.push({
|
||||
...state.layerState,
|
||||
});
|
||||
state.pastLayerStates.push(_.cloneDeep(state.layerState));
|
||||
|
||||
state.futureLayerStates = [];
|
||||
|
||||
@ -766,10 +813,19 @@ export const canvasSlice = createSlice({
|
||||
state.isMovingStage = false;
|
||||
state.isTransformingBoundingBox = false;
|
||||
},
|
||||
mouseLeftCanvas: (state) => {
|
||||
state.cursorPosition = null;
|
||||
state.isDrawing = false;
|
||||
state.isMouseOverBoundingBox = false;
|
||||
state.isMovingBoundingBox = false;
|
||||
state.isTransformingBoundingBox = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
addEraseRect,
|
||||
addFillRect,
|
||||
addImageToStagingArea,
|
||||
addLine,
|
||||
addPointToCurrentLine,
|
||||
@ -779,6 +835,7 @@ export const {
|
||||
commitStagingAreaImage,
|
||||
discardStagedImages,
|
||||
fitBoundingBoxToStage,
|
||||
mouseLeftCanvas,
|
||||
nextStagingAreaImage,
|
||||
prevStagingAreaImage,
|
||||
redo,
|
||||
@ -827,13 +884,13 @@ export const {
|
||||
setShouldSnapToGrid,
|
||||
setShouldUseInpaintReplace,
|
||||
setStageCoordinates,
|
||||
setStageDimensions,
|
||||
setStageScale,
|
||||
setTool,
|
||||
toggleShouldLockBoundingBox,
|
||||
toggleTool,
|
||||
undo,
|
||||
setScaledBoundingBoxDimensions,
|
||||
setShouldRestrictStrokesToBox,
|
||||
} = canvasSlice.actions;
|
||||
|
||||
export default canvasSlice.reducer;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as InvokeAI from 'app/invokeai';
|
||||
import { Vector2d } from 'konva/lib/types';
|
||||
import { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { RgbaColor } from 'react-colorful';
|
||||
|
||||
export const LAYER_NAMES_DICT = [
|
||||
@ -30,13 +30,6 @@ export type Dimensions = {
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type CanvasAnyLine = {
|
||||
kind: 'line';
|
||||
tool: CanvasDrawingTool;
|
||||
strokeWidth: number;
|
||||
points: number[];
|
||||
};
|
||||
|
||||
export type CanvasImage = {
|
||||
kind: 'image';
|
||||
layer: 'base';
|
||||
@ -47,24 +40,54 @@ export type CanvasImage = {
|
||||
image: InvokeAI.Image;
|
||||
};
|
||||
|
||||
export type CanvasMaskLine = CanvasAnyLine & {
|
||||
export type CanvasMaskLine = {
|
||||
layer: 'mask';
|
||||
kind: 'line';
|
||||
tool: CanvasDrawingTool;
|
||||
strokeWidth: number;
|
||||
points: number[];
|
||||
clip?: IRect;
|
||||
};
|
||||
|
||||
export type CanvasLine = CanvasAnyLine & {
|
||||
export type CanvasBaseLine = {
|
||||
layer: 'base';
|
||||
color?: RgbaColor;
|
||||
kind: 'line';
|
||||
tool: CanvasDrawingTool;
|
||||
strokeWidth: number;
|
||||
points: number[];
|
||||
clip?: IRect;
|
||||
};
|
||||
|
||||
export type CanvasObject = CanvasImage | CanvasLine | CanvasMaskLine;
|
||||
export type CanvasFillRect = {
|
||||
kind: 'fillRect';
|
||||
layer: 'base';
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
color: RgbaColor;
|
||||
};
|
||||
|
||||
export type CanvasEraseRect = {
|
||||
kind: 'eraseRect';
|
||||
layer: 'base';
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type CanvasObject =
|
||||
| CanvasImage
|
||||
| CanvasBaseLine
|
||||
| CanvasMaskLine
|
||||
| CanvasFillRect
|
||||
| CanvasEraseRect;
|
||||
|
||||
export type CanvasLayerState = {
|
||||
objects: CanvasObject[];
|
||||
stagingArea: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
images: CanvasImage[];
|
||||
selectedImageIndex: number;
|
||||
};
|
||||
@ -74,15 +97,21 @@ export type CanvasLayerState = {
|
||||
export const isCanvasMaskLine = (obj: CanvasObject): obj is CanvasMaskLine =>
|
||||
obj.kind === 'line' && obj.layer === 'mask';
|
||||
|
||||
export const isCanvasBaseLine = (obj: CanvasObject): obj is CanvasLine =>
|
||||
export const isCanvasBaseLine = (obj: CanvasObject): obj is CanvasBaseLine =>
|
||||
obj.kind === 'line' && obj.layer === 'base';
|
||||
|
||||
export const isCanvasBaseImage = (obj: CanvasObject): obj is CanvasImage =>
|
||||
obj.kind === 'image' && obj.layer === 'base';
|
||||
|
||||
export const isCanvasFillRect = (obj: CanvasObject): obj is CanvasFillRect =>
|
||||
obj.kind === 'fillRect' && obj.layer === 'base';
|
||||
|
||||
export const isCanvasEraseRect = (obj: CanvasObject): obj is CanvasEraseRect =>
|
||||
obj.kind === 'eraseRect' && obj.layer === 'base';
|
||||
|
||||
export const isCanvasAnyLine = (
|
||||
obj: CanvasObject
|
||||
): obj is CanvasMaskLine | CanvasLine => obj.kind === 'line';
|
||||
): obj is CanvasMaskLine | CanvasBaseLine => obj.kind === 'line';
|
||||
|
||||
export interface CanvasState {
|
||||
boundingBoxCoordinates: Vector2d;
|
||||
@ -119,6 +148,7 @@ export interface CanvasState {
|
||||
shouldDarkenOutsideBoundingBox: boolean;
|
||||
shouldLockBoundingBox: boolean;
|
||||
shouldPreserveMaskedArea: boolean;
|
||||
shouldRestrictStrokesToBox: boolean;
|
||||
shouldShowBoundingBox: boolean;
|
||||
shouldShowBrush: boolean;
|
||||
shouldShowBrushPreview: boolean;
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { Dimensions } from '../store/canvasTypes';
|
||||
|
||||
const getScaledBoundingBoxDimensions = (dimensions: Dimensions) => {
|
||||
const { width, height } = dimensions;
|
||||
|
||||
const scaledDimensions = { width, height };
|
||||
const targetArea = 512 * 512;
|
||||
const aspectRatio = width / height;
|
||||
let currentArea = width * height;
|
||||
let maxDimension = 448;
|
||||
while (currentArea < targetArea) {
|
||||
maxDimension += 64;
|
||||
if (width === height) {
|
||||
scaledDimensions.width = 512;
|
||||
scaledDimensions.height = 512;
|
||||
break;
|
||||
} else {
|
||||
if (aspectRatio > 1) {
|
||||
scaledDimensions.width = maxDimension;
|
||||
scaledDimensions.height = roundToMultiple(
|
||||
maxDimension / aspectRatio,
|
||||
64
|
||||
);
|
||||
} else if (aspectRatio < 1) {
|
||||
scaledDimensions.height = maxDimension;
|
||||
scaledDimensions.width = roundToMultiple(
|
||||
maxDimension * aspectRatio,
|
||||
64
|
||||
);
|
||||
}
|
||||
currentArea = scaledDimensions.width * scaledDimensions.height;
|
||||
}
|
||||
}
|
||||
|
||||
return scaledDimensions;
|
||||
};
|
||||
|
||||
export default getScaledBoundingBoxDimensions;
|
@ -27,6 +27,7 @@ import {
|
||||
FaCode,
|
||||
FaCopy,
|
||||
FaDownload,
|
||||
FaExpand,
|
||||
FaExpandArrowsAlt,
|
||||
FaGrinStars,
|
||||
FaQuoteRight,
|
||||
@ -357,6 +358,10 @@ const CurrentImageButtons = () => {
|
||||
[currentImage, shouldShowImageDetails]
|
||||
);
|
||||
|
||||
const handleLightBox = () => {
|
||||
dispatch(setIsLightBoxOpen(!isLightBoxOpen));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="current-image-options">
|
||||
<ButtonGroup isAttached={true}>
|
||||
@ -396,29 +401,38 @@ const CurrentImageButtons = () => {
|
||||
</IAIButton>
|
||||
</div>
|
||||
</IAIPopover>
|
||||
<IAIIconButton
|
||||
icon={<FaExpand />}
|
||||
tooltip={!isLightBoxOpen ? 'Open In Viewer (Z)' : 'Close Viewer (Z)'}
|
||||
aria-label={
|
||||
!isLightBoxOpen ? 'Open In Viewer (Z)' : 'Close Viewer (Z)'
|
||||
}
|
||||
data-selected={isLightBoxOpen}
|
||||
onClick={handleLightBox}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup isAttached={true}>
|
||||
<IAIIconButton
|
||||
icon={<FaQuoteRight />}
|
||||
tooltip="Use Prompt"
|
||||
aria-label="Use Prompt"
|
||||
tooltip="Use Prompt (P)"
|
||||
aria-label="Use Prompt (P)"
|
||||
isDisabled={!currentImage?.metadata?.image?.prompt}
|
||||
onClick={handleClickUsePrompt}
|
||||
/>
|
||||
|
||||
<IAIIconButton
|
||||
icon={<FaSeedling />}
|
||||
tooltip="Use Seed"
|
||||
aria-label="Use Seed"
|
||||
tooltip="Use Seed (S)"
|
||||
aria-label="Use Seed (S)"
|
||||
isDisabled={!currentImage?.metadata?.image?.seed}
|
||||
onClick={handleClickUseSeed}
|
||||
/>
|
||||
|
||||
<IAIIconButton
|
||||
icon={<FaAsterisk />}
|
||||
tooltip="Use All"
|
||||
aria-label="Use All"
|
||||
tooltip="Use All (A)"
|
||||
aria-label="Use All (A)"
|
||||
isDisabled={
|
||||
!['txt2img', 'img2img'].includes(
|
||||
currentImage?.metadata?.image?.type
|
||||
@ -474,13 +488,15 @@ const CurrentImageButtons = () => {
|
||||
</IAIPopover>
|
||||
</ButtonGroup>
|
||||
|
||||
<IAIIconButton
|
||||
icon={<FaCode />}
|
||||
tooltip="Details"
|
||||
aria-label="Details"
|
||||
data-selected={shouldShowImageDetails}
|
||||
onClick={handleClickShowImageDetails}
|
||||
/>
|
||||
<ButtonGroup isAttached={true}>
|
||||
<IAIIconButton
|
||||
icon={<FaCode />}
|
||||
tooltip="Info (I)"
|
||||
aria-label="Info (I)"
|
||||
data-selected={shouldShowImageDetails}
|
||||
onClick={handleClickShowImageDetails}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
<DeleteImageModal image={currentImage}>
|
||||
<IAIIconButton
|
||||
|
@ -24,7 +24,6 @@
|
||||
max-height: 100%;
|
||||
height: auto;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,10 +10,7 @@ import {
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
OptionsState,
|
||||
setIsLightBoxOpen,
|
||||
} from 'features/options/store/optionsSlice';
|
||||
import { OptionsState } from 'features/options/store/optionsSlice';
|
||||
import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer';
|
||||
|
||||
export const imagesSelector = createSelector(
|
||||
@ -82,10 +79,6 @@ export default function CurrentImagePreview() {
|
||||
dispatch(selectNextImage());
|
||||
};
|
||||
|
||||
const handleLightBox = () => {
|
||||
dispatch(setIsLightBoxOpen(true));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'current-image-preview'}>
|
||||
{imageToDisplay && (
|
||||
@ -93,7 +86,6 @@ export default function CurrentImagePreview() {
|
||||
src={imageToDisplay.url}
|
||||
width={imageToDisplay.width}
|
||||
height={imageToDisplay.height}
|
||||
onClick={handleLightBox}
|
||||
style={{
|
||||
imageRendering: isIntermediate ? 'pixelated' : 'initial',
|
||||
}}
|
||||
|
@ -116,7 +116,8 @@ const DeleteImageModal = forwardRef(
|
||||
<AlertDialogBody>
|
||||
<Flex direction={'column'} gap={5}>
|
||||
<Text>
|
||||
Are you sure? You can't undo this action afterwards.
|
||||
Are you sure? Deleted images will be sent to the Bin. You
|
||||
can restore from there if you wish to.
|
||||
</Text>
|
||||
<FormControl>
|
||||
<Flex alignItems={'center'}>
|
||||
|
@ -159,6 +159,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
};
|
||||
|
||||
const handleLightBox = () => {
|
||||
dispatch(setIsLightBoxOpen(true));
|
||||
dispatch(setCurrentImage(image));
|
||||
};
|
||||
|
||||
return (
|
||||
<ContextMenu.Root
|
||||
onOpenChange={(open: boolean) => {
|
||||
@ -220,6 +225,9 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
e.detail.originalEvent.preventDefault();
|
||||
}}
|
||||
>
|
||||
<ContextMenu.Item onClickCapture={handleLightBox}>
|
||||
Open In Viewer
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item
|
||||
onClickCapture={handleUsePrompt}
|
||||
disabled={image?.metadata?.image?.prompt === undefined}
|
||||
|
@ -166,7 +166,7 @@ export default function ImageGallery() {
|
||||
dispatch(selectPrevImage());
|
||||
},
|
||||
{
|
||||
enabled: !isStaging,
|
||||
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
|
||||
},
|
||||
[isStaging]
|
||||
);
|
||||
@ -177,7 +177,7 @@ export default function ImageGallery() {
|
||||
dispatch(selectNextImage());
|
||||
},
|
||||
{
|
||||
enabled: !isStaging,
|
||||
enabled: !isStaging || activeTabName !== 'unifiedCanvas',
|
||||
},
|
||||
[isStaging]
|
||||
);
|
||||
|
@ -10,6 +10,7 @@
|
||||
top: 0;
|
||||
background-color: var(--background-color-secondary);
|
||||
z-index: 30;
|
||||
animation: popIn 0.3s ease-in;
|
||||
|
||||
.image-gallery-wrapper {
|
||||
max-height: 100% !important;
|
||||
@ -75,3 +76,14 @@
|
||||
row-gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
filter: blur(100);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
filter: blur(0);
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,12 @@ export default function ReactPanZoom({
|
||||
};
|
||||
|
||||
return (
|
||||
<TransformWrapper centerOnInit minScale={0.1}>
|
||||
<TransformWrapper
|
||||
centerOnInit
|
||||
minScale={0.1}
|
||||
initialPositionX={50}
|
||||
initialPositionY={50}
|
||||
>
|
||||
{({ zoomIn, zoomOut, resetTransform, centerView }) => (
|
||||
<>
|
||||
<div className="lightbox-image-options">
|
||||
@ -103,8 +108,12 @@ export default function ReactPanZoom({
|
||||
fontSize={20}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TransformComponent wrapperStyle={{ width: '100%', height: '100%' }}>
|
||||
<TransformComponent
|
||||
wrapperStyle={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
transform: `rotate(${rotation * 90}deg) scaleX(${
|
||||
@ -116,7 +125,7 @@ export default function ReactPanZoom({
|
||||
alt={alt}
|
||||
ref={ref}
|
||||
className={styleClass ? styleClass : ''}
|
||||
onLoad={() => centerView()}
|
||||
onLoad={() => centerView(1, 0, 'easeOut')}
|
||||
/>
|
||||
</TransformComponent>
|
||||
</>
|
||||
|
@ -5,7 +5,6 @@ import IAISelect from 'common/components/IAISelect';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
setBoundingBoxDimensions,
|
||||
setBoundingBoxScaleMethod,
|
||||
setScaledBoundingBoxDimensions,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
@ -31,13 +30,11 @@ const selector = createSelector(
|
||||
const { infill_methods: availableInfillMethods } = system;
|
||||
|
||||
const {
|
||||
boundingBoxDimensions,
|
||||
boundingBoxScaleMethod: boundingBoxScale,
|
||||
scaledBoundingBoxDimensions,
|
||||
} = canvas;
|
||||
|
||||
return {
|
||||
boundingBoxDimensions,
|
||||
boundingBoxScale,
|
||||
scaledBoundingBoxDimensions,
|
||||
tileSize,
|
||||
@ -58,7 +55,6 @@ const InfillAndScalingOptions = () => {
|
||||
const {
|
||||
tileSize,
|
||||
infillMethod,
|
||||
boundingBoxDimensions,
|
||||
availableInfillMethods,
|
||||
boundingBoxScale,
|
||||
isManual,
|
||||
@ -105,7 +101,6 @@ const InfillAndScalingOptions = () => {
|
||||
e: ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
dispatch(setBoundingBoxScaleMethod(e.target.value as BoundingBoxScale));
|
||||
dispatch(setBoundingBoxDimensions(boundingBoxDimensions));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -72,8 +72,8 @@ const SeamCorrectionOptions = () => {
|
||||
<IAISlider
|
||||
sliderMarkRightOffset={-2}
|
||||
label={'Seam Strength'}
|
||||
min={0}
|
||||
max={1}
|
||||
min={0.01}
|
||||
max={0.99}
|
||||
step={0.01}
|
||||
value={seamStrength}
|
||||
onChange={(v) => {
|
||||
|
@ -19,7 +19,7 @@ export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
|
||||
const handleChangeStrength = (v: number) => dispatch(setImg2imgStrength(v));
|
||||
|
||||
const handleImg2ImgStrengthReset = () => {
|
||||
dispatch(setImg2imgStrength(0.5));
|
||||
dispatch(setImg2imgStrength(0.75));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -38,16 +38,5 @@ export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
|
||||
inputWidth={'5.5rem'}
|
||||
handleReset={handleImg2ImgStrengthReset}
|
||||
/>
|
||||
// <IAINumberInput
|
||||
// label={label}
|
||||
// step={0.01}
|
||||
// min={0.01}
|
||||
// max={0.99}
|
||||
// onChange={handleChangeStrength}
|
||||
// value={img2imgStrength}
|
||||
// width="100%"
|
||||
// isInteger={false}
|
||||
// styleClass={styleClass}
|
||||
// />
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { emptyTempFolder } from 'app/socketio/actions';
|
||||
import { useAppDispatch } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store';
|
||||
import IAIAlertDialog from 'common/components/IAIAlertDialog';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
clearCanvasHistory,
|
||||
resetCanvas,
|
||||
@ -9,6 +10,7 @@ import {
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
|
||||
const EmptyTempFolderButtonModal = () => {
|
||||
const isStaging = useAppSelector(isStagingSelector);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const acceptCallback = () => {
|
||||
@ -23,7 +25,7 @@ const EmptyTempFolderButtonModal = () => {
|
||||
acceptCallback={acceptCallback}
|
||||
acceptButtonText={'Empty Folder'}
|
||||
triggerComponent={
|
||||
<IAIButton leftIcon={<FaTrash />} size={'sm'}>
|
||||
<IAIButton leftIcon={<FaTrash />} size={'sm'} isDisabled={isStaging}>
|
||||
Empty Temp Image Folder
|
||||
</IAIButton>
|
||||
}
|
||||
|
@ -173,6 +173,16 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
|
||||
desc: 'Allows canvas navigation',
|
||||
hotkey: 'V',
|
||||
},
|
||||
{
|
||||
title: 'Fill Bounding Box',
|
||||
desc: 'Fills the bounding box with brush color',
|
||||
hotkey: 'Shift + F',
|
||||
},
|
||||
{
|
||||
title: 'Erase Bounding Box',
|
||||
desc: 'Erases the bounding box area',
|
||||
hotkey: 'Delete / Backspace',
|
||||
},
|
||||
{
|
||||
title: 'Select Color Picker',
|
||||
desc: 'Selects the canvas color picker',
|
||||
|
Loading…
Reference in New Issue
Block a user