diff --git a/backend/invoke_ai_web_server.py b/backend/invoke_ai_web_server.py index 91313f4303..b52aaa0136 100644 --- a/backend/invoke_ai_web_server.py +++ b/backend/invoke_ai_web_server.py @@ -172,8 +172,11 @@ class InvokeAIWebServer: (width, height) = pil_image.size + thumbnail_path = save_thumbnail(pil_image, file_path) + response = { "url": self.get_url_from_image_path(file_path), + "thumbnail": self.get_url_from_image_path(thumbnail_path), "mtime": mtime, "width": width, "height": height, @@ -306,6 +309,7 @@ class InvokeAIWebServer: ) paths = [] + for ext in ("*.png", "*.jpg", "*.jpeg"): paths.extend(glob.glob(os.path.join(base_path, ext))) @@ -329,11 +333,15 @@ class InvokeAIWebServer: else: sd_metadata = {} - (width, height) = Image.open(path).size + pil_image = Image.open(path) + (width, height) = pil_image.size + + thumbnail_path = save_thumbnail(pil_image, path) image_array.append( { "url": self.get_url_from_image_path(path), + "thumbnail": self.get_url_from_image_path(thumbnail_path), "mtime": os.path.getmtime(path), "metadata": sd_metadata, "width": width, @@ -389,11 +397,15 @@ class InvokeAIWebServer: else: sd_metadata = {} - (width, height) = Image.open(path).size + pil_image = Image.open(path) + (width, height) = pil_image.size + + thumbnail_path = save_thumbnail(pil_image, path) image_array.append( { "url": self.get_url_from_image_path(path), + "thumbnail": self.get_url_from_image_path(thumbnail_path), "mtime": os.path.getmtime(path), "metadata": sd_metadata, "width": width, @@ -537,6 +549,8 @@ class InvokeAIWebServer: postprocessing=postprocessing_parameters["type"], ) + thumbnail_path = save_thumbnail(image, path) + self.write_log_message( f'[Postprocessed] "{original_image_path}" > "{path}": {postprocessing_parameters}' ) @@ -549,6 +563,7 @@ class InvokeAIWebServer: "postprocessingResult", { "url": self.get_url_from_image_path(path), + "thumbnail": self.get_url_from_image_path(thumbnail_path), "mtime": os.path.getmtime(path), "metadata": metadata, "width": width, @@ -575,8 +590,12 @@ class InvokeAIWebServer: from send2trash import send2trash path = self.get_image_path_from_url(url) + base = os.path.splitext(path)[0] + thumbnail_path = base + ".webp" send2trash(path) + send2trash(thumbnail_path) + socketio.emit( "imageDeleted", {"url": url, "uuid": uuid, "category": category}, @@ -949,6 +968,8 @@ class InvokeAIWebServer: postprocessing=postprocessing, ) + thumbnail_path = save_thumbnail(image, path) + print(f'>> Image generated: "{path}"') self.write_log_message(f'[Generated] "{path}": {command}') @@ -966,6 +987,7 @@ class InvokeAIWebServer: "generationResult", { "url": self.get_url_from_image_path(path), + "thumbnail": self.get_url_from_image_path(thumbnail_path), "mtime": os.path.getmtime(path), "metadata": metadata, "width": width, @@ -1457,3 +1479,30 @@ def paste_image_into_bounding_box( bounds = (x, y, x + width, y + height) im.paste(donor_image, bounds) return recipient_image + + +""" +Saves a thumbnail of an image, returning its path. +""" + + +def save_thumbnail( + image: ImageType, + path: str, + size: int = 128, +) -> str: + base = os.path.splitext(path)[0] + thumbnail_path = base + ".webp" + + if os.path.exists(thumbnail_path): + return thumbnail_path + + thumbnail_width = size + thumbnail_height = round(size * (image.height / image.width)) + + image_copy = image.copy() + image_copy.thumbnail(size=(thumbnail_width, thumbnail_height)) + + image_copy.save(thumbnail_path, "WEBP") + + return thumbnail_path diff --git a/frontend/src/app/invokeai.d.ts b/frontend/src/app/invokeai.d.ts index e411e147e6..c26f9e4d7f 100644 --- a/frontend/src/app/invokeai.d.ts +++ b/frontend/src/app/invokeai.d.ts @@ -113,6 +113,7 @@ export declare type Metadata = SystemConfig & { export declare type Image = { uuid: string; url: string; + thumbnail: string; mtime: number; metadata?: Metadata; width: number; @@ -184,6 +185,7 @@ export declare type ImageUploadResponse = { mtime: number; width: number; height: number; + thumbnail: string; // bbox: [number, number, number, number]; }; diff --git a/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts b/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts index f12a8b2548..d06994aee9 100644 --- a/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts +++ b/frontend/src/features/canvas/store/thunks/mergeAndUploadCanvas.ts @@ -69,16 +69,14 @@ export const mergeAndUploadCanvas = body: formData, }); - const { url, mtime, width, height } = - (await response.json()) as InvokeAI.ImageUploadResponse; + const image = (await response.json()) as InvokeAI.ImageUploadResponse; + + const { url, width, height } = image; const newImage: InvokeAI.Image = { uuid: uuidv4(), - url, - mtime, category: shouldSaveToGallery ? 'result' : 'user', - width: width, - height: height, + ...image, }; if (shouldDownload) { diff --git a/frontend/src/features/gallery/components/HoverableImage.tsx b/frontend/src/features/gallery/components/HoverableImage.tsx index 63f5c0b2d7..91fe2f20df 100644 --- a/frontend/src/features/gallery/components/HoverableImage.tsx +++ b/frontend/src/features/gallery/components/HoverableImage.tsx @@ -23,13 +23,10 @@ import { import * as InvokeAI from 'app/invokeai'; import * as ContextMenu from '@radix-ui/react-context-menu'; import { - resetCanvasView, resizeAndScaleCanvas, - setDoesCanvasNeedScaling, setInitialCanvasImage, } from 'features/canvas/store/canvasSlice'; import { hoverableImageSelector } from 'features/gallery/store/gallerySliceSelectors'; -import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; interface HoverableImageProps { image: InvokeAI.Image; @@ -54,7 +51,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { isLightBoxOpen, } = useAppSelector(hoverableImageSelector); const { image, isSelected } = props; - const { url, uuid, metadata } = image; + const { url, thumbnail, uuid, metadata } = image; const [isHovered, setIsHovered] = useState(false); @@ -173,7 +170,7 @@ const HoverableImage = memo((props: HoverableImageProps) => { className="hoverable-image-image" objectFit={galleryImageObjectFit} rounded={'md'} - src={url} + src={thumbnail || url} loading={'lazy'} />
diff --git a/frontend/src/features/gallery/store/thunks/uploadImage.ts b/frontend/src/features/gallery/store/thunks/uploadImage.ts index 4759bb6d09..01595d7da3 100644 --- a/frontend/src/features/gallery/store/thunks/uploadImage.ts +++ b/frontend/src/features/gallery/store/thunks/uploadImage.ts @@ -37,16 +37,12 @@ export const uploadImage = body: formData, }); - const { url, mtime, width, height } = - (await response.json()) as InvokeAI.ImageUploadResponse; - + const image = (await response.json()) as InvokeAI.ImageUploadResponse; + console.log(image) const newImage: InvokeAI.Image = { uuid: uuidv4(), - url, - mtime, category: 'user', - width: width, - height: height, + ...image, }; dispatch(addImage({ image: newImage, category: 'user' }));