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
|
(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
|
||||||
|
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 = {
|
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];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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}>
|
||||||
|
@ -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' }));
|
||||||
|
Loading…
Reference in New Issue
Block a user