From 19322fc1ec84a1854862c1a517fe6be90ba534fa Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 18 Nov 2022 13:35:03 +1100 Subject: [PATCH] Fixes save to gallery including empty area, adds download and copy image --- backend/invoke_ai_web_server.py | 27 +++++++-------- .../IAICanvasToolbar/IAICanvasToolbar.tsx | 25 ++++++++++++-- .../src/features/canvas/util/copyImage.ts | 34 +++++++++++++++++++ .../src/features/canvas/util/downloadFile.ts | 14 ++++++++ .../canvas/util/mergeAndUploadCanvas.ts | 33 ++++++++++++------ .../src/features/gallery/util/uploadImage.ts | 8 ----- 6 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 frontend/src/features/canvas/util/copyImage.ts create mode 100644 frontend/src/features/canvas/util/downloadFile.ts diff --git a/backend/invoke_ai_web_server.py b/backend/invoke_ai_web_server.py index 271407381d..07e76e9263 100644 --- a/backend/invoke_ai_web_server.py +++ b/backend/invoke_ai_web_server.py @@ -165,20 +165,16 @@ class InvokeAIWebServer: pil_image = Image.open(file_path) - # visible_image_bbox = pil_image.getbbox() - # pil_image = pil_image.crop(visible_image_bbox) - # pil_image.save(file_path) - # if "cropVisible" in data and data["cropVisible"] == True: - # visible_image_bbox = pil_image.getbbox() - # pil_image = pil_image.crop(visible_image_bbox) - # pil_image.save(file_path) + if "cropVisible" in data and data["cropVisible"] == True: + visible_image_bbox = pil_image.getbbox() + pil_image = pil_image.crop(visible_image_bbox) + pil_image.save(file_path) (width, height) = pil_image.size response = { "url": self.get_url_from_image_path(file_path), "mtime": mtime, - # "bbox": visible_image_bbox, "width": width, "height": height, } @@ -607,6 +603,12 @@ class InvokeAIWebServer: actual_generation_mode = generation_parameters["generation_mode"] original_bounding_box = None + + progress = Progress(generation_parameters=generation_parameters) + + self.socketio.emit("progressUpdate", progress.to_formatted_dict()) + eventlet.sleep(0) + """ TODO: If a result image is used as an init image, and then deleted, we will want to be @@ -658,8 +660,6 @@ class InvokeAIWebServer: initial_image, mask_image ) - print(initial_image, mask_image) - """ Apply the mask to the init image, creating a "mask" image with transparency where inpainting should occur. This is the kind of @@ -708,11 +708,6 @@ class InvokeAIWebServer: init_img_path = self.get_image_path_from_url(init_img_url) generation_parameters["init_img"] = init_img_path - progress = Progress(generation_parameters=generation_parameters) - - self.socketio.emit("progressUpdate", progress.to_formatted_dict()) - eventlet.sleep(0) - def image_progress(sample, step): if self.canceled.is_set(): raise CanceledException @@ -967,6 +962,8 @@ class InvokeAIWebServer: eventlet.sleep(0) progress.set_current_iteration(progress.current_iteration + 1) + + print(generation_parameters) self.generate.prompt2image( **generation_parameters, diff --git a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx index a020a694c7..f175fed9f5 100644 --- a/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx +++ b/frontend/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx @@ -78,7 +78,6 @@ const IAICanvasOutpaintingControls = () => { dispatch( mergeAndUploadCanvas({ canvasImageLayerRef, - saveToGallery: false, }) ); }} @@ -89,7 +88,11 @@ const IAICanvasOutpaintingControls = () => { icon={} onClick={() => { dispatch( - mergeAndUploadCanvas({ canvasImageLayerRef, saveToGallery: true }) + mergeAndUploadCanvas({ + canvasImageLayerRef, + cropVisible: true, + saveToGallery: true, + }) ); }} /> @@ -97,11 +100,29 @@ const IAICanvasOutpaintingControls = () => { aria-label="Copy Selection" tooltip="Copy Selection" icon={} + onClick={() => { + dispatch( + mergeAndUploadCanvas({ + canvasImageLayerRef, + cropVisible: true, + copyAfterSaving: true, + }) + ); + }} /> } + onClick={() => { + dispatch( + mergeAndUploadCanvas({ + canvasImageLayerRef, + cropVisible: true, + downloadAfterSaving: true, + }) + ); + }} /> diff --git a/frontend/src/features/canvas/util/copyImage.ts b/frontend/src/features/canvas/util/copyImage.ts new file mode 100644 index 0000000000..06abbe47fe --- /dev/null +++ b/frontend/src/features/canvas/util/copyImage.ts @@ -0,0 +1,34 @@ +/** + * Copies an image to the clipboard by drawing it to a canvas and then + * calling toBlob() on the canvas. + */ +const copyImage = (url: string, width: number, height: number) => { + const imageElement = document.createElement('img'); + + imageElement.addEventListener('load', () => { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const context = canvas.getContext('2d'); + + if (!context) return; + + context.drawImage(imageElement, 0, 0); + + canvas.toBlob((blob) => { + blob && + navigator.clipboard.write([ + new ClipboardItem({ + [blob.type]: blob, + }), + ]); + }); + + canvas.remove(); + imageElement.remove(); + }); + + imageElement.src = url; +}; + +export default copyImage; diff --git a/frontend/src/features/canvas/util/downloadFile.ts b/frontend/src/features/canvas/util/downloadFile.ts new file mode 100644 index 0000000000..abb902d019 --- /dev/null +++ b/frontend/src/features/canvas/util/downloadFile.ts @@ -0,0 +1,14 @@ +/** + * Downloads a file, given its URL. + */ +const downloadFile = (url: string) => { + const a = document.createElement('a'); + a.href = url; + a.download = ''; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + a.remove(); +}; + +export default downloadFile; diff --git a/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts b/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts index 76ad6e4e41..fa6cc1d27b 100644 --- a/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts +++ b/frontend/src/features/canvas/util/mergeAndUploadCanvas.ts @@ -5,17 +5,28 @@ import { MutableRefObject } from 'react'; import * as InvokeAI from 'app/invokeai'; import { v4 as uuidv4 } from 'uuid'; import layerToDataURL from './layerToDataURL'; +import downloadFile from './downloadFile'; +import copyImage from './copyImage'; export const mergeAndUploadCanvas = createAsyncThunk( 'canvas/mergeAndUploadCanvas', async ( args: { canvasImageLayerRef: MutableRefObject; - saveToGallery: boolean; + cropVisible?: boolean; + saveToGallery?: boolean; + downloadAfterSaving?: boolean; + copyAfterSaving?: boolean; }, thunkAPI ) => { - const { canvasImageLayerRef, saveToGallery } = args; + const { + canvasImageLayerRef, + saveToGallery, + downloadAfterSaving, + cropVisible, + copyAfterSaving, + } = args; const { getState } = thunkAPI; @@ -40,7 +51,7 @@ export const mergeAndUploadCanvas = createAsyncThunk( dataURL, filename: 'merged_canvas.png', kind: saveToGallery ? 'result' : 'temp', - cropVisible: saveToGallery, + cropVisible, }) ); @@ -52,12 +63,15 @@ export const mergeAndUploadCanvas = createAsyncThunk( const { url, mtime, width, height } = (await response.json()) as InvokeAI.ImageUploadResponse; - // const newBoundingBox = { - // x: bbox[0], - // y: bbox[1], - // width: bbox[2], - // height: bbox[3], - // }; + if (downloadAfterSaving) { + downloadFile(url); + return; + } + + if (copyAfterSaving) { + copyImage(url, width, height); + return; + } const newImage: InvokeAI.Image = { uuid: uuidv4(), @@ -72,7 +86,6 @@ export const mergeAndUploadCanvas = createAsyncThunk( image: newImage, kind: saveToGallery ? 'merged_canvas' : 'temp_merged_canvas', originalBoundingBox, - // newBoundingBox, }; } ); diff --git a/frontend/src/features/gallery/util/uploadImage.ts b/frontend/src/features/gallery/util/uploadImage.ts index 5dca9a490d..95aba77f7c 100644 --- a/frontend/src/features/gallery/util/uploadImage.ts +++ b/frontend/src/features/gallery/util/uploadImage.ts @@ -29,7 +29,6 @@ export const uploadImage = createAsyncThunk( kind: 'init', }) ); - // formData.append('kind', 'init'); const response = await fetch(window.location.origin + '/upload', { method: 'POST', @@ -39,13 +38,6 @@ export const uploadImage = createAsyncThunk( const { url, mtime, width, height } = (await response.json()) as InvokeAI.ImageUploadResponse; - // const newBoundingBox = { - // x: bbox[0], - // y: bbox[1], - // width: bbox[2], - // height: bbox[3], - // }; - const newImage: InvokeAI.Image = { uuid: uuidv4(), url,