mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): aspect ratio preview is regional prompts canvas
This commit is contained in:
parent
bb37e25ed0
commit
c915220965
@ -1,73 +1,10 @@
|
|||||||
import { useSize } from '@chakra-ui/react-use-size';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import { Flex, Icon } from '@invoke-ai/ui-library';
|
import { StageComponent } from 'features/regionalPrompts/components/StageComponent';
|
||||||
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { useMemo, useRef } from 'react';
|
|
||||||
import { PiFrameCorners } from 'react-icons/pi';
|
|
||||||
|
|
||||||
import {
|
|
||||||
BOX_SIZE_CSS_CALC,
|
|
||||||
ICON_CONTAINER_STYLES,
|
|
||||||
ICON_HIGH_CUTOFF,
|
|
||||||
ICON_LOW_CUTOFF,
|
|
||||||
MOTION_ICON_ANIMATE,
|
|
||||||
MOTION_ICON_EXIT,
|
|
||||||
MOTION_ICON_INITIAL,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
export const AspectRatioPreview = () => {
|
export const AspectRatioPreview = () => {
|
||||||
const ctx = useImageSizeContext();
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
const containerSize = useSize(containerRef);
|
|
||||||
|
|
||||||
const shouldShowIcon = useMemo(
|
|
||||||
() => ctx.aspectRatioState.value < ICON_HIGH_CUTOFF && ctx.aspectRatioState.value > ICON_LOW_CUTOFF,
|
|
||||||
[ctx.aspectRatioState.value]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { width, height } = useMemo(() => {
|
|
||||||
if (!containerSize) {
|
|
||||||
return { width: 0, height: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
let width = ctx.width;
|
|
||||||
let height = ctx.height;
|
|
||||||
|
|
||||||
if (ctx.width > ctx.height) {
|
|
||||||
width = containerSize.width;
|
|
||||||
height = width / ctx.aspectRatioState.value;
|
|
||||||
} else {
|
|
||||||
height = containerSize.height;
|
|
||||||
width = height * ctx.aspectRatioState.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { width, height };
|
|
||||||
}, [containerSize, ctx.width, ctx.height, ctx.aspectRatioState.value]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w="full" h="full" alignItems="center" justifyContent="center" ref={containerRef}>
|
<Flex w="full" h="full" alignItems="center" justifyContent="center">
|
||||||
<Flex
|
<StageComponent asPreview />
|
||||||
bg="blackAlpha.400"
|
|
||||||
borderRadius="base"
|
|
||||||
width={`${width}px`}
|
|
||||||
height={`${height}px`}
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
>
|
|
||||||
<AnimatePresence>
|
|
||||||
{shouldShowIcon && (
|
|
||||||
<Flex
|
|
||||||
as={motion.div}
|
|
||||||
initial={MOTION_ICON_INITIAL}
|
|
||||||
animate={MOTION_ICON_ANIMATE}
|
|
||||||
exit={MOTION_ICON_EXIT}
|
|
||||||
style={ICON_CONTAINER_STYLES}
|
|
||||||
>
|
|
||||||
<Icon as={PiFrameCorners} color="base.700" boxSize={BOX_SIZE_CSS_CALC} />
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
@ -14,18 +15,18 @@ import {
|
|||||||
layerTranslated,
|
layerTranslated,
|
||||||
selectRegionalPromptsSlice,
|
selectRegionalPromptsSlice,
|
||||||
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { renderBackground, renderBbox, renderLayers, renderToolPreview } from 'features/regionalPrompts/util/renderers';
|
import { renderers } from 'features/regionalPrompts/util/renderers';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { IRect } from 'konva/lib/types';
|
import type { IRect } from 'konva/lib/types';
|
||||||
import { atom } from 'nanostores';
|
import type { MutableRefObject } from 'react';
|
||||||
import { memo, useCallback, useLayoutEffect } from 'react';
|
import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
// This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead?
|
// This will log warnings when layers > 5 - maybe use `import.meta.env.MODE === 'development'` instead?
|
||||||
Konva.showWarnings = false;
|
Konva.showWarnings = false;
|
||||||
|
|
||||||
const log = logger('regionalPrompts');
|
const log = logger('regionalPrompts');
|
||||||
const $stage = atom<Konva.Stage | null>(null);
|
|
||||||
const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
|
||||||
const layer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId);
|
const layer = regionalPrompts.present.layers.find((l) => l.id === regionalPrompts.present.selectedLayerId);
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
@ -35,43 +36,52 @@ const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSli
|
|||||||
return layer.previewColor;
|
return layer.previewColor;
|
||||||
});
|
});
|
||||||
|
|
||||||
const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElement | null, asPreview: boolean) => {
|
const useStageRenderer = (
|
||||||
|
stageRef: MutableRefObject<Konva.Stage>,
|
||||||
|
container: HTMLDivElement | null,
|
||||||
|
wrapper: HTMLDivElement | null,
|
||||||
|
asPreview: boolean
|
||||||
|
) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const width = useAppSelector((s) => s.generation.width);
|
const width = useAppSelector((s) => s.generation.width);
|
||||||
const height = useAppSelector((s) => s.generation.height);
|
const height = useAppSelector((s) => s.generation.height);
|
||||||
const state = useAppSelector((s) => s.regionalPrompts.present);
|
const state = useAppSelector((s) => s.regionalPrompts.present);
|
||||||
const stage = useStore($stage);
|
|
||||||
const tool = useStore($tool);
|
const tool = useStore($tool);
|
||||||
const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave } = useMouseEvents();
|
const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave } = useMouseEvents();
|
||||||
const cursorPosition = useStore($cursorPosition);
|
const cursorPosition = useStore($cursorPosition);
|
||||||
const lastMouseDownPos = useStore($lastMouseDownPos);
|
const lastMouseDownPos = useStore($lastMouseDownPos);
|
||||||
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
|
const selectedLayerIdColor = useAppSelector(selectSelectedLayerColor);
|
||||||
|
|
||||||
|
const renderLayers = useMemo(() => (asPreview ? renderers.layersDebounced : renderers.layers), [asPreview]);
|
||||||
|
const renderToolPreview = useMemo(
|
||||||
|
() => (asPreview ? renderers.toolPreviewDebounced : renderers.toolPreview),
|
||||||
|
[asPreview]
|
||||||
|
);
|
||||||
|
const renderBbox = useMemo(() => (asPreview ? renderers.bboxDebounced : renderers.bbox), [asPreview]);
|
||||||
|
const renderBackground = useMemo(
|
||||||
|
() => (asPreview ? renderers.backgroundDebounced : renderers.background),
|
||||||
|
[asPreview]
|
||||||
|
);
|
||||||
|
|
||||||
const onLayerPosChanged = useCallback(
|
const onLayerPosChanged = useCallback(
|
||||||
(layerId: string, x: number, y: number) => {
|
(layerId: string, x: number, y: number) => {
|
||||||
if (asPreview) {
|
|
||||||
dispatch(layerTranslated({ layerId, x, y }));
|
dispatch(layerTranslated({ layerId, x, y }));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[dispatch, asPreview]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onBboxChanged = useCallback(
|
const onBboxChanged = useCallback(
|
||||||
(layerId: string, bbox: IRect | null) => {
|
(layerId: string, bbox: IRect | null) => {
|
||||||
if (asPreview) {
|
|
||||||
dispatch(layerBboxChanged({ layerId, bbox }));
|
dispatch(layerBboxChanged({ layerId, bbox }));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[dispatch, asPreview]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onBboxMouseDown = useCallback(
|
const onBboxMouseDown = useCallback(
|
||||||
(layerId: string) => {
|
(layerId: string) => {
|
||||||
if (asPreview) {
|
|
||||||
dispatch(layerSelected(layerId));
|
dispatch(layerSelected(layerId));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[dispatch, asPreview]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -79,27 +89,24 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
|
|||||||
if (!container) {
|
if (!container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$stage.set(
|
const stage = stageRef.current.container(container);
|
||||||
new Konva.Stage({
|
|
||||||
container,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return () => {
|
return () => {
|
||||||
log.trace('Cleaning up stage');
|
log.trace('Cleaning up stage');
|
||||||
$stage.get()?.destroy();
|
stage.destroy();
|
||||||
};
|
};
|
||||||
}, [container]);
|
}, [container, stageRef]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
log.trace('Adding stage listeners');
|
log.trace('Adding stage listeners');
|
||||||
if (!stage || asPreview) {
|
if (asPreview) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stage.on('mousedown', onMouseDown);
|
stageRef.current.on('mousedown', onMouseDown);
|
||||||
stage.on('mouseup', onMouseUp);
|
stageRef.current.on('mouseup', onMouseUp);
|
||||||
stage.on('mousemove', onMouseMove);
|
stageRef.current.on('mousemove', onMouseMove);
|
||||||
stage.on('mouseenter', onMouseEnter);
|
stageRef.current.on('mouseenter', onMouseEnter);
|
||||||
stage.on('mouseleave', onMouseLeave);
|
stageRef.current.on('mouseleave', onMouseLeave);
|
||||||
|
const stage = stageRef.current;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
log.trace('Cleaning up stage listeners');
|
log.trace('Cleaning up stage listeners');
|
||||||
@ -109,14 +116,16 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
|
|||||||
stage.off('mouseenter', onMouseEnter);
|
stage.off('mouseenter', onMouseEnter);
|
||||||
stage.off('mouseleave', onMouseLeave);
|
stage.off('mouseleave', onMouseLeave);
|
||||||
};
|
};
|
||||||
}, [stage, asPreview, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave]);
|
}, [stageRef, asPreview, onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
log.trace('Updating stage dimensions');
|
log.trace('Updating stage dimensions');
|
||||||
if (!stage || !wrapper) {
|
if (!wrapper) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stage = stageRef.current;
|
||||||
|
|
||||||
const fitStageToContainer = () => {
|
const fitStageToContainer = () => {
|
||||||
const newXScale = wrapper.offsetWidth / width;
|
const newXScale = wrapper.offsetWidth / width;
|
||||||
const newYScale = wrapper.offsetHeight / height;
|
const newYScale = wrapper.offsetHeight / height;
|
||||||
@ -134,15 +143,15 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
|
|||||||
return () => {
|
return () => {
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
};
|
};
|
||||||
}, [stage, width, height, wrapper]);
|
}, [stageRef, width, height, wrapper]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
log.trace('Rendering brush preview');
|
log.trace('Rendering brush preview');
|
||||||
if (!stage || asPreview) {
|
if (asPreview) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderToolPreview(
|
renderToolPreview(
|
||||||
stage,
|
stageRef.current,
|
||||||
tool,
|
tool,
|
||||||
selectedLayerIdColor,
|
selectedLayerIdColor,
|
||||||
state.globalMaskLayerOpacity,
|
state.globalMaskLayerOpacity,
|
||||||
@ -152,47 +161,36 @@ const useStageRenderer = (container: HTMLDivElement | null, wrapper: HTMLDivElem
|
|||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
asPreview,
|
asPreview,
|
||||||
stage,
|
stageRef,
|
||||||
tool,
|
tool,
|
||||||
selectedLayerIdColor,
|
selectedLayerIdColor,
|
||||||
state.globalMaskLayerOpacity,
|
state.globalMaskLayerOpacity,
|
||||||
cursorPosition,
|
cursorPosition,
|
||||||
lastMouseDownPos,
|
lastMouseDownPos,
|
||||||
state.brushSize,
|
state.brushSize,
|
||||||
|
renderToolPreview,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
log.trace('Rendering layers');
|
log.trace('Rendering layers');
|
||||||
if (!stage) {
|
renderLayers(stageRef.current, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged);
|
||||||
return;
|
}, [stageRef, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged, renderLayers]);
|
||||||
}
|
|
||||||
renderLayers(stage, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged);
|
|
||||||
}, [stage, state.layers, state.globalMaskLayerOpacity, tool, onLayerPosChanged]);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
log.trace('Rendering bbox');
|
log.trace('Rendering bbox');
|
||||||
if (!stage || asPreview) {
|
if (asPreview) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderBbox(stage, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown);
|
renderBbox(stageRef.current, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown);
|
||||||
}, [stage, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown]);
|
}, [stageRef, asPreview, state.layers, state.selectedLayerId, tool, onBboxChanged, onBboxMouseDown, renderBbox]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
log.trace('Rendering background');
|
log.trace('Rendering background');
|
||||||
if (!stage || asPreview) {
|
if (asPreview) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderBackground(stage, width, height);
|
renderBackground(stageRef.current, width, height);
|
||||||
}, [stage, asPreview, width, height]);
|
}, [stageRef, asPreview, width, height, renderBackground]);
|
||||||
};
|
|
||||||
|
|
||||||
const $container = atom<HTMLDivElement | null>(null);
|
|
||||||
const containerRef = (el: HTMLDivElement | null) => {
|
|
||||||
$container.set(el);
|
|
||||||
};
|
|
||||||
const $wrapper = atom<HTMLDivElement | null>(null);
|
|
||||||
const wrapperRef = (el: HTMLDivElement | null) => {
|
|
||||||
$wrapper.set(el);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -200,24 +198,39 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const StageComponent = memo(({ asPreview = false }: Props) => {
|
export const StageComponent = memo(({ asPreview = false }: Props) => {
|
||||||
const container = useStore($container);
|
const stageRef = useRef<Konva.Stage>(
|
||||||
const wrapper = useStore($wrapper);
|
new Konva.Stage({
|
||||||
useStageRenderer(container, wrapper, asPreview);
|
container: document.createElement('div'), // We will overwrite this shortly...
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const [container, setContainer] = useState<HTMLDivElement | null>(null);
|
||||||
|
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const containerRef = useCallback((el: HTMLDivElement | null) => {
|
||||||
|
setContainer(el);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const wrapperRef = useCallback((el: HTMLDivElement | null) => {
|
||||||
|
setWrapper(el);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useStageRenderer(stageRef, container, wrapper, asPreview);
|
||||||
|
|
||||||
|
const sx = useMemo<SystemStyleObject>(
|
||||||
|
() => ({
|
||||||
|
bg: 'base.850',
|
||||||
|
p: asPreview ? 0 : 2,
|
||||||
|
borderRadius: asPreview ? 0 : 'base',
|
||||||
|
borderWidth: asPreview ? 0 : 1,
|
||||||
|
w: 'min-content',
|
||||||
|
h: 'min-content',
|
||||||
|
}),
|
||||||
|
[asPreview]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Flex overflow="hidden" w="full" h="full">
|
<Flex overflow="hidden" w="full" h="full">
|
||||||
<Flex ref={wrapperRef} w="full" h="full" alignItems="center" justifyContent="center">
|
<Flex ref={wrapperRef} w="full" h="full" alignItems="center" justifyContent="center">
|
||||||
<Flex
|
<Flex ref={containerRef} tabIndex={-1} sx={sx} />
|
||||||
ref={containerRef}
|
|
||||||
tabIndex={-1}
|
|
||||||
bg="base.850"
|
|
||||||
p={2}
|
|
||||||
borderRadius="base"
|
|
||||||
borderWidth={1}
|
|
||||||
w="min-content"
|
|
||||||
h="min-content"
|
|
||||||
minW={64}
|
|
||||||
minH={64}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@ import { getStore } from 'app/store/nanostores/store';
|
|||||||
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
||||||
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
||||||
import { VECTOR_MASK_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { VECTOR_MASK_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { renderLayers } from 'features/regionalPrompts/util/renderers';
|
import { renderers } from 'features/regionalPrompts/util/renderers';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ export const getRegionalPromptLayerBlobs = async (
|
|||||||
const reduxLayers = state.regionalPrompts.present.layers;
|
const reduxLayers = state.regionalPrompts.present.layers;
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
const stage = new Konva.Stage({ container, width: state.generation.width, height: state.generation.height });
|
const stage = new Konva.Stage({ container, width: state.generation.width, height: state.generation.height });
|
||||||
renderLayers(stage, reduxLayers, 1, 'brush');
|
renderers.layers(stage, reduxLayers, 1, 'brush');
|
||||||
|
|
||||||
const konvaLayers = stage.find<Konva.Layer>(`.${VECTOR_MASK_LAYER_NAME}`);
|
const konvaLayers = stage.find<Konva.Layer>(`.${VECTOR_MASK_LAYER_NAME}`);
|
||||||
const blobs: Record<string, Blob> = {};
|
const blobs: Record<string, Blob> = {};
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox';
|
import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
@ -34,6 +35,8 @@ const BBOX_NOT_SELECTED_STROKE = 'rgba(255, 255, 255, 0.353)';
|
|||||||
const BBOX_NOT_SELECTED_MOUSEOVER_STROKE = 'rgba(255, 255, 255, 0.661)';
|
const BBOX_NOT_SELECTED_MOUSEOVER_STROKE = 'rgba(255, 255, 255, 0.661)';
|
||||||
const BRUSH_BORDER_INNER_COLOR = 'rgba(0,0,0,1)';
|
const BRUSH_BORDER_INNER_COLOR = 'rgba(0,0,0,1)';
|
||||||
const BRUSH_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)';
|
const BRUSH_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)';
|
||||||
|
const STAGE_BG_DATAURL =
|
||||||
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAEsmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjIwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMjAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyMCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjMwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIzMDAvMSIKICAgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIKICAgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjEwLjgiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDQtMjNUMDg6MjA6NDcrMTA6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/Pn9pdVgAAAGBaUNDUHNSR0IgSUVDNjE5NjYtMi4xAAAokXWR3yuDURjHP5uJmKghFy6WxpVpqMWNMgm1tGbKr5vt3S+1d3t73y3JrXKrKHHj1wV/AbfKtVJESq53TdywXs9rakv2nJ7zfM73nOfpnOeAPZJRVMPhAzWb18NTAffC4pK7oYiDTjpw4YgqhjYeCgWpaR8P2Kx457Vq1T73rzXHE4YCtkbhMUXT88LTwsG1vGbxrnC7ko7Ghc+F+3W5oPC9pcfKXLQ4VeYvi/VIeALsbcLuVBXHqlhJ66qwvByPmikov/exXuJMZOfnJPaId2MQZooAbmaYZAI/g4zK7MfLEAOyoka+7yd/lpzkKjJrrKOzSoo0efpFLUj1hMSk6AkZGdat/v/tq5EcHipXdwag/sU033qhYQdK26b5eWyapROoe4arbCU/dwQj76JvVzTPIbRuwsV1RYvtweUWdD1pUT36I9WJ25NJeD2DlkVw3ULTcrlnv/ucPkJkQ77qBvYPoE/Ot658AxagZ8FoS/a7AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAL0lEQVQ4jWM8ffo0A25gYmKCR5YJjxxBMKp5ZGhm/P//Px7pM2fO0MrmUc0jQzMAB2EIhZC3pUYAAAAASUVORK5CYII=';
|
||||||
|
|
||||||
const mapId = (object: { id: string }) => object.id;
|
const mapId = (object: { id: string }) => object.id;
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ const selectVectorMaskObjects = (node: Konva.Node) => {
|
|||||||
* @param lastMouseDownPos The position of the last mouse down event - used for the rect tool.
|
* @param lastMouseDownPos The position of the last mouse down event - used for the rect tool.
|
||||||
* @param brushSize The brush size.
|
* @param brushSize The brush size.
|
||||||
*/
|
*/
|
||||||
export const renderToolPreview = (
|
const toolPreview = (
|
||||||
stage: Konva.Stage,
|
stage: Konva.Stage,
|
||||||
tool: Tool,
|
tool: Tool,
|
||||||
color: RgbColor | null,
|
color: RgbColor | null,
|
||||||
@ -197,7 +200,7 @@ export const renderToolPreview = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderVectorMaskLayer = (
|
const vectorMaskLayer = (
|
||||||
stage: Konva.Stage,
|
stage: Konva.Stage,
|
||||||
vmLayer: VectorMaskLayer,
|
vmLayer: VectorMaskLayer,
|
||||||
vmLayerIndex: number,
|
vmLayerIndex: number,
|
||||||
@ -369,7 +372,7 @@ const renderVectorMaskLayer = (
|
|||||||
* @param onLayerPosChanged Callback for when the layer's position changes. This is optional to allow for offscreen rendering.
|
* @param onLayerPosChanged Callback for when the layer's position changes. This is optional to allow for offscreen rendering.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const renderLayers = (
|
const layers = (
|
||||||
stage: Konva.Stage,
|
stage: Konva.Stage,
|
||||||
reduxLayers: Layer[],
|
reduxLayers: Layer[],
|
||||||
globalMaskLayerOpacity: number,
|
globalMaskLayerOpacity: number,
|
||||||
@ -389,7 +392,7 @@ export const renderLayers = (
|
|||||||
const reduxLayer = reduxLayers[layerIndex];
|
const reduxLayer = reduxLayers[layerIndex];
|
||||||
assert(reduxLayer, `Layer at index ${layerIndex} is undefined`);
|
assert(reduxLayer, `Layer at index ${layerIndex} is undefined`);
|
||||||
if (isVectorMaskLayer(reduxLayer)) {
|
if (isVectorMaskLayer(reduxLayer)) {
|
||||||
renderVectorMaskLayer(stage, reduxLayer, layerIndex, globalMaskLayerOpacity, tool, onLayerPosChanged);
|
vectorMaskLayer(stage, reduxLayer, layerIndex, globalMaskLayerOpacity, tool, onLayerPosChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -402,7 +405,7 @@ export const renderLayers = (
|
|||||||
* @param onBboxChanged A callback to be called when the bounding box changes.
|
* @param onBboxChanged A callback to be called when the bounding box changes.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const renderBbox = (
|
const bbox = (
|
||||||
stage: Konva.Stage,
|
stage: Konva.Stage,
|
||||||
reduxLayers: Layer[],
|
reduxLayers: Layer[],
|
||||||
selectedLayerId: string | null,
|
selectedLayerId: string | null,
|
||||||
@ -478,7 +481,7 @@ export const renderBbox = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderBackground = (stage: Konva.Stage, width: number, height: number) => {
|
const background = (stage: Konva.Stage, width: number, height: number) => {
|
||||||
let layer = stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`);
|
let layer = stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`);
|
||||||
|
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
@ -501,8 +504,7 @@ export const renderBackground = (stage: Konva.Stage, width: number, height: numb
|
|||||||
background.fillPatternImage(image);
|
background.fillPatternImage(image);
|
||||||
};
|
};
|
||||||
// This is invokeai/frontend/web/public/assets/images/transparent_bg.png as a dataURL
|
// This is invokeai/frontend/web/public/assets/images/transparent_bg.png as a dataURL
|
||||||
image.src =
|
image.src = STAGE_BG_DATAURL;
|
||||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAEsmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjIwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMjAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyMCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjMwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIzMDAvMSIKICAgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIKICAgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjEwLjgiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDQtMjNUMDg6MjA6NDcrMTA6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/Pn9pdVgAAAGBaUNDUHNSR0IgSUVDNjE5NjYtMi4xAAAokXWR3yuDURjHP5uJmKghFy6WxpVpqMWNMgm1tGbKr5vt3S+1d3t73y3JrXKrKHHj1wV/AbfKtVJESq53TdywXs9rakv2nJ7zfM73nOfpnOeAPZJRVMPhAzWb18NTAffC4pK7oYiDTjpw4YgqhjYeCgWpaR8P2Kx457Vq1T73rzXHE4YCtkbhMUXT88LTwsG1vGbxrnC7ko7Ghc+F+3W5oPC9pcfKXLQ4VeYvi/VIeALsbcLuVBXHqlhJ66qwvByPmikov/exXuJMZOfnJPaId2MQZooAbmaYZAI/g4zK7MfLEAOyoka+7yd/lpzkKjJrrKOzSoo0efpFLUj1hMSk6AkZGdat/v/tq5EcHipXdwag/sU033qhYQdK26b5eWyapROoe4arbCU/dwQj76JvVzTPIbRuwsV1RYvtweUWdD1pUT36I9WJ25NJeD2DlkVw3ULTcrlnv/ucPkJkQ77qBvYPoE/Ot658AxagZ8FoS/a7AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAL0lEQVQ4jWM8ffo0A25gYmKCR5YJjxxBMKp5ZGhm/P//Px7pM2fO0MrmUc0jQzMAB2EIhZC3pUYAAAAASUVORK5CYII=';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const background = layer.findOne<Konva.Rect>(`#${BACKGROUND_RECT_ID}`);
|
const background = layer.findOne<Konva.Rect>(`#${BACKGROUND_RECT_ID}`);
|
||||||
@ -525,3 +527,16 @@ export const renderBackground = (stage: Konva.Stage, width: number, height: numb
|
|||||||
// Apply that movement to the fill pattern
|
// Apply that movement to the fill pattern
|
||||||
background.fillPatternOffset(stagePos);
|
background.fillPatternOffset(stagePos);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEBOUNCE_MS = 100;
|
||||||
|
|
||||||
|
export const renderers = {
|
||||||
|
toolPreview,
|
||||||
|
toolPreviewDebounced: debounce(toolPreview, DEBOUNCE_MS),
|
||||||
|
layers,
|
||||||
|
layersDebounced: debounce(layers, DEBOUNCE_MS),
|
||||||
|
bbox,
|
||||||
|
bboxDebounced: debounce(bbox, DEBOUNCE_MS),
|
||||||
|
background,
|
||||||
|
backgroundDebounced: debounce(background, DEBOUNCE_MS),
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user