diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx index ab17c9ef4e..4377658348 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparison.tsx @@ -1,32 +1,31 @@ -import type { UseMeasureRect } from '@reactuses/core'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import IAIDroppable from 'common/components/IAIDroppable'; import type { SelectForCompareDropData } from 'features/dnd/types'; import { ImageComparisonSideBySide } from 'features/gallery/components/ImageViewer/ImageComparisonSideBySide'; import { ImageComparisonSlider } from 'features/gallery/components/ImageViewer/ImageComparisonSlider'; +import { selectGallerySlice } from 'features/gallery/store/gallerySlice'; import type { PropsWithChildren } from 'react'; import { memo } from 'react'; -type Props = { - containerSize: UseMeasureRect; -}; +const selector = createMemoizedSelector(selectGallerySlice, (gallerySlice) => { + const firstImage = gallerySlice.selection.slice(-1)[0] ?? null; + const secondImage = gallerySlice.imageToCompare; + return { firstImage, secondImage }; +}); -export const ImageComparison = memo(({ containerSize }: Props) => { +export const ImageComparison = memo(() => { const comparisonMode = useAppSelector((s) => s.gallery.comparisonMode); - const { firstImage, secondImage } = useAppSelector((s) => { - const firstImage = s.gallery.selection.slice(-1)[0] ?? null; - const secondImage = s.gallery.imageToCompare; - return { firstImage, secondImage }; - }); + const { firstImage, secondImage } = useAppSelector(selector); if (!firstImage || !secondImage) { - return No images to compare; + return Select an image to compare; } if (comparisonMode === 'slider') { return ( - + ); } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx index edc2199aed..0f9636a61c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSideBySide.tsx @@ -1,7 +1,8 @@ -import { Flex, Image } from '@invoke-ai/ui-library'; +import { Flex } from '@invoke-ai/ui-library'; +import IAIDndImage from 'common/components/IAIDndImage'; +import type { ImageDraggableData } from 'features/dnd/types'; import ResizeHandle from 'features/ui/components/tabs/ResizeHandle'; -import { memo, useCallback, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; +import { memo, useCallback, useMemo, useRef } from 'react'; import type { ImperativePanelGroupHandle } from 'react-resizable-panels'; import { Panel, PanelGroup } from 'react-resizable-panels'; import type { ImageDTO } from 'services/api/types'; @@ -18,7 +19,6 @@ type Props = { }; export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Props) => { - const { t } = useTranslation(); const panelGroupRef = useRef(null); const onDoubleClickHandle = useCallback(() => { if (!panelGroupRef.current) { @@ -27,21 +27,31 @@ export const ImageComparisonSideBySide = memo(({ firstImage, secondImage }: Prop panelGroupRef.current.setLayout([50, 50]); }, []); + const firstImageDraggableData = useMemo( + () => ({ + id: 'image-compare-first-image', + payloadType: 'IMAGE_DTO', + payload: { imageDTO: firstImage }, + }), + [firstImage] + ); + + const secondImageDraggableData = useMemo( + () => ({ + id: 'image-compare-second-image', + payloadType: 'IMAGE_DTO', + payload: { imageDTO: secondImage }, + }), + [secondImage] + ); + return ( - + - + diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx index c9f169eeff..3eacacf6e5 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/ImageComparisonSlider.tsx @@ -1,5 +1,5 @@ import { Box, Flex, Icon, Image, Text } from '@invoke-ai/ui-library'; -import type { UseMeasureRect } from '@reactuses/core'; +import { useMeasure } from '@reactuses/core'; import type { Dimensions } from 'features/canvas/store/canvasTypes'; import { STAGE_BG_DATAURL } from 'features/controlLayers/util/renderers'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -25,13 +25,9 @@ type Props = { * The second image to compare */ secondImage: ImageDTO; - /** - * The size of the container, required to fit the component correctly and manage aspect ratios. - */ - containerSize: UseMeasureRect; }; -export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerSize }: Props) => { +export const ImageComparisonSlider = memo(({ firstImage, secondImage }: Props) => { const { t } = useTranslation(); // How far the handle is from the left - this will be a CSS calculation that takes into account the handle width const [left, setLeft] = useState(HANDLE_LEFT_INITIAL_PX); @@ -40,6 +36,7 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerS const handleRef = useRef(null); // If the container size is not provided, use an internal ref and measure - can cause flicker on mount tho const containerRef = useRef(null); + const [containerSize] = useMeasure(containerRef); // To keep things smooth, we use RAF to update the handle position & gate it to 60fps const rafRef = useRef(null); const lastMoveTimeRef = useRef(0); @@ -94,13 +91,13 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerS const targetAspectRatio = containerSize.width / containerSize.height; const imageAspectRatio = firstImage.width / firstImage.height; + let width: number; + let height: number; + if (firstImage.width <= containerSize.width && firstImage.height <= containerSize.height) { return { width: firstImage.width, height: firstImage.height }; } - let width: number; - let height: number; - if (imageAspectRatio > targetAspectRatio) { // Image is wider than container's aspect ratio width = containerSize.width; @@ -123,7 +120,16 @@ export const ImageComparisonSlider = memo(({ firstImage, secondImage, containerS ); return ( - + { const { viewerMode, onToggle, openEditor } = useImageViewer(); const activeTabName = useAppSelector(activeTabNameSelector); const isViewerEnabled = useMemo(() => VIEWER_ENABLED_TABS.includes(activeTabName), [activeTabName]); - const containerRef = useRef(null); - const [containerSize] = useMeasure(containerRef); const shouldShowViewer = useMemo(() => { if (!isViewerEnabled) { return false; @@ -70,9 +67,9 @@ export const ImageViewer = memo(() => { - + {viewerMode === 'view' && } - {viewerMode === 'compare' && } + {viewerMode === 'compare' && } );