mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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:
parent
a3308c853d
commit
50a67a7172
@ -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
|
||||
|
2
frontend/src/app/invokeai.d.ts
vendored
2
frontend/src/app/invokeai.d.ts
vendored
@ -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];
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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<boolean>(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'}
|
||||
/>
|
||||
<div className="hoverable-image-content" onClick={handleSelectImage}>
|
||||
|
@ -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' }));
|
||||
|
Loading…
Reference in New Issue
Block a user