mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
chore(ui): format
Lots of changed bc the line length is now 120. May as well do it now.
This commit is contained in:
@ -1,8 +1,4 @@
|
||||
import {
|
||||
Button,
|
||||
ConfirmationAlertDialog,
|
||||
useDisclosure,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { Button, ConfirmationAlertDialog, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
||||
@ -15,19 +11,11 @@ const ClearCanvasHistoryButtonModal = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const acceptCallback = useCallback(
|
||||
() => dispatch(clearCanvasHistory()),
|
||||
[dispatch]
|
||||
);
|
||||
const acceptCallback = useCallback(() => dispatch(clearCanvasHistory()), [dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={onOpen}
|
||||
size="sm"
|
||||
leftIcon={<PiTrashSimpleFill />}
|
||||
isDisabled={isStaging}
|
||||
>
|
||||
<Button onClick={onOpen} size="sm" leftIcon={<PiTrashSimpleFill />} isDisabled={isStaging}>
|
||||
{t('unifiedCanvas.clearCanvasHistory')}
|
||||
</Button>
|
||||
<ConfirmationAlertDialog
|
||||
|
@ -19,10 +19,7 @@ import {
|
||||
$tool,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
canvasResized,
|
||||
selectCanvasSlice,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { canvasResized, selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import type Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import type { Vector2d } from 'konva/lib/types';
|
||||
@ -55,18 +52,12 @@ const ChakraStage = chakra(Stage, {
|
||||
const IAICanvas = () => {
|
||||
const isStaging = useAppSelector(isStagingSelector);
|
||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||
const shouldShowBoundingBox = useAppSelector(
|
||||
(s) => s.canvas.shouldShowBoundingBox
|
||||
);
|
||||
const shouldShowBoundingBox = useAppSelector((s) => s.canvas.shouldShowBoundingBox);
|
||||
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
|
||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||
const shouldShowIntermediates = useAppSelector(
|
||||
(s) => s.canvas.shouldShowIntermediates
|
||||
);
|
||||
const shouldShowIntermediates = useAppSelector((s) => s.canvas.shouldShowIntermediates);
|
||||
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
|
||||
const shouldRestrictStrokesToBox = useAppSelector(
|
||||
(s) => s.canvas.shouldRestrictStrokesToBox
|
||||
);
|
||||
const shouldRestrictStrokesToBox = useAppSelector((s) => s.canvas.shouldRestrictStrokesToBox);
|
||||
const { stageCoordinates, stageDimensions } = useAppSelector(selector);
|
||||
const dispatch = useAppDispatch();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@ -95,22 +86,12 @@ const IAICanvas = () => {
|
||||
return 'default';
|
||||
}
|
||||
return 'none';
|
||||
}, [
|
||||
isMouseOverBoundingBox,
|
||||
isMovingStage,
|
||||
isStaging,
|
||||
isTransformingBoundingBox,
|
||||
shouldRestrictStrokesToBox,
|
||||
tool,
|
||||
]);
|
||||
}, [isMouseOverBoundingBox, isMovingStage, isStaging, isTransformingBoundingBox, shouldRestrictStrokesToBox, tool]);
|
||||
|
||||
const canvasBaseLayerRefCallback = useCallback(
|
||||
(layerElement: Konva.Layer) => {
|
||||
$canvasBaseLayer.set(layerElement);
|
||||
canvasBaseLayerRef.current = layerElement;
|
||||
},
|
||||
[]
|
||||
);
|
||||
const canvasBaseLayerRefCallback = useCallback((layerElement: Konva.Layer) => {
|
||||
$canvasBaseLayer.set(layerElement);
|
||||
canvasBaseLayerRef.current = layerElement;
|
||||
}, []);
|
||||
|
||||
const lastCursorPositionRef = useRef<Vector2d>({ x: 0, y: 0 });
|
||||
|
||||
@ -120,18 +101,10 @@ const IAICanvas = () => {
|
||||
const handleWheel = useCanvasWheel(stageRef);
|
||||
const handleMouseDown = useCanvasMouseDown(stageRef);
|
||||
const handleMouseUp = useCanvasMouseUp(stageRef, didMouseMoveRef);
|
||||
const handleMouseMove = useCanvasMouseMove(
|
||||
stageRef,
|
||||
didMouseMoveRef,
|
||||
lastCursorPositionRef
|
||||
);
|
||||
const { handleDragStart, handleDragMove, handleDragEnd } =
|
||||
useCanvasDragMove();
|
||||
const handleMouseMove = useCanvasMouseMove(stageRef, didMouseMoveRef, lastCursorPositionRef);
|
||||
const { handleDragStart, handleDragMove, handleDragEnd } = useCanvasDragMove();
|
||||
const handleMouseOut = useCanvasMouseOut();
|
||||
const handleContextMenu = useCallback(
|
||||
(e: KonvaEventObject<MouseEvent>) => e.evt.preventDefault(),
|
||||
[]
|
||||
);
|
||||
const handleContextMenu = useCallback((e: KonvaEventObject<MouseEvent>) => e.evt.preventDefault(), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) {
|
||||
@ -169,14 +142,7 @@ const IAICanvas = () => {
|
||||
const scale = useMemo(() => ({ x: stageScale, y: stageScale }), [stageScale]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
id="canvas-container"
|
||||
ref={containerRef}
|
||||
position="relative"
|
||||
height="100%"
|
||||
width="100%"
|
||||
borderRadius="base"
|
||||
>
|
||||
<Flex id="canvas-container" ref={containerRef} position="relative" height="100%" width="100%" borderRadius="base">
|
||||
<Box position="absolute">
|
||||
<ChakraStage
|
||||
tabIndex={-1}
|
||||
@ -205,19 +171,10 @@ const IAICanvas = () => {
|
||||
<IAICanvasGrid />
|
||||
</Layer>
|
||||
|
||||
<Layer
|
||||
id="base"
|
||||
ref={canvasBaseLayerRefCallback}
|
||||
listening={false}
|
||||
imageSmoothingEnabled={shouldAntialias}
|
||||
>
|
||||
<Layer id="base" ref={canvasBaseLayerRefCallback} listening={false} imageSmoothingEnabled={shouldAntialias}>
|
||||
<IAICanvasObjectRenderer />
|
||||
</Layer>
|
||||
<Layer
|
||||
id="mask"
|
||||
visible={isMaskEnabled && !isStaging}
|
||||
listening={false}
|
||||
>
|
||||
<Layer id="mask" visible={isMaskEnabled && !isStaging} listening={false}>
|
||||
<IAICanvasMaskLines visible={true} listening={false} />
|
||||
<IAICanvasMaskCompositer listening={false} />
|
||||
</Layer>
|
||||
@ -225,17 +182,10 @@ const IAICanvas = () => {
|
||||
<IAICanvasBoundingBoxOverlay />
|
||||
</Layer>
|
||||
<Layer id="preview" imageSmoothingEnabled={shouldAntialias}>
|
||||
{!isStaging && (
|
||||
<IAICanvasToolPreview
|
||||
visible={tool !== 'move'}
|
||||
listening={false}
|
||||
/>
|
||||
)}
|
||||
{!isStaging && <IAICanvasToolPreview visible={tool !== 'move'} listening={false} />}
|
||||
<IAICanvasStagingArea listening={false} visible={isStaging} />
|
||||
{shouldShowIntermediates && <IAICanvasIntermediateImage />}
|
||||
<IAICanvasBoundingBox
|
||||
visible={shouldShowBoundingBox && !isStaging}
|
||||
/>
|
||||
<IAICanvasBoundingBox visible={shouldShowBoundingBox && !isStaging} />
|
||||
</Layer>
|
||||
</ChakraStage>
|
||||
</Box>
|
||||
|
@ -5,12 +5,7 @@ import { memo } from 'react';
|
||||
import { Group, Rect } from 'react-konva';
|
||||
|
||||
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
stageDimensions,
|
||||
stageCoordinates,
|
||||
} = canvas;
|
||||
const { boundingBoxCoordinates, boundingBoxDimensions, stageDimensions, stageCoordinates } = canvas;
|
||||
|
||||
return {
|
||||
boundingBoxCoordinates,
|
||||
@ -21,15 +16,8 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
});
|
||||
|
||||
const IAICanvasBoundingBoxOverlay = () => {
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
stageCoordinates,
|
||||
stageDimensions,
|
||||
} = useAppSelector(selector);
|
||||
const shouldDarkenOutsideBoundingBox = useAppSelector(
|
||||
(s) => s.canvas.shouldDarkenOutsideBoundingBox
|
||||
);
|
||||
const { boundingBoxCoordinates, boundingBoxDimensions, stageCoordinates, stageDimensions } = useAppSelector(selector);
|
||||
const shouldDarkenOutsideBoundingBox = useAppSelector((s) => s.canvas.shouldDarkenOutsideBoundingBox);
|
||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||
|
||||
return (
|
||||
|
@ -13,13 +13,8 @@ type IAICanvasImageProps = {
|
||||
};
|
||||
const IAICanvasImage = (props: IAICanvasImageProps) => {
|
||||
const { x, y, imageName } = props.canvasImage;
|
||||
const { currentData: imageDTO, isError } = useGetImageDTOQuery(
|
||||
imageName ?? skipToken
|
||||
);
|
||||
const [image, status] = useImage(
|
||||
imageDTO?.image_url ?? '',
|
||||
$authToken.get() ? 'use-credentials' : 'anonymous'
|
||||
);
|
||||
const { currentData: imageDTO, isError } = useGetImageDTOQuery(imageName ?? skipToken);
|
||||
const [image, status] = useImage(imageDTO?.image_url ?? '', $authToken.get() ? 'use-credentials' : 'anonymous');
|
||||
|
||||
if (isError || status === 'failed') {
|
||||
return <IAICanvasImageErrorFallback canvasImage={props.canvasImage} />;
|
||||
|
@ -7,9 +7,7 @@ import { Group, Rect, Text } from 'react-konva';
|
||||
type IAICanvasImageErrorFallbackProps = {
|
||||
canvasImage: CanvasImage;
|
||||
};
|
||||
const IAICanvasImageErrorFallback = ({
|
||||
canvasImage,
|
||||
}: IAICanvasImageErrorFallbackProps) => {
|
||||
const IAICanvasImageErrorFallback = ({ canvasImage }: IAICanvasImageErrorFallbackProps) => {
|
||||
const [rectFill, textFill] = useToken('colors', ['base.500', 'base.900']);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
|
@ -5,26 +5,20 @@ import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { Image as KonvaImage } from 'react-konva';
|
||||
|
||||
const progressImageSelector = createMemoizedSelector(
|
||||
[selectSystemSlice, selectCanvasSlice],
|
||||
(system, canvas) => {
|
||||
const { denoiseProgress } = system;
|
||||
const { batchIds } = canvas;
|
||||
const progressImageSelector = createMemoizedSelector([selectSystemSlice, selectCanvasSlice], (system, canvas) => {
|
||||
const { denoiseProgress } = system;
|
||||
const { batchIds } = canvas;
|
||||
|
||||
return {
|
||||
progressImage:
|
||||
denoiseProgress && batchIds.includes(denoiseProgress.batch_id)
|
||||
? denoiseProgress.progress_image
|
||||
: undefined,
|
||||
boundingBox: canvas.layerState.stagingArea.boundingBox,
|
||||
};
|
||||
}
|
||||
);
|
||||
return {
|
||||
progressImage:
|
||||
denoiseProgress && batchIds.includes(denoiseProgress.batch_id) ? denoiseProgress.progress_image : undefined,
|
||||
boundingBox: canvas.layerState.stagingArea.boundingBox,
|
||||
};
|
||||
});
|
||||
|
||||
const IAICanvasIntermediateImage = () => {
|
||||
const { progressImage, boundingBox } = useAppSelector(progressImageSelector);
|
||||
const [loadedImageElement, setLoadedImageElement] =
|
||||
useState<HTMLImageElement | null>(null);
|
||||
const [loadedImageElement, setLoadedImageElement] = useState<HTMLImageElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!progressImage) {
|
||||
|
@ -9,30 +9,22 @@ import { isNumber } from 'lodash-es';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Rect } from 'react-konva';
|
||||
|
||||
export const canvasMaskCompositerSelector = createMemoizedSelector(
|
||||
selectCanvasSlice,
|
||||
(canvas) => {
|
||||
return {
|
||||
stageCoordinates: canvas.stageCoordinates,
|
||||
stageDimensions: canvas.stageDimensions,
|
||||
};
|
||||
}
|
||||
);
|
||||
export const canvasMaskCompositerSelector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
return {
|
||||
stageCoordinates: canvas.stageCoordinates,
|
||||
stageDimensions: canvas.stageDimensions,
|
||||
};
|
||||
});
|
||||
|
||||
type IAICanvasMaskCompositerProps = RectConfig;
|
||||
|
||||
const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
||||
const { ...rest } = props;
|
||||
|
||||
const { stageCoordinates, stageDimensions } = useAppSelector(
|
||||
canvasMaskCompositerSelector
|
||||
);
|
||||
const { stageCoordinates, stageDimensions } = useAppSelector(canvasMaskCompositerSelector);
|
||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||
const maskColorString = useAppSelector((s) =>
|
||||
rgbaColorToString(s.canvas.maskColor)
|
||||
);
|
||||
const [fillPatternImage, setFillPatternImage] =
|
||||
useState<HTMLImageElement | null>(null);
|
||||
const maskColorString = useAppSelector((s) => rgbaColorToString(s.canvas.maskColor));
|
||||
const [fillPatternImage, setFillPatternImage] = useState<HTMLImageElement | null>(null);
|
||||
|
||||
const [offset, setOffset] = useState<number>(0);
|
||||
|
||||
@ -66,10 +58,7 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const fillPatternScale = useMemo(
|
||||
() => ({ x: 1 / stageScale, y: 1 / stageScale }),
|
||||
[stageScale]
|
||||
);
|
||||
const fillPatternScale = useMemo(() => ({ x: 1 / stageScale, y: 1 / stageScale }), [stageScale]);
|
||||
|
||||
if (
|
||||
!fillPatternImage ||
|
||||
|
@ -27,9 +27,7 @@ const IAICanvasLines = (props: InpaintingCanvasLinesProps) => {
|
||||
lineJoin="round"
|
||||
shadowForStrokeEnabled={false}
|
||||
listening={false}
|
||||
globalCompositeOperation={
|
||||
line.tool === 'brush' ? 'source-over' : 'destination-out'
|
||||
}
|
||||
globalCompositeOperation={line.tool === 'brush' ? 'source-over' : 'destination-out'}
|
||||
/>
|
||||
))}
|
||||
</Group>
|
||||
|
@ -31,9 +31,7 @@ const IAICanvasObjectRenderer = () => {
|
||||
lineJoin="round"
|
||||
shadowForStrokeEnabled={false}
|
||||
listening={false}
|
||||
globalCompositeOperation={
|
||||
obj.tool === 'brush' ? 'source-over' : 'destination-out'
|
||||
}
|
||||
globalCompositeOperation={obj.tool === 'brush' ? 'source-over' : 'destination-out'}
|
||||
/>
|
||||
);
|
||||
if (obj.clip) {
|
||||
|
@ -22,9 +22,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
|
||||
return {
|
||||
currentStagingAreaImage:
|
||||
images.length > 0 && selectedImageIndex !== undefined
|
||||
? images[selectedImageIndex]
|
||||
: undefined,
|
||||
images.length > 0 && selectedImageIndex !== undefined ? images[selectedImageIndex] : undefined,
|
||||
isOnFirstImage: selectedImageIndex === 0,
|
||||
isOnLastImage: selectedImageIndex === images.length - 1,
|
||||
shouldShowStagingImage,
|
||||
@ -39,21 +37,12 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
type Props = GroupConfig;
|
||||
|
||||
const IAICanvasStagingArea = (props: Props) => {
|
||||
const {
|
||||
currentStagingAreaImage,
|
||||
shouldShowStagingImage,
|
||||
shouldShowStagingOutline,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} = useAppSelector(selector);
|
||||
const { currentStagingAreaImage, shouldShowStagingImage, shouldShowStagingOutline, x, y, width, height } =
|
||||
useAppSelector(selector);
|
||||
|
||||
return (
|
||||
<Group {...props}>
|
||||
{shouldShowStagingImage && currentStagingAreaImage && (
|
||||
<IAICanvasImage canvasImage={currentStagingAreaImage} />
|
||||
)}
|
||||
{shouldShowStagingImage && currentStagingAreaImage && <IAICanvasImage canvasImage={currentStagingAreaImage} />}
|
||||
{shouldShowStagingOutline && (
|
||||
<Group listening={false}>
|
||||
<Rect
|
||||
|
@ -38,8 +38,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
return {
|
||||
currentIndex: selectedImageIndex,
|
||||
total: images.length,
|
||||
currentStagingAreaImage:
|
||||
images.length > 0 ? images[selectedImageIndex] : undefined,
|
||||
currentStagingAreaImage: images.length > 0 ? images[selectedImageIndex] : undefined,
|
||||
shouldShowStagingImage,
|
||||
shouldShowStagingOutline,
|
||||
};
|
||||
@ -47,12 +46,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
|
||||
const IAICanvasStagingAreaToolbar = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
currentStagingAreaImage,
|
||||
shouldShowStagingImage,
|
||||
currentIndex,
|
||||
total,
|
||||
} = useAppSelector(selector);
|
||||
const { currentStagingAreaImage, shouldShowStagingImage, currentIndex, total } = useAppSelector(selector);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -64,20 +58,11 @@ const IAICanvasStagingAreaToolbar = () => {
|
||||
dispatch(setShouldShowStagingOutline(false));
|
||||
}, [dispatch]);
|
||||
|
||||
const handlePrevImage = useCallback(
|
||||
() => dispatch(prevStagingAreaImage()),
|
||||
[dispatch]
|
||||
);
|
||||
const handlePrevImage = useCallback(() => dispatch(prevStagingAreaImage()), [dispatch]);
|
||||
|
||||
const handleNextImage = useCallback(
|
||||
() => dispatch(nextStagingAreaImage()),
|
||||
[dispatch]
|
||||
);
|
||||
const handleNextImage = useCallback(() => dispatch(nextStagingAreaImage()), [dispatch]);
|
||||
|
||||
const handleAccept = useCallback(
|
||||
() => dispatch(commitStagingAreaImage()),
|
||||
[dispatch]
|
||||
);
|
||||
const handleAccept = useCallback(() => dispatch(commitStagingAreaImage()), [dispatch]);
|
||||
|
||||
useHotkeys(['left'], handlePrevImage, {
|
||||
enabled: () => true,
|
||||
@ -94,9 +79,7 @@ const IAICanvasStagingAreaToolbar = () => {
|
||||
preventDefault: true,
|
||||
});
|
||||
|
||||
const { data: imageDTO } = useGetImageDTOQuery(
|
||||
currentStagingAreaImage?.imageName ?? skipToken
|
||||
);
|
||||
const { data: imageDTO } = useGetImageDTOQuery(currentStagingAreaImage?.imageName ?? skipToken);
|
||||
|
||||
const handleToggleShouldShowStagingImage = useCallback(() => {
|
||||
dispatch(setShouldShowStagingImage(!shouldShowStagingImage));
|
||||
@ -166,16 +149,8 @@ const IAICanvasStagingAreaToolbar = () => {
|
||||
colorScheme="invokeBlue"
|
||||
/>
|
||||
<IconButton
|
||||
tooltip={
|
||||
shouldShowStagingImage
|
||||
? t('unifiedCanvas.showResultsOn')
|
||||
: t('unifiedCanvas.showResultsOff')
|
||||
}
|
||||
aria-label={
|
||||
shouldShowStagingImage
|
||||
? t('unifiedCanvas.showResultsOn')
|
||||
: t('unifiedCanvas.showResultsOff')
|
||||
}
|
||||
tooltip={shouldShowStagingImage ? t('unifiedCanvas.showResultsOn') : t('unifiedCanvas.showResultsOff')}
|
||||
aria-label={shouldShowStagingImage ? t('unifiedCanvas.showResultsOn') : t('unifiedCanvas.showResultsOff')}
|
||||
data-alert={!shouldShowStagingImage}
|
||||
icon={shouldShowStagingImage ? <PiEyeBold /> : <PiEyeSlashBold />}
|
||||
onClick={handleToggleShouldShowStagingImage}
|
||||
|
@ -16,10 +16,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
stageDimensions: { width: stageWidth, height: stageHeight },
|
||||
stageCoordinates: { x: stageX, y: stageY },
|
||||
boundingBoxDimensions: { width: boxWidth, height: boxHeight },
|
||||
scaledBoundingBoxDimensions: {
|
||||
width: scaledBoxWidth,
|
||||
height: scaledBoxHeight,
|
||||
},
|
||||
scaledBoundingBoxDimensions: { width: scaledBoxWidth, height: scaledBoxHeight },
|
||||
boundingBoxCoordinates: { x: boxX, y: boxY },
|
||||
stageScale,
|
||||
shouldShowCanvasDebugInfo,
|
||||
@ -31,10 +28,8 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
let boundingBoxColor = 'inherit';
|
||||
|
||||
if (
|
||||
(boundingBoxScaleMethod === 'none' &&
|
||||
(boxWidth < 512 || boxHeight < 512)) ||
|
||||
(boundingBoxScaleMethod === 'manual' &&
|
||||
scaledBoxWidth * scaledBoxHeight < 512 * 512)
|
||||
(boundingBoxScaleMethod === 'none' && (boxWidth < 512 || boxHeight < 512)) ||
|
||||
(boundingBoxScaleMethod === 'manual' && scaledBoxWidth * scaledBoxHeight < 512 * 512)
|
||||
) {
|
||||
boundingBoxColor = warningColor;
|
||||
}
|
||||
@ -45,14 +40,10 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
activeLayerColor,
|
||||
layer,
|
||||
boundingBoxColor,
|
||||
boundingBoxCoordinatesString: `(${roundToHundreth(boxX)}, ${roundToHundreth(
|
||||
boxY
|
||||
)})`,
|
||||
boundingBoxCoordinatesString: `(${roundToHundreth(boxX)}, ${roundToHundreth(boxY)})`,
|
||||
boundingBoxDimensionsString: `${boxWidth}×${boxHeight}`,
|
||||
scaledBoundingBoxDimensionsString: `${scaledBoxWidth}×${scaledBoxHeight}`,
|
||||
canvasCoordinatesString: `${roundToHundreth(stageX)}×${roundToHundreth(
|
||||
stageY
|
||||
)}`,
|
||||
canvasCoordinatesString: `${roundToHundreth(stageX)}×${roundToHundreth(stageY)}`,
|
||||
canvasDimensionsString: `${stageWidth}×${stageHeight}`,
|
||||
canvasScaleString: Math.round(stageScale * 100),
|
||||
shouldShowCanvasDebugInfo,
|
||||
@ -99,9 +90,7 @@ const IAICanvasStatusText = () => {
|
||||
bg="base.800"
|
||||
>
|
||||
<GenerationModeStatusText />
|
||||
<Box color={activeLayerColor}>{`${t('unifiedCanvas.activeLayer')}: ${t(
|
||||
`unifiedCanvas.${layer}`
|
||||
)}`}</Box>
|
||||
<Box color={activeLayerColor}>{`${t('unifiedCanvas.activeLayer')}: ${t(`unifiedCanvas.${layer}`)}`}</Box>
|
||||
<Box>{`${t('unifiedCanvas.canvasScale')}: ${canvasScaleString}%`}</Box>
|
||||
{shouldPreserveMaskedArea && (
|
||||
<Box color={warningColor}>
|
||||
@ -109,9 +98,7 @@ const IAICanvasStatusText = () => {
|
||||
</Box>
|
||||
)}
|
||||
{shouldShowBoundingBox && (
|
||||
<Box color={boundingBoxColor}>{`${t(
|
||||
'unifiedCanvas.boundingBox'
|
||||
)}: ${boundingBoxDimensionsString}`}</Box>
|
||||
<Box color={boundingBoxColor}>{`${t('unifiedCanvas.boundingBox')}: ${boundingBoxDimensionsString}`}</Box>
|
||||
)}
|
||||
{shouldShowScaledBoundingBox && (
|
||||
<Box color={boundingBoxColor}>{`${t(
|
||||
@ -120,15 +107,9 @@ const IAICanvasStatusText = () => {
|
||||
)}
|
||||
{shouldShowCanvasDebugInfo && (
|
||||
<>
|
||||
<Box>{`${t(
|
||||
'unifiedCanvas.boundingBoxPosition'
|
||||
)}: ${boundingBoxCoordinatesString}`}</Box>
|
||||
<Box>{`${t(
|
||||
'unifiedCanvas.canvasDimensions'
|
||||
)}: ${canvasDimensionsString}`}</Box>
|
||||
<Box>{`${t(
|
||||
'unifiedCanvas.canvasPosition'
|
||||
)}: ${canvasCoordinatesString}`}</Box>
|
||||
<Box>{`${t('unifiedCanvas.boundingBoxPosition')}: ${boundingBoxCoordinatesString}`}</Box>
|
||||
<Box>{`${t('unifiedCanvas.canvasDimensions')}: ${canvasDimensionsString}`}</Box>
|
||||
<Box>{`${t('unifiedCanvas.canvasPosition')}: ${canvasCoordinatesString}`}</Box>
|
||||
<IAICanvasStatusTextCursorPos />
|
||||
</>
|
||||
)}
|
||||
|
@ -14,11 +14,7 @@ const IAICanvasStatusTextCursorPos = () => {
|
||||
return `(${roundToHundreth(x)}, ${roundToHundreth(y)})`;
|
||||
}, [cursorPosition?.x, cursorPosition?.y]);
|
||||
|
||||
return (
|
||||
<Box>{`${t(
|
||||
'unifiedCanvas.cursorPosition'
|
||||
)}: ${cursorCoordinatesString}`}</Box>
|
||||
);
|
||||
return <Box>{`${t('unifiedCanvas.cursorPosition')}: ${cursorCoordinatesString}`}</Box>;
|
||||
};
|
||||
|
||||
export default memo(IAICanvasStatusTextCursorPos);
|
||||
|
@ -9,104 +9,84 @@ import {
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
import {
|
||||
COLOR_PICKER_SIZE,
|
||||
COLOR_PICKER_STROKE_RADIUS,
|
||||
} from 'features/canvas/util/constants';
|
||||
import { COLOR_PICKER_SIZE, COLOR_PICKER_STROKE_RADIUS } from 'features/canvas/util/constants';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { Circle, Group } from 'react-konva';
|
||||
|
||||
const canvasBrushPreviewSelector = createMemoizedSelector(
|
||||
selectCanvasSlice,
|
||||
(canvas) => {
|
||||
const {
|
||||
stageDimensions,
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
shouldRestrictStrokesToBox,
|
||||
} = canvas;
|
||||
const canvasBrushPreviewSelector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||
const { stageDimensions, boundingBoxCoordinates, boundingBoxDimensions, shouldRestrictStrokesToBox } = canvas;
|
||||
|
||||
const clip = shouldRestrictStrokesToBox
|
||||
? {
|
||||
clipX: boundingBoxCoordinates.x,
|
||||
clipY: boundingBoxCoordinates.y,
|
||||
clipWidth: boundingBoxDimensions.width,
|
||||
clipHeight: boundingBoxDimensions.height,
|
||||
}
|
||||
: {};
|
||||
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;
|
||||
// // 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 {
|
||||
clip,
|
||||
stageDimensions,
|
||||
};
|
||||
}
|
||||
);
|
||||
return {
|
||||
clip,
|
||||
stageDimensions,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Draws a black circle around the canvas brush preview.
|
||||
*/
|
||||
const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
const radius = useAppSelector((s) => s.canvas.brushSize / 2);
|
||||
const maskColorString = useAppSelector((s) =>
|
||||
rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 })
|
||||
);
|
||||
const maskColorString = useAppSelector((s) => rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 }));
|
||||
const tool = useStore($tool);
|
||||
const layer = useAppSelector((s) => s.canvas.layer);
|
||||
const dotRadius = useAppSelector((s) => 1.5 / s.canvas.stageScale);
|
||||
const strokeWidth = useAppSelector((s) => 1.5 / s.canvas.stageScale);
|
||||
const brushColorString = useAppSelector((s) =>
|
||||
rgbaColorToString(s.canvas.brushColor)
|
||||
);
|
||||
const colorPickerColorString = useAppSelector((s) =>
|
||||
rgbaColorToString(s.canvas.colorPickerColor)
|
||||
);
|
||||
const brushColorString = useAppSelector((s) => rgbaColorToString(s.canvas.brushColor));
|
||||
const colorPickerColorString = useAppSelector((s) => rgbaColorToString(s.canvas.colorPickerColor));
|
||||
const colorPickerInnerRadius = useAppSelector(
|
||||
(s) =>
|
||||
(COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / s.canvas.stageScale
|
||||
);
|
||||
const colorPickerOuterRadius = useAppSelector(
|
||||
(s) => COLOR_PICKER_SIZE / s.canvas.stageScale
|
||||
(s) => (COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / s.canvas.stageScale
|
||||
);
|
||||
const colorPickerOuterRadius = useAppSelector((s) => COLOR_PICKER_SIZE / s.canvas.stageScale);
|
||||
const { clip, stageDimensions } = useAppSelector(canvasBrushPreviewSelector);
|
||||
|
||||
const cursorPosition = useStore($cursorPosition);
|
||||
@ -123,8 +103,7 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
);
|
||||
|
||||
const shouldDrawBrushPreview = useMemo(
|
||||
() =>
|
||||
!(isMovingBoundingBox || isTransformingBoundingBox || !cursorPosition),
|
||||
() => !(isMovingBoundingBox || isTransformingBoundingBox || !cursorPosition),
|
||||
[cursorPosition, isMovingBoundingBox, isTransformingBoundingBox]
|
||||
);
|
||||
|
||||
@ -162,9 +141,7 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
y={brushY}
|
||||
radius={radius}
|
||||
fill={layer === 'mask' ? maskColorString : brushColorString}
|
||||
globalCompositeOperation={
|
||||
tool === 'eraser' ? 'destination-out' : 'source-out'
|
||||
}
|
||||
globalCompositeOperation={tool === 'eraser' ? 'destination-out' : 'source-out'}
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
@ -187,20 +164,8 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Circle
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={dotRadius * 2}
|
||||
fill="rgba(255,255,255,0.4)"
|
||||
listening={false}
|
||||
/>
|
||||
<Circle
|
||||
x={brushX}
|
||||
y={brushY}
|
||||
radius={dotRadius}
|
||||
fill="rgba(0,0,0,1)"
|
||||
listening={false}
|
||||
/>
|
||||
<Circle x={brushX} y={brushY} radius={dotRadius * 2} fill="rgba(255,255,255,0.4)" listening={false} />
|
||||
<Circle x={brushX} y={brushY} radius={dotRadius} fill="rgba(0,0,0,1)" listening={false} />
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,7 @@
|
||||
import { useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
roundDownToMultiple,
|
||||
roundDownToMultipleMin,
|
||||
roundToMultiple,
|
||||
} from 'common/util/roundDownToMultiple';
|
||||
import { roundDownToMultiple, roundDownToMultipleMin, roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import {
|
||||
$isDrawing,
|
||||
$isMouseOverBoundingBox,
|
||||
@ -20,10 +16,7 @@ import {
|
||||
setBoundingBoxDimensions,
|
||||
setShouldSnapToGrid,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
CANVAS_GRID_SIZE_COARSE,
|
||||
CANVAS_GRID_SIZE_FINE,
|
||||
} from 'features/canvas/store/constants';
|
||||
import { CANVAS_GRID_SIZE_COARSE, CANVAS_GRID_SIZE_FINE } from 'features/canvas/store/constants';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import type Konva from 'konva';
|
||||
@ -41,12 +34,8 @@ type IAICanvasBoundingBoxPreviewProps = GroupConfig;
|
||||
const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
const { ...rest } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const boundingBoxCoordinates = useAppSelector(
|
||||
(s) => s.canvas.boundingBoxCoordinates
|
||||
);
|
||||
const boundingBoxDimensions = useAppSelector(
|
||||
(s) => s.canvas.boundingBoxDimensions
|
||||
);
|
||||
const boundingBoxCoordinates = useAppSelector((s) => s.canvas.boundingBoxCoordinates);
|
||||
const boundingBoxDimensions = useAppSelector((s) => s.canvas.boundingBoxDimensions);
|
||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
||||
const hitStrokeWidth = useAppSelector((s) => 20 / s.canvas.stageScale);
|
||||
@ -59,9 +48,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
const isDrawing = useStore($isDrawing);
|
||||
const isMovingBoundingBox = useStore($isMovingBoundingBox);
|
||||
const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
|
||||
const isMouseOverBoundingBoxOutline = useStore(
|
||||
$isMouseOverBoundingBoxOutline
|
||||
);
|
||||
const isMouseOverBoundingBoxOutline = useStore($isMouseOverBoundingBoxOutline);
|
||||
|
||||
useEffect(() => {
|
||||
if (!transformerRef.current || !shapeRef.current) {
|
||||
@ -71,14 +58,8 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
transformerRef.current.getLayer()?.batchDraw();
|
||||
}, []);
|
||||
|
||||
const gridSize = useMemo(
|
||||
() => (shift ? CANVAS_GRID_SIZE_FINE : CANVAS_GRID_SIZE_COARSE),
|
||||
[shift]
|
||||
);
|
||||
const scaledStep = useMemo(
|
||||
() => gridSize * stageScale,
|
||||
[gridSize, stageScale]
|
||||
);
|
||||
const gridSize = useMemo(() => (shift ? CANVAS_GRID_SIZE_FINE : CANVAS_GRID_SIZE_COARSE), [shift]);
|
||||
const scaledStep = useMemo(() => gridSize * stageScale, [gridSize, stageScale]);
|
||||
|
||||
useHotkeys(
|
||||
'N',
|
||||
@ -143,10 +124,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
const y = Math.round(rect.y());
|
||||
|
||||
if (aspectRatio.isLocked) {
|
||||
const newDimensions = calculateNewSize(
|
||||
aspectRatio.value,
|
||||
width * height
|
||||
);
|
||||
const newDimensions = calculateNewSize(aspectRatio.value, width * height);
|
||||
dispatch(
|
||||
setBoundingBoxDimensions(
|
||||
{
|
||||
@ -186,14 +164,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
rect.scaleX(1);
|
||||
rect.scaleY(1);
|
||||
},
|
||||
[
|
||||
aspectRatio.isLocked,
|
||||
aspectRatio.value,
|
||||
dispatch,
|
||||
shouldSnapToGrid,
|
||||
gridSize,
|
||||
optimalDimension,
|
||||
]
|
||||
[aspectRatio.isLocked, aspectRatio.value, dispatch, shouldSnapToGrid, gridSize, optimalDimension]
|
||||
);
|
||||
|
||||
const anchorDragBoundFunc = useCallback(
|
||||
@ -255,9 +226,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
}, []);
|
||||
|
||||
const handleMouseOut = useCallback(() => {
|
||||
!isTransformingBoundingBox &&
|
||||
!isMovingBoundingBox &&
|
||||
$isMouseOverBoundingBoxOutline.set(false);
|
||||
!isTransformingBoundingBox && !isMovingBoundingBox && $isMouseOverBoundingBoxOutline.set(false);
|
||||
}, [isMovingBoundingBox, isTransformingBoundingBox]);
|
||||
|
||||
const handleMouseEnterBoundingBox = useCallback(() => {
|
||||
@ -269,35 +238,18 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
}, []);
|
||||
|
||||
const stroke = useMemo(() => {
|
||||
if (
|
||||
isMouseOverBoundingBoxOutline ||
|
||||
isMovingBoundingBox ||
|
||||
isTransformingBoundingBox
|
||||
) {
|
||||
if (isMouseOverBoundingBoxOutline || isMovingBoundingBox || isTransformingBoundingBox) {
|
||||
return 'rgba(255,255,255,0.5)';
|
||||
}
|
||||
return 'white';
|
||||
}, [
|
||||
isMouseOverBoundingBoxOutline,
|
||||
isMovingBoundingBox,
|
||||
isTransformingBoundingBox,
|
||||
]);
|
||||
}, [isMouseOverBoundingBoxOutline, isMovingBoundingBox, isTransformingBoundingBox]);
|
||||
|
||||
const strokeWidth = useMemo(() => {
|
||||
if (
|
||||
isMouseOverBoundingBoxOutline ||
|
||||
isMovingBoundingBox ||
|
||||
isTransformingBoundingBox
|
||||
) {
|
||||
if (isMouseOverBoundingBoxOutline || isMovingBoundingBox || isTransformingBoundingBox) {
|
||||
return 6 / stageScale;
|
||||
}
|
||||
return 1 / stageScale;
|
||||
}, [
|
||||
isMouseOverBoundingBoxOutline,
|
||||
isMovingBoundingBox,
|
||||
isTransformingBoundingBox,
|
||||
stageScale,
|
||||
]);
|
||||
}, [isMouseOverBoundingBoxOutline, isMovingBoundingBox, isTransformingBoundingBox, stageScale]);
|
||||
|
||||
const enabledAnchors = useMemo(() => {
|
||||
if (tool !== 'move') {
|
||||
|
@ -30,11 +30,7 @@ import { memo, useCallback } from 'react';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
PiExcludeBold,
|
||||
PiFloppyDiskBackFill,
|
||||
PiTrashSimpleFill,
|
||||
} from 'react-icons/pi';
|
||||
import { PiExcludeBold, PiFloppyDiskBackFill, PiTrashSimpleFill } from 'react-icons/pi';
|
||||
|
||||
const formLabelProps: FormLabelProps = {
|
||||
flexGrow: 1,
|
||||
@ -46,9 +42,7 @@ const IAICanvasMaskOptions = () => {
|
||||
const layer = useAppSelector((s) => s.canvas.layer);
|
||||
const maskColor = useAppSelector((s) => s.canvas.maskColor);
|
||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||
const shouldPreserveMaskedArea = useAppSelector(
|
||||
(s) => s.canvas.shouldPreserveMaskedArea
|
||||
);
|
||||
const shouldPreserveMaskedArea = useAppSelector((s) => s.canvas.shouldPreserveMaskedArea);
|
||||
const isStaging = useAppSelector(isStagingSelector);
|
||||
|
||||
useHotkeys(
|
||||
@ -134,38 +128,21 @@ const IAICanvasMaskOptions = () => {
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<FormControl>
|
||||
<FormLabel>{`${t('unifiedCanvas.enableMask')} (H)`}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={isMaskEnabled}
|
||||
onChange={handleToggleEnableMask}
|
||||
/>
|
||||
<Checkbox isChecked={isMaskEnabled} onChange={handleToggleEnableMask} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.preserveMaskedArea')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldPreserveMaskedArea}
|
||||
onChange={handleChangePreserveMaskedArea}
|
||||
/>
|
||||
<Checkbox isChecked={shouldPreserveMaskedArea} onChange={handleChangePreserveMaskedArea} />
|
||||
</FormControl>
|
||||
</FormControlGroup>
|
||||
<Box pt={2} pb={2}>
|
||||
<IAIColorPicker
|
||||
color={maskColor}
|
||||
onChange={handleChangeMaskColor}
|
||||
/>
|
||||
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
|
||||
</Box>
|
||||
<ButtonGroup isAttached={false}>
|
||||
<Button
|
||||
size="sm"
|
||||
leftIcon={<PiFloppyDiskBackFill />}
|
||||
onClick={handleSaveMask}
|
||||
>
|
||||
<Button size="sm" leftIcon={<PiFloppyDiskBackFill />} onClick={handleSaveMask}>
|
||||
{t('unifiedCanvas.saveMask')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
leftIcon={<PiTrashSimpleFill />}
|
||||
onClick={handleClearMask}
|
||||
>
|
||||
<Button size="sm" leftIcon={<PiTrashSimpleFill />} onClick={handleClearMask}>
|
||||
{t('unifiedCanvas.clearMask')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -38,23 +38,13 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const shouldAutoSave = useAppSelector((s) => s.canvas.shouldAutoSave);
|
||||
const shouldCropToBoundingBoxOnSave = useAppSelector(
|
||||
(s) => s.canvas.shouldCropToBoundingBoxOnSave
|
||||
);
|
||||
const shouldDarkenOutsideBoundingBox = useAppSelector(
|
||||
(s) => s.canvas.shouldDarkenOutsideBoundingBox
|
||||
);
|
||||
const shouldShowCanvasDebugInfo = useAppSelector(
|
||||
(s) => s.canvas.shouldShowCanvasDebugInfo
|
||||
);
|
||||
const shouldCropToBoundingBoxOnSave = useAppSelector((s) => s.canvas.shouldCropToBoundingBoxOnSave);
|
||||
const shouldDarkenOutsideBoundingBox = useAppSelector((s) => s.canvas.shouldDarkenOutsideBoundingBox);
|
||||
const shouldShowCanvasDebugInfo = useAppSelector((s) => s.canvas.shouldShowCanvasDebugInfo);
|
||||
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
|
||||
const shouldShowIntermediates = useAppSelector(
|
||||
(s) => s.canvas.shouldShowIntermediates
|
||||
);
|
||||
const shouldShowIntermediates = useAppSelector((s) => s.canvas.shouldShowIntermediates);
|
||||
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
||||
const shouldRestrictStrokesToBox = useAppSelector(
|
||||
(s) => s.canvas.shouldRestrictStrokesToBox
|
||||
);
|
||||
const shouldRestrictStrokesToBox = useAppSelector((s) => s.canvas.shouldRestrictStrokesToBox);
|
||||
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
|
||||
|
||||
useHotkeys(
|
||||
@ -70,49 +60,40 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
);
|
||||
|
||||
const handleChangeShouldSnapToGrid = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldSnapToGrid(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldSnapToGrid(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChangeShouldShowIntermediates = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowIntermediates(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldShowIntermediates(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldShowGrid = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowGrid(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldShowGrid(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldDarkenOutsideBoundingBox = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldAutoSave = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAutoSave(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldAutoSave(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldCropToBoundingBoxOnSave = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldRestrictStrokesToBox = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldRestrictStrokesToBox(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldShowCanvasDebugInfo = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowCanvasDebugInfo(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldShowCanvasDebugInfo(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldAntialias = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAntialias(e.target.checked)),
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldAntialias(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
@ -131,29 +112,18 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
<FormControlGroup formLabelProps={formLabelProps}>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.showIntermediates')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldShowIntermediates}
|
||||
onChange={handleChangeShouldShowIntermediates}
|
||||
/>
|
||||
<Checkbox isChecked={shouldShowIntermediates} onChange={handleChangeShouldShowIntermediates} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.showGrid')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldShowGrid}
|
||||
onChange={handleChangeShouldShowGrid}
|
||||
/>
|
||||
<Checkbox isChecked={shouldShowGrid} onChange={handleChangeShouldShowGrid} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.snapToGrid')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnapToGrid}
|
||||
/>
|
||||
<Checkbox isChecked={shouldSnapToGrid} onChange={handleChangeShouldSnapToGrid} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>
|
||||
{t('unifiedCanvas.darkenOutsideSelection')}
|
||||
</FormLabel>
|
||||
<FormLabel>{t('unifiedCanvas.darkenOutsideSelection')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldDarkenOutsideBoundingBox}
|
||||
onChange={handleChangeShouldDarkenOutsideBoundingBox}
|
||||
@ -161,10 +131,7 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.autoSaveToGallery')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldAutoSave}
|
||||
onChange={handleChangeShouldAutoSave}
|
||||
/>
|
||||
<Checkbox isChecked={shouldAutoSave} onChange={handleChangeShouldAutoSave} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.saveBoxRegionOnly')}</FormLabel>
|
||||
@ -175,24 +142,15 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.limitStrokesToBox')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldRestrictStrokesToBox}
|
||||
onChange={handleChangeShouldRestrictStrokesToBox}
|
||||
/>
|
||||
<Checkbox isChecked={shouldRestrictStrokesToBox} onChange={handleChangeShouldRestrictStrokesToBox} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.showCanvasDebugInfo')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldShowCanvasDebugInfo}
|
||||
onChange={handleChangeShouldShowCanvasDebugInfo}
|
||||
/>
|
||||
<Checkbox isChecked={shouldShowCanvasDebugInfo} onChange={handleChangeShouldShowCanvasDebugInfo} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>{t('unifiedCanvas.antialiasing')}</FormLabel>
|
||||
<Checkbox
|
||||
isChecked={shouldAntialias}
|
||||
onChange={handleChangeShouldAntialias}
|
||||
/>
|
||||
<Checkbox isChecked={shouldAntialias} onChange={handleChangeShouldAntialias} />
|
||||
</FormControl>
|
||||
</FormControlGroup>
|
||||
<ClearCanvasHistoryButtonModal />
|
||||
|
@ -15,17 +15,9 @@ import {
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import {
|
||||
$tool,
|
||||
resetToolInteractionState,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import { $tool, resetToolInteractionState } from 'features/canvas/store/canvasNanostore';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
addEraseRect,
|
||||
addFillRect,
|
||||
setBrushColor,
|
||||
setBrushSize,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { addEraseRect, addFillRect, setBrushColor, setBrushSize } from 'features/canvas/store/canvasSlice';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
@ -275,11 +267,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
</FormControl>
|
||||
</Flex>
|
||||
<Box w="full" pt={2} pb={2}>
|
||||
<IAIColorPicker
|
||||
color={brushColor}
|
||||
onChange={handleChangeBrushColor}
|
||||
withNumberInput
|
||||
/>
|
||||
<IAIColorPicker color={brushColor} onChange={handleChangeBrushColor} withNumberInput />
|
||||
</Box>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
|
@ -1,12 +1,5 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import {
|
||||
ButtonGroup,
|
||||
Combobox,
|
||||
Flex,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { ButtonGroup, Combobox, Flex, FormControl, IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
||||
@ -20,12 +13,7 @@ import {
|
||||
} from 'features/canvas/store/actions';
|
||||
import { $canvasBaseLayer, $tool } from 'features/canvas/store/canvasNanostore';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import {
|
||||
resetCanvas,
|
||||
resetCanvasView,
|
||||
setIsMaskEnabled,
|
||||
setLayer,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { resetCanvas, resetCanvasView, setIsMaskEnabled, setLayer } from 'features/canvas/store/canvasSlice';
|
||||
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
||||
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
@ -203,20 +191,13 @@ const IAICanvasToolbar = () => {
|
||||
[dispatch, isMaskEnabled]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0],
|
||||
[layer]
|
||||
);
|
||||
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" gap={2} flexWrap="wrap">
|
||||
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
||||
<FormControl isDisabled={isStaging} w="5rem">
|
||||
<Combobox
|
||||
value={value}
|
||||
options={LAYER_NAMES_DICT}
|
||||
onChange={handleChangeLayer}
|
||||
/>
|
||||
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
$isMovingBoundingBox,
|
||||
$isMovingStage,
|
||||
$tool,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import { $isMovingBoundingBox, $isMovingStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setStageCoordinates } from 'features/canvas/store/canvasSlice';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
@ -13,9 +9,7 @@ const useCanvasDrag = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isStaging = useAppSelector(isStagingSelector);
|
||||
const handleDragStart = useCallback(() => {
|
||||
if (
|
||||
!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())
|
||||
) {
|
||||
if (!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())) {
|
||||
return;
|
||||
}
|
||||
$isMovingStage.set(true);
|
||||
@ -36,9 +30,7 @@ const useCanvasDrag = () => {
|
||||
);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
if (
|
||||
!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())
|
||||
) {
|
||||
if (!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())) {
|
||||
return;
|
||||
}
|
||||
$isMovingStage.set(false);
|
||||
|
@ -8,30 +8,16 @@ import { useDebounce } from 'react-use';
|
||||
export const useCanvasGenerationMode = () => {
|
||||
const layerState = useAppSelector((s) => s.canvas.layerState);
|
||||
|
||||
const boundingBoxCoordinates = useAppSelector(
|
||||
(s) => s.canvas.boundingBoxCoordinates
|
||||
);
|
||||
const boundingBoxDimensions = useAppSelector(
|
||||
(s) => s.canvas.boundingBoxDimensions
|
||||
);
|
||||
const boundingBoxCoordinates = useAppSelector((s) => s.canvas.boundingBoxCoordinates);
|
||||
const boundingBoxDimensions = useAppSelector((s) => s.canvas.boundingBoxDimensions);
|
||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||
|
||||
const shouldPreserveMaskedArea = useAppSelector(
|
||||
(s) => s.canvas.shouldPreserveMaskedArea
|
||||
);
|
||||
const [generationMode, setGenerationMode] = useState<
|
||||
GenerationMode | undefined
|
||||
>();
|
||||
const shouldPreserveMaskedArea = useAppSelector((s) => s.canvas.shouldPreserveMaskedArea);
|
||||
const [generationMode, setGenerationMode] = useState<GenerationMode | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
setGenerationMode(undefined);
|
||||
}, [
|
||||
layerState,
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
isMaskEnabled,
|
||||
shouldPreserveMaskedArea,
|
||||
]);
|
||||
}, [layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea]);
|
||||
|
||||
useDebounce(
|
||||
async () => {
|
||||
@ -51,21 +37,12 @@ export const useCanvasGenerationMode = () => {
|
||||
const { baseImageData, maskImageData } = canvasBlobsAndImageData;
|
||||
|
||||
// Determine the generation mode
|
||||
const generationMode = getCanvasGenerationMode(
|
||||
baseImageData,
|
||||
maskImageData
|
||||
);
|
||||
const generationMode = getCanvasGenerationMode(baseImageData, maskImageData);
|
||||
|
||||
setGenerationMode(generationMode);
|
||||
},
|
||||
1000,
|
||||
[
|
||||
layerState,
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
isMaskEnabled,
|
||||
shouldPreserveMaskedArea,
|
||||
]
|
||||
[layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea]
|
||||
);
|
||||
|
||||
return generationMode;
|
||||
|
@ -22,9 +22,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||
const useInpaintingCanvasHotkeys = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
const shouldShowBoundingBox = useAppSelector(
|
||||
(s) => s.canvas.shouldShowBoundingBox
|
||||
);
|
||||
const shouldShowBoundingBox = useAppSelector((s) => s.canvas.shouldShowBoundingBox);
|
||||
const isStaging = useAppSelector(isStagingSelector);
|
||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
||||
@ -44,8 +42,7 @@ const useInpaintingCanvasHotkeys = () => {
|
||||
[]
|
||||
);
|
||||
|
||||
const handleToggleEnableMask = () =>
|
||||
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||
const handleToggleEnableMask = () => dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||
|
||||
useHotkeys(
|
||||
['h'],
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
$isDrawing,
|
||||
$isMovingStage,
|
||||
$tool,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import { $isDrawing, $isMovingStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { addLine } from 'features/canvas/store/canvasSlice';
|
||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
$cursorPosition,
|
||||
$isDrawing,
|
||||
$tool,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import { $cursorPosition, $isDrawing, $tool } from 'features/canvas/store/canvasNanostore';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
|
||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||
@ -49,17 +45,8 @@ const useCanvasMouseMove = (
|
||||
}
|
||||
|
||||
didMouseMoveRef.current = true;
|
||||
dispatch(
|
||||
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
|
||||
);
|
||||
}, [
|
||||
didMouseMoveRef,
|
||||
dispatch,
|
||||
isStaging,
|
||||
lastCursorPositionRef,
|
||||
stageRef,
|
||||
updateColorUnderCursor,
|
||||
]);
|
||||
dispatch(addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y]));
|
||||
}, [didMouseMoveRef, dispatch, isStaging, lastCursorPositionRef, stageRef, updateColorUnderCursor]);
|
||||
};
|
||||
|
||||
export default useCanvasMouseMove;
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
$isDrawing,
|
||||
$isMovingStage,
|
||||
$tool,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import { $isDrawing, $isMovingStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
|
||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||
@ -39,9 +35,7 @@ const useCanvasMouseUp = (
|
||||
* the line's existing points. This allows the line to render as a circle
|
||||
* centered on that point.
|
||||
*/
|
||||
dispatch(
|
||||
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
|
||||
);
|
||||
dispatch(addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y]));
|
||||
} else {
|
||||
didMouseMoveRef.current = false;
|
||||
}
|
||||
|
@ -1,15 +1,8 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { $isMoveStageKeyHeld } from 'features/canvas/store/canvasNanostore';
|
||||
import {
|
||||
setStageCoordinates,
|
||||
setStageScale,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import {
|
||||
CANVAS_SCALE_BY,
|
||||
MAX_CANVAS_SCALE,
|
||||
MIN_CANVAS_SCALE,
|
||||
} from 'features/canvas/util/constants';
|
||||
import { setStageCoordinates, setStageScale } from 'features/canvas/store/canvasSlice';
|
||||
import { CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/canvas/util/constants';
|
||||
import type Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { clamp } from 'lodash-es';
|
||||
@ -49,11 +42,7 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
||||
delta = -delta;
|
||||
}
|
||||
|
||||
const newScale = clamp(
|
||||
stageScale * CANVAS_SCALE_BY ** delta,
|
||||
MIN_CANVAS_SCALE,
|
||||
MAX_CANVAS_SCALE
|
||||
);
|
||||
const newScale = clamp(stageScale * CANVAS_SCALE_BY ** delta, MIN_CANVAS_SCALE, MAX_CANVAS_SCALE);
|
||||
|
||||
const newCoordinates = {
|
||||
x: cursorPos.x - mousePointTo.x * newScale,
|
||||
|
@ -1,13 +1,6 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import {
|
||||
$canvasBaseLayer,
|
||||
$canvasStage,
|
||||
$tool,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import {
|
||||
commitColorPickerColor,
|
||||
setColorPickerColor,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { $canvasBaseLayer, $canvasStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||
import { commitColorPickerColor, setColorPickerColor } from 'features/canvas/store/canvasSlice';
|
||||
import Konva from 'konva';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@ -31,19 +24,9 @@ const useColorPicker = () => {
|
||||
|
||||
const [r, g, b, a] = canvasBaseLayer
|
||||
.getContext()
|
||||
.getImageData(
|
||||
position.x * pixelRatio,
|
||||
position.y * pixelRatio,
|
||||
1,
|
||||
1
|
||||
).data;
|
||||
.getImageData(position.x * pixelRatio, position.y * pixelRatio, 1, 1).data;
|
||||
|
||||
if (
|
||||
r === undefined ||
|
||||
g === undefined ||
|
||||
b === undefined ||
|
||||
a === undefined
|
||||
) {
|
||||
if (r === undefined || g === undefined || b === undefined || a === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3,28 +3,16 @@ import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
export const canvasSavedToGallery = createAction('canvas/canvasSavedToGallery');
|
||||
|
||||
export const canvasMaskSavedToGallery = createAction(
|
||||
'canvas/canvasMaskSavedToGallery'
|
||||
);
|
||||
export const canvasMaskSavedToGallery = createAction('canvas/canvasMaskSavedToGallery');
|
||||
|
||||
export const canvasCopiedToClipboard = createAction(
|
||||
'canvas/canvasCopiedToClipboard'
|
||||
);
|
||||
export const canvasCopiedToClipboard = createAction('canvas/canvasCopiedToClipboard');
|
||||
|
||||
export const canvasDownloadedAsImage = createAction(
|
||||
'canvas/canvasDownloadedAsImage'
|
||||
);
|
||||
export const canvasDownloadedAsImage = createAction('canvas/canvasDownloadedAsImage');
|
||||
|
||||
export const canvasMerged = createAction('canvas/canvasMerged');
|
||||
|
||||
export const stagingAreaImageSaved = createAction<{ imageDTO: ImageDTO }>(
|
||||
'canvas/stagingAreaImageSaved'
|
||||
);
|
||||
export const stagingAreaImageSaved = createAction<{ imageDTO: ImageDTO }>('canvas/stagingAreaImageSaved');
|
||||
|
||||
export const canvasMaskToControlAdapter = createAction<{ id: string }>(
|
||||
'canvas/canvasMaskToControlAdapter'
|
||||
);
|
||||
export const canvasMaskToControlAdapter = createAction<{ id: string }>('canvas/canvasMaskToControlAdapter');
|
||||
|
||||
export const canvasImageToControlAdapter = createAction<{ id: string }>(
|
||||
'canvas/canvasImageToControlAdapter'
|
||||
);
|
||||
export const canvasImageToControlAdapter = createAction<{ id: string }>('canvas/canvasImageToControlAdapter');
|
||||
|
@ -16,8 +16,7 @@ export const $isTransformingBoundingBox = atom<boolean>(false);
|
||||
export const $isMouseOverBoundingBoxOutline = atom<boolean>(false);
|
||||
export const $isModifyingBoundingBox = computed(
|
||||
[$isTransformingBoundingBox, $isMovingBoundingBox],
|
||||
(isTransformingBoundingBox, isMovingBoundingBox) =>
|
||||
isTransformingBoundingBox || isMovingBoundingBox
|
||||
(isTransformingBoundingBox, isMovingBoundingBox) => isTransformingBoundingBox || isMovingBoundingBox
|
||||
);
|
||||
|
||||
export const resetCanvasInteractionState = () => {
|
||||
|
@ -6,12 +6,9 @@ import { isCanvasBaseImage } from './canvasTypes';
|
||||
|
||||
export const isStagingSelector = createSelector(
|
||||
selectCanvasSlice,
|
||||
(canvas) =>
|
||||
canvas.batchIds.length > 0 ||
|
||||
canvas.layerState.stagingArea.images.length > 0
|
||||
(canvas) => canvas.batchIds.length > 0 || canvas.layerState.stagingArea.images.length > 0
|
||||
);
|
||||
|
||||
export const initialCanvasImageSelector = createMemoizedSelector(
|
||||
selectCanvasSlice,
|
||||
(canvas) => canvas.layerState.objects.find(isCanvasBaseImage)
|
||||
export const initialCanvasImageSelector = createMemoizedSelector(selectCanvasSlice, (canvas) =>
|
||||
canvas.layerState.objects.find(isCanvasBaseImage)
|
||||
);
|
||||
|
@ -1,10 +1,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import {
|
||||
roundDownToMultiple,
|
||||
roundToMultiple,
|
||||
} from 'common/util/roundDownToMultiple';
|
||||
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import calculateCoordinates from 'features/canvas/util/calculateCoordinates';
|
||||
import calculateScale from 'features/canvas/util/calculateScale';
|
||||
import { STAGE_PADDING_PERCENTAGE } from 'features/canvas/util/constants';
|
||||
@ -14,10 +11,7 @@ import { initialAspectRatioState } from 'features/parameters/components/ImageSiz
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { modelChanged } from 'features/parameters/store/generationSlice';
|
||||
import type { PayloadActionWithOptimalDimension } from 'features/parameters/store/types';
|
||||
import {
|
||||
getIsSizeOptimal,
|
||||
getOptimalDimension,
|
||||
} from 'features/parameters/util/optimalDimension';
|
||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { clamp, cloneDeep } from 'lodash-es';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
@ -105,10 +99,7 @@ const setBoundingBoxDimensionsReducer = (
|
||||
};
|
||||
state.boundingBoxDimensions = newDimensions;
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(newDimensions, optimalDimension);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
};
|
||||
@ -131,9 +122,7 @@ export const canvasSlice = createSlice({
|
||||
},
|
||||
clearMask: (state) => {
|
||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||
state.layerState.objects = state.layerState.objects.filter(
|
||||
(obj) => !isCanvasMaskLine(obj)
|
||||
);
|
||||
state.layerState.objects = state.layerState.objects.filter((obj) => !isCanvasMaskLine(obj));
|
||||
state.futureLayerStates = [];
|
||||
state.shouldPreserveMaskedArea = false;
|
||||
},
|
||||
@ -157,32 +146,17 @@ export const canvasSlice = createSlice({
|
||||
const { stageDimensions } = state;
|
||||
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
width: roundDownToMultiple(clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE),
|
||||
height: roundDownToMultiple(clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension), CANVAS_GRID_SIZE_FINE),
|
||||
};
|
||||
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
width / 2 - newBoundingBoxDimensions.width / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
y: roundToMultiple(
|
||||
height / 2 - newBoundingBoxDimensions.height / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
x: roundToMultiple(width / 2 - newBoundingBoxDimensions.width / 2, CANVAS_GRID_SIZE_FINE),
|
||||
y: roundToMultiple(height / 2 - newBoundingBoxDimensions.height / 2, CANVAS_GRID_SIZE_FINE),
|
||||
};
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(newBoundingBoxDimensions, optimalDimension);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
|
||||
@ -247,10 +221,7 @@ export const canvasSlice = createSlice({
|
||||
setStageScale: (state, action: PayloadAction<number>) => {
|
||||
state.stageScale = action.payload;
|
||||
},
|
||||
setShouldDarkenOutsideBoundingBox: (
|
||||
state,
|
||||
action: PayloadAction<boolean>
|
||||
) => {
|
||||
setShouldDarkenOutsideBoundingBox: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldDarkenOutsideBoundingBox = action.payload;
|
||||
},
|
||||
clearCanvasHistory: (state) => {
|
||||
@ -306,8 +277,7 @@ export const canvasSlice = createSlice({
|
||||
imageName: image.image_name,
|
||||
});
|
||||
|
||||
state.layerState.stagingArea.selectedImageIndex =
|
||||
state.layerState.stagingArea.images.length - 1;
|
||||
state.layerState.stagingArea.selectedImageIndex = state.layerState.stagingArea.images.length - 1;
|
||||
|
||||
state.futureLayerStates = [];
|
||||
},
|
||||
@ -318,9 +288,7 @@ export const canvasSlice = createSlice({
|
||||
state.pastLayerStates.shift();
|
||||
}
|
||||
|
||||
state.layerState.stagingArea = cloneDeep(
|
||||
cloneDeep(initialLayerState)
|
||||
).stagingArea;
|
||||
state.layerState.stagingArea = cloneDeep(cloneDeep(initialLayerState)).stagingArea;
|
||||
|
||||
state.futureLayerStates = [];
|
||||
state.shouldShowStagingOutline = true;
|
||||
@ -328,8 +296,7 @@ export const canvasSlice = createSlice({
|
||||
state.batchIds = [];
|
||||
},
|
||||
addFillRect: (state) => {
|
||||
const { boundingBoxCoordinates, boundingBoxDimensions, brushColor } =
|
||||
state;
|
||||
const { boundingBoxCoordinates, boundingBoxDimensions, brushColor } = state;
|
||||
|
||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||
|
||||
@ -365,12 +332,8 @@ export const canvasSlice = createSlice({
|
||||
|
||||
state.futureLayerStates = [];
|
||||
},
|
||||
addLine: (
|
||||
state,
|
||||
action: PayloadAction<{ points: number[]; tool: CanvasTool }>
|
||||
) => {
|
||||
const { layer, brushColor, brushSize, shouldRestrictStrokesToBox } =
|
||||
state;
|
||||
addLine: (state, action: PayloadAction<{ points: number[]; tool: CanvasTool }>) => {
|
||||
const { layer, brushColor, brushSize, shouldRestrictStrokesToBox } = state;
|
||||
const { points, tool } = action.payload;
|
||||
|
||||
if (tool === 'move' || tool === 'colorPicker') {
|
||||
@ -380,8 +343,7 @@ export const canvasSlice = createSlice({
|
||||
const newStrokeWidth = brushSize / 2;
|
||||
|
||||
// set & then spread this to only conditionally add the "color" key
|
||||
const newColor =
|
||||
layer === 'base' && tool === 'brush' ? { color: brushColor } : {};
|
||||
const newColor = layer === 'base' && tool === 'brush' ? { color: brushColor } : {};
|
||||
|
||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||
|
||||
@ -488,10 +450,7 @@ export const canvasSlice = createSlice({
|
||||
1
|
||||
);
|
||||
},
|
||||
canvasResized: (
|
||||
state,
|
||||
action: PayloadAction<{ width: number; height: number }>
|
||||
) => {
|
||||
canvasResized: (state, action: PayloadAction<{ width: number; height: number }>) => {
|
||||
state.stageDimensions = {
|
||||
width: Math.floor(action.payload.width),
|
||||
height: Math.floor(action.payload.height),
|
||||
@ -540,8 +499,7 @@ export const canvasSlice = createSlice({
|
||||
const nextIndex = state.layerState.stagingArea.selectedImageIndex + 1;
|
||||
const lastIndex = state.layerState.stagingArea.images.length - 1;
|
||||
|
||||
state.layerState.stagingArea.selectedImageIndex =
|
||||
nextIndex > lastIndex ? 0 : nextIndex;
|
||||
state.layerState.stagingArea.selectedImageIndex = nextIndex > lastIndex ? 0 : nextIndex;
|
||||
},
|
||||
prevStagingAreaImage: (state) => {
|
||||
if (!state.layerState.stagingArea.images.length) {
|
||||
@ -551,8 +509,7 @@ export const canvasSlice = createSlice({
|
||||
const prevIndex = state.layerState.stagingArea.selectedImageIndex - 1;
|
||||
const lastIndex = state.layerState.stagingArea.images.length - 1;
|
||||
|
||||
state.layerState.stagingArea.selectedImageIndex =
|
||||
prevIndex < 0 ? lastIndex : prevIndex;
|
||||
state.layerState.stagingArea.selectedImageIndex = prevIndex < 0 ? lastIndex : prevIndex;
|
||||
},
|
||||
commitStagingAreaImage: (state) => {
|
||||
if (!state.layerState.stagingArea.images.length) {
|
||||
@ -582,19 +539,13 @@ export const canvasSlice = createSlice({
|
||||
state.batchIds = [];
|
||||
},
|
||||
setBoundingBoxScaleMethod: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadActionWithOptimalDimension<BoundingBoxScaleMethod>
|
||||
) => {
|
||||
reducer: (state, action: PayloadActionWithOptimalDimension<BoundingBoxScaleMethod>) => {
|
||||
const boundingBoxScaleMethod = action.payload;
|
||||
const { optimalDimension } = action.meta;
|
||||
state.boundingBoxScaleMethod = boundingBoxScaleMethod;
|
||||
|
||||
if (boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
state.boundingBoxDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(state.boundingBoxDimensions, optimalDimension);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
},
|
||||
@ -605,25 +556,15 @@ export const canvasSlice = createSlice({
|
||||
},
|
||||
}),
|
||||
},
|
||||
setScaledBoundingBoxDimensions: (
|
||||
state,
|
||||
action: PayloadAction<Partial<Dimensions>>
|
||||
) => {
|
||||
setScaledBoundingBoxDimensions: (state, action: PayloadAction<Partial<Dimensions>>) => {
|
||||
state.scaledBoundingBoxDimensions = {
|
||||
...state.scaledBoundingBoxDimensions,
|
||||
...action.payload,
|
||||
};
|
||||
},
|
||||
setBoundingBoxDimensions: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadActionWithOptimalDimension<Partial<Dimensions>>
|
||||
) => {
|
||||
setBoundingBoxDimensionsReducer(
|
||||
state,
|
||||
action.payload,
|
||||
action.meta.optimalDimension
|
||||
);
|
||||
reducer: (state, action: PayloadActionWithOptimalDimension<Partial<Dimensions>>) => {
|
||||
setBoundingBoxDimensionsReducer(state, action.payload, action.meta.optimalDimension);
|
||||
},
|
||||
prepare: (payload: Partial<Dimensions>, optimalDimension: number) => ({
|
||||
payload,
|
||||
@ -660,10 +601,7 @@ export const canvasSlice = createSlice({
|
||||
setShouldAntialias: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldAntialias = action.payload;
|
||||
},
|
||||
setShouldCropToBoundingBoxOnSave: (
|
||||
state,
|
||||
action: PayloadAction<boolean>
|
||||
) => {
|
||||
setShouldCropToBoundingBoxOnSave: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldCropToBoundingBoxOnSave = action.payload;
|
||||
},
|
||||
setColorPickerColor: (state, action: PayloadAction<RgbaColor>) => {
|
||||
@ -688,9 +626,7 @@ export const canvasSlice = createSlice({
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(modelChanged, (state, action) => {
|
||||
if (
|
||||
action.meta.previousModel?.base_model === action.payload?.base_model
|
||||
) {
|
||||
if (action.meta.previousModel?.base_model === action.payload?.base_model) {
|
||||
// The base model hasn't changed, we don't need to optimize the size
|
||||
return;
|
||||
}
|
||||
@ -716,25 +652,15 @@ export const canvasSlice = createSlice({
|
||||
}
|
||||
|
||||
if (batch_status.in_progress === 0 && batch_status.pending === 0) {
|
||||
state.batchIds = state.batchIds.filter(
|
||||
(id) => id !== batch_status.batch_id
|
||||
);
|
||||
state.batchIds = state.batchIds.filter((id) => id !== batch_status.batch_id);
|
||||
}
|
||||
});
|
||||
builder.addMatcher(
|
||||
queueApi.endpoints.clearQueue.matchFulfilled,
|
||||
(state) => {
|
||||
state.batchIds = [];
|
||||
}
|
||||
);
|
||||
builder.addMatcher(
|
||||
queueApi.endpoints.cancelByBatchIds.matchFulfilled,
|
||||
(state, action) => {
|
||||
state.batchIds = state.batchIds.filter(
|
||||
(id) => !action.meta.arg.originalArgs.batch_ids.includes(id)
|
||||
);
|
||||
}
|
||||
);
|
||||
builder.addMatcher(queueApi.endpoints.clearQueue.matchFulfilled, (state) => {
|
||||
state.batchIds = [];
|
||||
});
|
||||
builder.addMatcher(queueApi.endpoints.cancelByBatchIds.matchFulfilled, (state, action) => {
|
||||
state.batchIds = state.batchIds.filter((id) => !action.meta.arg.originalArgs.batch_ids.includes(id));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -14,9 +14,8 @@ export const LAYER_NAMES = ['base', 'mask'] as const;
|
||||
|
||||
export const zBoundingBoxScaleMethod = z.enum(['none', 'auto', 'manual']);
|
||||
export type BoundingBoxScaleMethod = z.infer<typeof zBoundingBoxScaleMethod>;
|
||||
export const isBoundingBoxScaleMethod = (
|
||||
v: unknown
|
||||
): v is BoundingBoxScaleMethod => zBoundingBoxScaleMethod.safeParse(v).success;
|
||||
export const isBoundingBoxScaleMethod = (v: unknown): v is BoundingBoxScaleMethod =>
|
||||
zBoundingBoxScaleMethod.safeParse(v).success;
|
||||
|
||||
export type CanvasDrawingTool = 'brush' | 'eraser';
|
||||
|
||||
@ -75,12 +74,7 @@ export type CanvasEraseRect = {
|
||||
height: number;
|
||||
};
|
||||
|
||||
export type CanvasObject =
|
||||
| CanvasImage
|
||||
| CanvasBaseLine
|
||||
| CanvasMaskLine
|
||||
| CanvasFillRect
|
||||
| CanvasEraseRect;
|
||||
export type CanvasObject = CanvasImage | CanvasBaseLine | CanvasMaskLine | CanvasFillRect | CanvasEraseRect;
|
||||
|
||||
export type CanvasLayerState = {
|
||||
objects: CanvasObject[];
|
||||
@ -112,9 +106,7 @@ export const isCanvasFillRect = (obj: CanvasObject): obj is CanvasFillRect =>
|
||||
export const isCanvasEraseRect = (obj: CanvasObject): obj is CanvasEraseRect =>
|
||||
obj.kind === 'eraseRect' && obj.layer === 'base';
|
||||
|
||||
export const isCanvasAnyLine = (
|
||||
obj: CanvasObject
|
||||
): obj is CanvasMaskLine | CanvasBaseLine => obj.kind === 'line';
|
||||
export const isCanvasAnyLine = (obj: CanvasObject): obj is CanvasMaskLine | CanvasBaseLine => obj.kind === 'line';
|
||||
|
||||
export interface CanvasState {
|
||||
_version: 1;
|
||||
|
@ -9,12 +9,8 @@ const calculateCoordinates = (
|
||||
contentHeight: number,
|
||||
scale: number
|
||||
): Vector2d => {
|
||||
const x = Math.floor(
|
||||
containerWidth / 2 - (containerX + contentWidth / 2) * scale
|
||||
);
|
||||
const y = Math.floor(
|
||||
containerHeight / 2 - (containerY + contentHeight / 2) * scale
|
||||
);
|
||||
const x = Math.floor(containerWidth / 2 - (containerX + contentWidth / 2) * scale);
|
||||
const y = Math.floor(containerHeight / 2 - (containerY + contentHeight / 2) * scale);
|
||||
return { x, y };
|
||||
};
|
||||
|
||||
|
@ -44,8 +44,7 @@ const createMaskStage = async (
|
||||
lineCap: 'round',
|
||||
lineJoin: 'round',
|
||||
shadowForStrokeEnabled: false,
|
||||
globalCompositeOperation:
|
||||
line.tool === 'brush' ? 'source-over' : 'destination-out',
|
||||
globalCompositeOperation: line.tool === 'brush' ? 'source-over' : 'destination-out',
|
||||
})
|
||||
)
|
||||
);
|
||||
|
@ -1,11 +1,7 @@
|
||||
/**
|
||||
* Gets an ImageData object from an image dataURL by drawing it to a canvas.
|
||||
*/
|
||||
export const dataURLToImageData = async (
|
||||
dataURL: string,
|
||||
width: number,
|
||||
height: number
|
||||
): Promise<ImageData> =>
|
||||
export const dataURLToImageData = async (dataURL: string, width: number, height: number): Promise<ImageData> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
|
@ -6,21 +6,14 @@ import { konvaNodeToBlob } from './konvaNodeToBlob';
|
||||
/**
|
||||
* Get the canvas base layer blob, with or without bounding box according to `shouldCropToBoundingBoxOnSave`
|
||||
*/
|
||||
export const getBaseLayerBlob = async (
|
||||
state: RootState,
|
||||
alwaysUseBoundingBox: boolean = false
|
||||
) => {
|
||||
export const getBaseLayerBlob = async (state: RootState, alwaysUseBoundingBox: boolean = false) => {
|
||||
const canvasBaseLayer = $canvasBaseLayer.get();
|
||||
|
||||
if (!canvasBaseLayer) {
|
||||
throw new Error('Problem getting base layer blob');
|
||||
}
|
||||
|
||||
const {
|
||||
shouldCropToBoundingBoxOnSave,
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
} = state.canvas;
|
||||
const { shouldCropToBoundingBoxOnSave, boundingBoxCoordinates, boundingBoxDimensions } = state.canvas;
|
||||
|
||||
const clonedBaseLayer = canvasBaseLayer.clone();
|
||||
|
||||
|
@ -1,12 +1,6 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import {
|
||||
$canvasBaseLayer,
|
||||
$canvasStage,
|
||||
} from 'features/canvas/store/canvasNanostore';
|
||||
import type {
|
||||
CanvasLayerState,
|
||||
Dimensions,
|
||||
} from 'features/canvas/store/canvasTypes';
|
||||
import { $canvasBaseLayer, $canvasStage } from 'features/canvas/store/canvasNanostore';
|
||||
import type { CanvasLayerState, Dimensions } from 'features/canvas/store/canvasTypes';
|
||||
import { isCanvasMaskLine } from 'features/canvas/store/canvasTypes';
|
||||
import { konvaNodeToImageData } from 'features/canvas/util/konvaNodeToImageData';
|
||||
import type { Vector2d } from 'konva/lib/types';
|
||||
@ -57,10 +51,7 @@ export const getCanvasData = async (
|
||||
|
||||
// For the base layer, use the offset boundingBox
|
||||
const baseBlob = await konvaNodeToBlob(clonedBaseLayer, offsetBoundingBox);
|
||||
const baseImageData = await konvaNodeToImageData(
|
||||
clonedBaseLayer,
|
||||
offsetBoundingBox
|
||||
);
|
||||
const baseImageData = await konvaNodeToImageData(clonedBaseLayer, offsetBoundingBox);
|
||||
|
||||
// For the mask layer, use the normal boundingBox
|
||||
const maskStage = await createMaskStage(
|
||||
|
@ -1,17 +1,9 @@
|
||||
import {
|
||||
areAnyPixelsBlack,
|
||||
getImageDataTransparency,
|
||||
} from 'common/util/arrayBuffer';
|
||||
import { areAnyPixelsBlack, getImageDataTransparency } from 'common/util/arrayBuffer';
|
||||
import type { GenerationMode } from 'features/canvas/store/canvasTypes';
|
||||
|
||||
export const getCanvasGenerationMode = (
|
||||
baseImageData: ImageData,
|
||||
maskImageData: ImageData
|
||||
): GenerationMode => {
|
||||
const {
|
||||
isPartiallyTransparent: baseIsPartiallyTransparent,
|
||||
isFullyTransparent: baseIsFullyTransparent,
|
||||
} = getImageDataTransparency(baseImageData.data);
|
||||
export const getCanvasGenerationMode = (baseImageData: ImageData, maskImageData: ImageData): GenerationMode => {
|
||||
const { isPartiallyTransparent: baseIsPartiallyTransparent, isFullyTransparent: baseIsFullyTransparent } =
|
||||
getImageDataTransparency(baseImageData.data);
|
||||
|
||||
// check mask for black
|
||||
const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData.data);
|
||||
|
@ -2,10 +2,7 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import type { Dimensions } from 'features/canvas/store/canvasTypes';
|
||||
import { CANVAS_GRID_SIZE_FINE } from 'features/canvas/store/constants';
|
||||
|
||||
const getScaledBoundingBoxDimensions = (
|
||||
dimensions: Dimensions,
|
||||
optimalDimension: number
|
||||
) => {
|
||||
const getScaledBoundingBoxDimensions = (dimensions: Dimensions, optimalDimension: number) => {
|
||||
const { width, height } = dimensions;
|
||||
|
||||
const scaledDimensions = { width, height };
|
||||
@ -22,16 +19,10 @@ const getScaledBoundingBoxDimensions = (
|
||||
} else {
|
||||
if (aspectRatio > 1) {
|
||||
scaledDimensions.width = maxDimension;
|
||||
scaledDimensions.height = roundToMultiple(
|
||||
maxDimension / aspectRatio,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
);
|
||||
scaledDimensions.height = roundToMultiple(maxDimension / aspectRatio, CANVAS_GRID_SIZE_FINE);
|
||||
} else if (aspectRatio < 1) {
|
||||
scaledDimensions.height = maxDimension;
|
||||
scaledDimensions.width = roundToMultiple(
|
||||
maxDimension * aspectRatio,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
);
|
||||
scaledDimensions.width = roundToMultiple(maxDimension * aspectRatio, CANVAS_GRID_SIZE_FINE);
|
||||
}
|
||||
currentArea = scaledDimensions.width * scaledDimensions.height;
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import { CANVAS_TAB_TESTID } from 'features/canvas/store/constants';
|
||||
* it's safer to check the canvas tab and grab its parent.
|
||||
*/
|
||||
export const isElChildOfCanvasTab = (el: HTMLElement) => {
|
||||
const canvasContainerEl = document.querySelector(
|
||||
`[data-testid="${CANVAS_TAB_TESTID}"]`
|
||||
);
|
||||
const canvasContainerEl = document.querySelector(`[data-testid="${CANVAS_TAB_TESTID}"]`);
|
||||
return Boolean(canvasContainerEl?.parentElement?.contains(el));
|
||||
};
|
||||
|
@ -9,9 +9,6 @@ import { canvasToBlob } from './canvasToBlob';
|
||||
* @param boundingBox - The bounding box to crop to
|
||||
* @returns A Promise that resolves with Blob of the node cropped to the bounding box
|
||||
*/
|
||||
export const konvaNodeToBlob = async (
|
||||
node: Konva.Node,
|
||||
boundingBox: IRect
|
||||
): Promise<Blob> => {
|
||||
export const konvaNodeToBlob = async (node: Konva.Node, boundingBox: IRect): Promise<Blob> => {
|
||||
return await canvasToBlob(node.toCanvas(boundingBox));
|
||||
};
|
||||
|
@ -9,16 +9,9 @@ import { dataURLToImageData } from './dataURLToImageData';
|
||||
* @param boundingBox - The bounding box to crop to
|
||||
* @returns A Promise that resolves with ImageData object of the node cropped to the bounding box
|
||||
*/
|
||||
export const konvaNodeToImageData = async (
|
||||
node: Konva.Node,
|
||||
boundingBox: IRect
|
||||
): Promise<ImageData> => {
|
||||
export const konvaNodeToImageData = async (node: Konva.Node, boundingBox: IRect): Promise<ImageData> => {
|
||||
// get a dataURL of the bbox'd region
|
||||
const dataURL = node.toDataURL(boundingBox);
|
||||
|
||||
return await dataURLToImageData(
|
||||
dataURL,
|
||||
boundingBox.width,
|
||||
boundingBox.height
|
||||
);
|
||||
return await dataURLToImageData(dataURL, boundingBox.width, boundingBox.height);
|
||||
};
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import type { Dimensions } from 'features/canvas/store/canvasTypes';
|
||||
|
||||
const roundDimensionsToMultiple = (
|
||||
dimensions: Dimensions,
|
||||
multiple: number
|
||||
): Dimensions => {
|
||||
const roundDimensionsToMultiple = (dimensions: Dimensions, multiple: number): Dimensions => {
|
||||
return {
|
||||
width: roundToMultiple(dimensions.width, multiple),
|
||||
height: roundToMultiple(dimensions.height, multiple),
|
||||
|
Reference in New Issue
Block a user