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,