Implements thumbnails for gallery

- Thumbnails are saved whenever an image is saved, and when gallery requests images from server
- Thumbnails saved at original image aspect ratio with width of 128px as WEBP
- If the thumbnail property of an image is unavailable for whatever reason, the image's full size URL is used instead
This commit is contained in:
psychedelicious 2022-11-20 00:04:06 +11:00 committed by blessedcoolant
parent a3308c853d
commit 50a67a7172
5 changed files with 62 additions and 20 deletions

View File

@ -172,8 +172,11 @@ class InvokeAIWebServer:
(width, height) = pil_image.size (width, height) = pil_image.size
thumbnail_path = save_thumbnail(pil_image, file_path)
response = { response = {
"url": self.get_url_from_image_path(file_path), "url": self.get_url_from_image_path(file_path),
"thumbnail": self.get_url_from_image_path(thumbnail_path),
"mtime": mtime, "mtime": mtime,
"width": width, "width": width,
"height": height, "height": height,
@ -306,6 +309,7 @@ class InvokeAIWebServer:
) )
paths = [] paths = []
for ext in ("*.png", "*.jpg", "*.jpeg"): for ext in ("*.png", "*.jpg", "*.jpeg"):
paths.extend(glob.glob(os.path.join(base_path, ext))) paths.extend(glob.glob(os.path.join(base_path, ext)))
@ -329,11 +333,15 @@ class InvokeAIWebServer:
else: else:
sd_metadata = {} 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( image_array.append(
{ {
"url": self.get_url_from_image_path(path), "url": self.get_url_from_image_path(path),
"thumbnail": self.get_url_from_image_path(thumbnail_path),
"mtime": os.path.getmtime(path), "mtime": os.path.getmtime(path),
"metadata": sd_metadata, "metadata": sd_metadata,
"width": width, "width": width,
@ -389,11 +397,15 @@ class InvokeAIWebServer:
else: else:
sd_metadata = {} 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( image_array.append(
{ {
"url": self.get_url_from_image_path(path), "url": self.get_url_from_image_path(path),
"thumbnail": self.get_url_from_image_path(thumbnail_path),
"mtime": os.path.getmtime(path), "mtime": os.path.getmtime(path),
"metadata": sd_metadata, "metadata": sd_metadata,
"width": width, "width": width,
@ -537,6 +549,8 @@ class InvokeAIWebServer:
postprocessing=postprocessing_parameters["type"], postprocessing=postprocessing_parameters["type"],
) )
thumbnail_path = save_thumbnail(image, path)
self.write_log_message( self.write_log_message(
f'[Postprocessed] "{original_image_path}" > "{path}": {postprocessing_parameters}' f'[Postprocessed] "{original_image_path}" > "{path}": {postprocessing_parameters}'
) )
@ -549,6 +563,7 @@ class InvokeAIWebServer:
"postprocessingResult", "postprocessingResult",
{ {
"url": self.get_url_from_image_path(path), "url": self.get_url_from_image_path(path),
"thumbnail": self.get_url_from_image_path(thumbnail_path),
"mtime": os.path.getmtime(path), "mtime": os.path.getmtime(path),
"metadata": metadata, "metadata": metadata,
"width": width, "width": width,
@ -575,8 +590,12 @@ class InvokeAIWebServer:
from send2trash import send2trash from send2trash import send2trash
path = self.get_image_path_from_url(url) path = self.get_image_path_from_url(url)
base = os.path.splitext(path)[0]
thumbnail_path = base + ".webp"
send2trash(path) send2trash(path)
send2trash(thumbnail_path)
socketio.emit( socketio.emit(
"imageDeleted", "imageDeleted",
{"url": url, "uuid": uuid, "category": category}, {"url": url, "uuid": uuid, "category": category},
@ -949,6 +968,8 @@ class InvokeAIWebServer:
postprocessing=postprocessing, postprocessing=postprocessing,
) )
thumbnail_path = save_thumbnail(image, path)
print(f'>> Image generated: "{path}"') print(f'>> Image generated: "{path}"')
self.write_log_message(f'[Generated] "{path}": {command}') self.write_log_message(f'[Generated] "{path}": {command}')
@ -966,6 +987,7 @@ class InvokeAIWebServer:
"generationResult", "generationResult",
{ {
"url": self.get_url_from_image_path(path), "url": self.get_url_from_image_path(path),
"thumbnail": self.get_url_from_image_path(thumbnail_path),
"mtime": os.path.getmtime(path), "mtime": os.path.getmtime(path),
"metadata": metadata, "metadata": metadata,
"width": width, "width": width,
@ -1457,3 +1479,30 @@ def paste_image_into_bounding_box(
bounds = (x, y, x + width, y + height) bounds = (x, y, x + width, y + height)
im.paste(donor_image, bounds) im.paste(donor_image, bounds)
return recipient_image 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

View File

@ -113,6 +113,7 @@ export declare type Metadata = SystemConfig & {
export declare type Image = { export declare type Image = {
uuid: string; uuid: string;
url: string; url: string;
thumbnail: string;
mtime: number; mtime: number;
metadata?: Metadata; metadata?: Metadata;
width: number; width: number;
@ -184,6 +185,7 @@ export declare type ImageUploadResponse = {
mtime: number; mtime: number;
width: number; width: number;
height: number; height: number;
thumbnail: string;
// bbox: [number, number, number, number]; // bbox: [number, number, number, number];
}; };

View File

@ -69,16 +69,14 @@ export const mergeAndUploadCanvas =
body: formData, body: formData,
}); });
const { url, mtime, width, height } = const image = (await response.json()) as InvokeAI.ImageUploadResponse;
(await response.json()) as InvokeAI.ImageUploadResponse;
const { url, width, height } = image;
const newImage: InvokeAI.Image = { const newImage: InvokeAI.Image = {
uuid: uuidv4(), uuid: uuidv4(),
url,
mtime,
category: shouldSaveToGallery ? 'result' : 'user', category: shouldSaveToGallery ? 'result' : 'user',
width: width, ...image,
height: height,
}; };
if (shouldDownload) { if (shouldDownload) {

View File

@ -23,13 +23,10 @@ import {
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
import * as ContextMenu from '@radix-ui/react-context-menu'; import * as ContextMenu from '@radix-ui/react-context-menu';
import { import {
resetCanvasView,
resizeAndScaleCanvas, resizeAndScaleCanvas,
setDoesCanvasNeedScaling,
setInitialCanvasImage, setInitialCanvasImage,
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import { hoverableImageSelector } from 'features/gallery/store/gallerySliceSelectors'; import { hoverableImageSelector } from 'features/gallery/store/gallerySliceSelectors';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
interface HoverableImageProps { interface HoverableImageProps {
image: InvokeAI.Image; image: InvokeAI.Image;
@ -54,7 +51,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
isLightBoxOpen, isLightBoxOpen,
} = useAppSelector(hoverableImageSelector); } = useAppSelector(hoverableImageSelector);
const { image, isSelected } = props; const { image, isSelected } = props;
const { url, uuid, metadata } = image; const { url, thumbnail, uuid, metadata } = image;
const [isHovered, setIsHovered] = useState<boolean>(false); const [isHovered, setIsHovered] = useState<boolean>(false);
@ -173,7 +170,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
className="hoverable-image-image" className="hoverable-image-image"
objectFit={galleryImageObjectFit} objectFit={galleryImageObjectFit}
rounded={'md'} rounded={'md'}
src={url} src={thumbnail || url}
loading={'lazy'} loading={'lazy'}
/> />
<div className="hoverable-image-content" onClick={handleSelectImage}> <div className="hoverable-image-content" onClick={handleSelectImage}>

View File

@ -37,16 +37,12 @@ export const uploadImage =
body: formData, body: formData,
}); });
const { url, mtime, width, height } = const image = (await response.json()) as InvokeAI.ImageUploadResponse;
(await response.json()) as InvokeAI.ImageUploadResponse; console.log(image)
const newImage: InvokeAI.Image = { const newImage: InvokeAI.Image = {
uuid: uuidv4(), uuid: uuidv4(),
url,
mtime,
category: 'user', category: 'user',
width: width, ...image,
height: height,
}; };
dispatch(addImage({ image: newImage, category: 'user' })); dispatch(addImage({ image: newImage, category: 'user' }));