model2 commited on
Commit
1c237e7
·
1 Parent(s): 211faaf

image sizing

Browse files
app.py CHANGED
@@ -7,10 +7,12 @@ import sys
7
  from typing import Any, Mapping, Sequence, Union
8
 
9
  import gradio as gr
 
10
  import spaces
11
  import torch
12
  import yaml
13
  from huggingface_hub import hf_hub_download
 
14
 
15
  import folder_paths
16
  from nodes import NODE_CLASS_MAPPINGS
@@ -56,6 +58,7 @@ hf_hub_download(
56
  # ReActor has its own special snowflake installation
57
  os.system("cd custom_nodes/ComfyUI-ReActor && python install.py")
58
 
 
59
  def import_custom_nodes() -> None:
60
  """Find all custom nodes in the custom_nodes folder and add those node objects to NODE_CLASS_MAPPINGS
61
 
@@ -204,14 +207,16 @@ def add_extra_model_paths() -> None:
204
  add_comfyui_directory_to_sys_path()
205
  add_extra_model_paths()
206
 
 
207
  @spaces.GPU(duration=60)
208
  def advance_blur(input_image):
209
  with torch.inference_mode():
 
210
  loaded_input_image = loadimage.load_image(
211
  image=input_image,
212
  )
213
 
214
- image_size = getimagesize.execute(
215
  image=get_value_at_index(loaded_input_image, 0),
216
  )
217
  original_width = get_value_at_index(image_size, 0)
@@ -258,14 +263,14 @@ def advance_blur(input_image):
258
  image=get_value_at_index(upscaled_image, 0),
259
  )
260
 
261
- saved_image = local_save.process_images(
262
- prefix="advance_blur",
263
- file_format="JPEG",
264
- images=get_value_at_index(final_image, 0),
265
  )
266
-
267
- saved_path = f"output/{saved_image['ui']['images'][0]['filename']}"
268
- return saved_path
269
 
270
 
271
  if __name__ == "__main__":
 
7
  from typing import Any, Mapping, Sequence, Union
8
 
9
  import gradio as gr
10
+ import numpy as np
11
  import spaces
12
  import torch
13
  import yaml
14
  from huggingface_hub import hf_hub_download
15
+ from PIL import Image
16
 
17
  import folder_paths
18
  from nodes import NODE_CLASS_MAPPINGS
 
58
  # ReActor has its own special snowflake installation
59
  os.system("cd custom_nodes/ComfyUI-ReActor && python install.py")
60
 
61
+
62
  def import_custom_nodes() -> None:
63
  """Find all custom nodes in the custom_nodes folder and add those node objects to NODE_CLASS_MAPPINGS
64
 
 
207
  add_comfyui_directory_to_sys_path()
208
  add_extra_model_paths()
209
 
210
+
211
  @spaces.GPU(duration=60)
212
  def advance_blur(input_image):
213
  with torch.inference_mode():
214
+ image_file_name = os.path.splitext(os.path.basename(input_image))[0]
215
  loaded_input_image = loadimage.load_image(
216
  image=input_image,
217
  )
218
 
219
+ image_size = getimagesize.execute(
220
  image=get_value_at_index(loaded_input_image, 0),
221
  )
222
  original_width = get_value_at_index(image_size, 0)
 
263
  image=get_value_at_index(upscaled_image, 0),
264
  )
265
 
266
+ img = Image.fromarray(
267
+ np.clip(
268
+ 255.0 & get_value_at_index(final_image, 0).cpu().numpy(), 0, 255
269
+ ).astype(np.uint8)
270
  )
271
+ outpath = f"output/{image_file_name}-advance-blurred.jpg"
272
+ img.save(outpath, quality=80, dpi=(72, 72))
273
+ return outpath
274
 
275
 
276
  if __name__ == "__main__":
custom_nodes/comfyui-saveasjpeg/README.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ComfyUI-Saveasjpeg
2
+ Save a picture as jpeg file in Comfy + Workflow loading
3
+
4
+ ## Warning:
5
+
6
+ I'm a novice at best at coding and some of the code is pretty hacky, so this can definitely break.
7
+
8
+ Also, jpeg only supports files up to 16383 x 16383.
9
+
10
+ ### Known issues:
11
+
12
+ Import of jpegfiles breaks if import a workflow that has }Prompt:{ in a Node that has dynamic wildcards disabled.
13
+
14
+
15
+ Node doesn't resize on Save - image is in there, just needs to be resized to be visible.
16
+
17
+ ## Description:
18
+
19
+ This adds a custom node to save a picture as a jpeg File and also adds a script to Comfy to drag and drop generated jpegfiles into the UI to load the workflow.
20
+
21
+ I've added a compression slider and a lossy/lossless option. The compression slider is a bit misleading.
22
+
23
+ In lossless mode, it only affects the "effort" taken to compress where 100 is the smallest possible size and 1 is the biggest possible size, it's a tradeoff for saving speed.
24
+
25
+ In lossy mode, that's the other way around, where 100 is the biggest possible size with the least compression and 1 is the smallest possible size with maximum compression.
26
+
27
+ On default it's set to lossy with a compression of 80, below are examples for that.
28
+
29
+
30
+
31
+ ## Installation:
32
+
33
+ Use git clone https://github.com/aureagle/ComfyUI-Saveasjpeg in your ComfyUI custom nodes directory
34
+
35
+ ## Examples:
36
+
37
+ Lossless with compression set to 100, 17069KB
38
+ https://postimg.cc/2bPkkFN9
39
+
40
+ Lossless with compression set to 10, 17059KB
41
+ https://postimg.cc/bZTwxt1d
42
+
43
+
44
+ Lossy with compression set to 100 , 5384 KB
45
+ https://postimg.cc/WFDpcYCJ
46
+
47
+
48
+ Lossy with compression set to 10, 174 KB
49
+ https://postimg.cc/VSkLDkFg
50
+
51
+
52
+ Lossy with compression set to 80 ( Default ) , 935 KB
53
+ https://postimg.cc/3yfr6QST
custom_nodes/comfyui-saveasjpeg/Save_as_jpeg.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from PIL import Image
3
+ import folder_paths
4
+ import os
5
+ import json
6
+
7
+ # by Kaharos94
8
+ # https://github.com/Kaharos94/ComfyUI-Saveasjpeg
9
+ # comfyUI node to save an image in jpeg format
10
+
11
+ class Save_as_jpeg:
12
+ def __init__(self):
13
+ self.output_dir = folder_paths.get_output_directory()
14
+ self.type = "output"
15
+
16
+ @classmethod
17
+ def INPUT_TYPES(s):
18
+ return {"required":
19
+ {"images": ("IMAGE", ),
20
+ "filename_prefix": ("STRING", {"default": "ComfyUI"}),
21
+ "savePrompt":(["yes","no"],),
22
+ "compression":("INT", {"default": 80, "min": 1, "max": 100, "step": 1},)},
23
+ "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "mode":"lossy" },
24
+ }
25
+
26
+ RETURN_TYPES = ()
27
+ FUNCTION = "Save_as_jpeg"
28
+
29
+ OUTPUT_NODE = True
30
+
31
+ CATEGORY = "image"
32
+
33
+ def Save_as_jpeg(self, savePrompt, compression, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None , mode="lossy"):
34
+ def map_filename(filename):
35
+ prefix_len = len(os.path.basename(filename_prefix))
36
+ prefix = filename[:prefix_len + 1]
37
+ try:
38
+ digits = int(filename[prefix_len + 1:].split('_')[0])
39
+ except:
40
+ digits = 0
41
+ return (digits, prefix)
42
+
43
+ def compute_vars(input):
44
+ input = input.replace("%width%", str(images[0].shape[1]))
45
+ input = input.replace("%height%", str(images[0].shape[0]))
46
+ return input
47
+
48
+ filename_prefix = compute_vars(filename_prefix)
49
+
50
+ subfolder = os.path.dirname(os.path.normpath(filename_prefix))
51
+ filename = os.path.basename(os.path.normpath(filename_prefix))
52
+
53
+ full_output_folder = os.path.join(self.output_dir, subfolder)
54
+
55
+ if os.path.commonpath((self.output_dir, os.path.abspath(full_output_folder))) != self.output_dir:
56
+ print("Saving image outside the output folder is not allowed.")
57
+ return {}
58
+
59
+ try:
60
+ counter = max(filter(lambda a: a[1][:-1] == filename and a[1][-1] == "_", map(map_filename, os.listdir(full_output_folder))))[0] + 1
61
+ except ValueError:
62
+ counter = 1
63
+ except FileNotFoundError:
64
+ os.makedirs(full_output_folder, exist_ok=True)
65
+ counter = 1
66
+
67
+ results = list()
68
+ for image in images:
69
+ i = 255. * image.cpu().numpy()
70
+ img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
71
+ workflowmetadata = str()
72
+ promptstr = str()
73
+ imgexif = img.getexif() #get the (empty) Exif data of the generated Picture
74
+
75
+
76
+ if prompt is not None:
77
+ promptstr="".join(json.dumps(prompt)) #prepare prompt String
78
+ if savePrompt == "yes":
79
+ imgexif[0x010f] ="Prompt:"+ promptstr #Add PromptString to EXIF position 0x010f (Exif.Image.Make)
80
+ if extra_pnginfo is not None:
81
+ for x in extra_pnginfo:
82
+ workflowmetadata += "".join(json.dumps(extra_pnginfo[x]))
83
+ if savePrompt == "yes":
84
+ imgexif[0x010e] = "Workflow:"+ workflowmetadata #Add Workflowstring to EXIF position 0x010e (Exif.Image.ImageDescription)
85
+ file = f"{filename}_{counter:05}_.jpeg"
86
+ if mode =="lossless":
87
+ boolloss = True
88
+ if mode =="lossy":
89
+ boolloss = False
90
+
91
+
92
+ img.save(os.path.join(full_output_folder, file), method=6 , exif= imgexif, lossless=boolloss , quality=compression) #Save as jpeg - options to be determined
93
+ results.append({
94
+ "filename": file,
95
+ "subfolder": subfolder,
96
+ "type": self.type
97
+ });
98
+ counter += 1
99
+
100
+ return { "ui": { "images": results } }
101
+ NODE_CLASS_MAPPINGS = {
102
+ "Save_as_jpeg": Save_as_jpeg
103
+ }
custom_nodes/comfyui-saveasjpeg/__init__.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import shutil
2
+ import folder_paths
3
+ import os, sys, subprocess
4
+ import filecmp
5
+
6
+
7
+
8
+ print("### Loading: Save as Jpeg")
9
+
10
+ comfy_path = os.path.dirname(folder_paths.__file__)
11
+
12
+ def setup_js():
13
+ jpeg_path = os.path.dirname(__file__)
14
+ js_dest_path = os.path.join(comfy_path, "web", "extensions", "jpeginfo")
15
+ js_src_path = os.path.join(jpeg_path, "jpeginfo", "jpeginfo.js")
16
+
17
+ ## Creating folder if it's not present, then Copy.
18
+ print("Copying JS files for Workflow loading")
19
+ if (os.path.isdir(js_dest_path)==False):
20
+ os.mkdir(js_dest_path)
21
+ shutil.copy(js_src_path, js_dest_path)
22
+ else:
23
+ shutil.copy(js_src_path, js_dest_path)
24
+
25
+
26
+
27
+ setup_js()
28
+
29
+ from .Save_as_jpeg import NODE_CLASS_MAPPINGS
30
+
31
+ __all__ = ['NODE_CLASS_MAPPINGS']
custom_nodes/comfyui-saveasjpeg/jpeginfo/jpeginfo.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "/scripts/app.js";
2
+ app.registerExtension({
3
+ name: "kaharos.jpegsave",
4
+ setup(app,file){
5
+
6
+ async function getjpegExifData(jpegFile) {
7
+ const reader = new FileReader();
8
+ reader.readAsArrayBuffer(jpegFile);
9
+
10
+ return new Promise((resolve, reject) => {
11
+ reader.onloadend = function() {
12
+ const buffer = reader.result;
13
+ const view = new DataView(buffer);
14
+ let offset = 0;
15
+
16
+ // Search for the "EXIF" tag
17
+ while (offset < view.byteLength - 4) {
18
+ if (view.getUint32(offset, true) === 0x46495845 /* "EXIF" in big-endian */) {
19
+ const exifOffset = offset + 6;
20
+ const exifData = buffer.slice(exifOffset);
21
+ const exifString = new TextDecoder().decode(exifData).replaceAll(String.fromCharCode(0), ''); //Remove Null Terminators from string
22
+ let exifJsonString = exifString.slice(exifString.indexOf("Workflow")); //find beginning of Workflow Exif Tag
23
+ let promptregex="(?<!\{)}Prompt:{(?![\w\s]*[\}])"; //Regex to split }Prompt:{ // Hacky as fuck - theoretically if somebody has a text encode with dynamic prompts turned off, they could enter }Prompt:{ which breaks this
24
+ let exifJsonStringMap = new Map([
25
+
26
+ ["workflow",exifJsonString.slice(9,exifJsonString.search(promptregex)+1)], // Remove "Workflow:" keyword in front of the JSON workflow data passed
27
+ ["prompt",exifJsonString.substring((exifJsonString.search(promptregex)+8))] //Find and remove "Prompt:" keyword in front of the JSON prompt data
28
+
29
+ ]);
30
+ let fullJson=Object.fromEntries(exifJsonStringMap); //object to pass back
31
+
32
+ resolve(fullJson);
33
+
34
+ }
35
+
36
+ offset++;
37
+ }
38
+
39
+ reject(new Error('EXIF metadata not found'));
40
+ }})};
41
+
42
+
43
+
44
+ const handleFile = app.handleFile;
45
+ app.handleFile = async function(file) { // Add the 'file' parameter to the function definition
46
+ if (file.type === "image/jpeg") {
47
+
48
+ const jpegInfo =await getjpegExifData(file);
49
+ if (jpegInfo) {
50
+ if (jpegInfo.workflow) {
51
+ if(app.load_workflow_with_components) {
52
+ app.load_workflow_with_components(jpegInfo.workflow);
53
+ }
54
+ else
55
+ this.loadGraphData(JSON.parse(jpegInfo.workflow));
56
+ }
57
+ }
58
+ } else {
59
+ return handleFile.apply(this, arguments);
60
+ }
61
+ }
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+ },});