mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-18 09:22:20 -05:00
feat(ui): support embedding metadata when rasterizing composite layer
- Allow `uploadImage` util to accept `metadata` to embed in the image - Update compositor to support `metadata` field when uploading rasterized composite layer
This commit is contained in:
@@ -30,7 +30,7 @@ export const CanvasEntityMergeVisibleButton = memo(({ type }: Props) => {
|
||||
if (type === 'raster_layer') {
|
||||
const rect = canvasManager.stage.getVisibleRect('raster_layer');
|
||||
const result = await withResultAsync(() =>
|
||||
canvasManager.compositor.rasterizeAndUploadCompositeRasterLayer(rect, false)
|
||||
canvasManager.compositor.rasterizeAndUploadCompositeRasterLayer(rect, { is_intermediate: true })
|
||||
);
|
||||
|
||||
if (result.isOk()) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { withResultAsync } from 'common/util/result';
|
||||
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
rgAdded,
|
||||
rgIPAdapterImageChanged,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectCanvasMetadata } from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
CanvasControlLayerState,
|
||||
CanvasEntityIdentifier,
|
||||
@@ -38,10 +40,12 @@ type UseSaveCanvasArg = {
|
||||
toastOk: string;
|
||||
toastError: string;
|
||||
onSave?: (imageDTO: ImageDTO, rect: Rect) => void;
|
||||
withMetadata?: boolean;
|
||||
};
|
||||
|
||||
const useSaveCanvas = ({ region, saveToGallery, toastOk, toastError, onSave }: UseSaveCanvasArg) => {
|
||||
const useSaveCanvas = ({ region, saveToGallery, toastOk, toastError, onSave, withMetadata }: UseSaveCanvasArg) => {
|
||||
const { t } = useTranslation();
|
||||
const store = useAppStore();
|
||||
|
||||
const canvasManager = useCanvasManager();
|
||||
|
||||
@@ -58,8 +62,17 @@ const useSaveCanvas = ({ region, saveToGallery, toastOk, toastError, onSave }: U
|
||||
return;
|
||||
}
|
||||
|
||||
let metadata: SerializableObject | undefined = undefined;
|
||||
|
||||
if (withMetadata) {
|
||||
metadata = selectCanvasMetadata(store.getState());
|
||||
}
|
||||
|
||||
const result = await withResultAsync(() =>
|
||||
canvasManager.compositor.rasterizeAndUploadCompositeRasterLayer(rect, saveToGallery)
|
||||
canvasManager.compositor.rasterizeAndUploadCompositeRasterLayer(rect, {
|
||||
is_intermediate: !saveToGallery,
|
||||
metadata,
|
||||
})
|
||||
);
|
||||
|
||||
if (result.isOk()) {
|
||||
@@ -78,9 +91,11 @@ const useSaveCanvas = ({ region, saveToGallery, toastOk, toastError, onSave }: U
|
||||
onSave,
|
||||
region,
|
||||
saveToGallery,
|
||||
store,
|
||||
t,
|
||||
toastError,
|
||||
toastOk,
|
||||
withMetadata,
|
||||
]);
|
||||
|
||||
return saveCanvas;
|
||||
@@ -94,6 +109,7 @@ export const useSaveCanvasToGallery = () => {
|
||||
saveToGallery: true,
|
||||
toastOk: t('controlLayers.savedToGalleryOk'),
|
||||
toastError: t('controlLayers.savedToGalleryError'),
|
||||
withMetadata: true,
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
@@ -109,6 +125,7 @@ export const useSaveBboxToGallery = () => {
|
||||
saveToGallery: true,
|
||||
toastOk: t('controlLayers.savedToGalleryOk'),
|
||||
toastError: t('controlLayers.savedToGalleryError'),
|
||||
withMetadata: true,
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { GenerationMode, Rect } from 'features/controlLayers/store/types';
|
||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||
import { atom, computed } from 'nanostores';
|
||||
import type { Logger } from 'roarr';
|
||||
import type { UploadOptions } from 'services/api/endpoints/images';
|
||||
import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import stableHash from 'stable-hash';
|
||||
@@ -149,10 +150,13 @@ export class CanvasCompositorModule extends CanvasModuleBase {
|
||||
* If the hash of the composite raster layer is found in the cache, the cached image DTO is returned.
|
||||
*
|
||||
* @param rect The region to include in the rasterized image
|
||||
* @param saveToGallery Whether to save the image to the gallery or just return the uploaded image DTO
|
||||
* @param options Options for uploading the image
|
||||
* @returns A promise that resolves to the uploaded image DTO
|
||||
*/
|
||||
rasterizeAndUploadCompositeRasterLayer = async (rect: Rect, saveToGallery: boolean): Promise<ImageDTO> => {
|
||||
rasterizeAndUploadCompositeRasterLayer = async (
|
||||
rect: Rect,
|
||||
options: Pick<UploadOptions, 'is_intermediate' | 'metadata'>
|
||||
): Promise<ImageDTO> => {
|
||||
this.log.trace({ rect }, 'Rasterizing composite raster layer');
|
||||
|
||||
assert(rect.width > 0 && rect.height > 0, 'Unable to rasterize empty rect');
|
||||
@@ -178,8 +182,9 @@ export class CanvasCompositorModule extends CanvasModuleBase {
|
||||
blob,
|
||||
fileName: 'composite-raster-layer.png',
|
||||
image_category: 'general',
|
||||
is_intermediate: !saveToGallery,
|
||||
board_id: saveToGallery ? selectAutoAddBoardId(this.manager.store.getState()) : undefined,
|
||||
is_intermediate: options.is_intermediate,
|
||||
board_id: options.is_intermediate ? undefined : selectAutoAddBoardId(this.manager.store.getState()),
|
||||
metadata: options.metadata,
|
||||
})
|
||||
);
|
||||
this.$isUploading.set(false);
|
||||
@@ -212,7 +217,7 @@ export class CanvasCompositorModule extends CanvasModuleBase {
|
||||
}
|
||||
}
|
||||
|
||||
imageDTO = await this.rasterizeAndUploadCompositeRasterLayer(rect, false);
|
||||
imageDTO = await this.rasterizeAndUploadCompositeRasterLayer(rect, { is_intermediate: true });
|
||||
this.manager.cache.imageNameCache.set(hash, imageDTO.image_name);
|
||||
return imageDTO;
|
||||
};
|
||||
|
||||
@@ -587,20 +587,29 @@ export const getImageDTO = async (image_name: string, forceRefetch?: boolean): P
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadImage = async (arg: {
|
||||
export type UploadOptions = {
|
||||
blob: Blob;
|
||||
fileName: string;
|
||||
image_category: ImageCategory;
|
||||
is_intermediate: boolean;
|
||||
crop_visible?: boolean;
|
||||
board_id?: BoardId;
|
||||
}): Promise<ImageDTO> => {
|
||||
const { blob, fileName, image_category, is_intermediate, crop_visible = false, board_id } = arg;
|
||||
metadata?: SerializableObject;
|
||||
};
|
||||
export const uploadImage = async (arg: UploadOptions): Promise<ImageDTO> => {
|
||||
const { blob, fileName, image_category, is_intermediate, crop_visible = false, board_id, metadata } = arg;
|
||||
|
||||
const { dispatch } = getStore();
|
||||
const file = new File([blob], fileName, { type: 'image/png' });
|
||||
const req = dispatch(
|
||||
imagesApi.endpoints.uploadImage.initiate({ file, image_category, is_intermediate, crop_visible, board_id })
|
||||
imagesApi.endpoints.uploadImage.initiate({
|
||||
file,
|
||||
image_category,
|
||||
is_intermediate,
|
||||
crop_visible,
|
||||
board_id,
|
||||
metadata,
|
||||
})
|
||||
);
|
||||
req.reset();
|
||||
return await req.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user