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:
psychedelicious
2024-09-19 17:56:54 +10:00
parent 641a61171e
commit bd028acdae
4 changed files with 44 additions and 13 deletions

View File

@@ -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()) {

View File

@@ -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]
);

View File

@@ -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;
};

View File

@@ -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();