From 481a0bd336fc4fa8ebf68172fbb932e0e6a2dcf1 Mon Sep 17 00:00:00 2001 From: sh570655308 <570655308@qq.com> Date: Fri, 29 Nov 2024 21:08:47 +0800 Subject: [PATCH] Resolved conflicts --- __init__.py | 23 +++ __pycache__/__init__.cpython-312.pyc | Bin 0 -> 1662 bytes __pycache__/gigapixel.cpython-312.pyc | Bin 0 -> 10026 bytes gigapixel.js | 40 +++++ gigapixel.py | 228 ++++++++++++++++++++++++++ web/js/gigapixel.js | 40 +++++ 6 files changed, 331 insertions(+) create mode 100644 __init__.py create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 __pycache__/gigapixel.cpython-312.pyc create mode 100644 gigapixel.js create mode 100644 gigapixel.py create mode 100644 web/js/gigapixel.js diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..b7d2bc2 --- /dev/null +++ b/__init__.py @@ -0,0 +1,23 @@ +from .gigapixel import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +import os +import shutil +import __main__ + +WEB_DIRECTORY = "./web" +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] + +# 确保扩展路径存在 +extensions_path = os.path.join(os.path.dirname(os.path.realpath(__main__.__file__)), "web", "extensions", "gigapixel") +if not os.path.exists(extensions_path): + os.makedirs(extensions_path) + +# 复制所有 *.js 文件到扩展路径 +js_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "web", "js") +for file in os.listdir(js_path): + if file.endswith(".js"): + src_file = os.path.join(js_path, file) + dst_file = os.path.join(extensions_path, file) + if os.path.exists(dst_file): + os.remove(dst_file) + shutil.copy(src_file, dst_file) + print('installed %s to %s' % (file, extensions_path)) diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4064712c9d5c20ff708d5b2beabd62ca4c7caa55 GIT binary patch literal 1662 zcmc&yO>7%Q6rT02z3ZRFmK~DZM_o;#JSm=Q)T>oa_S7yux8Y7qT4;HK*6oq$_J76v)ofbK zYU2}c z>0VoLDC^P;>@iAJQ&KP|ishoDilXkZE3%?Uazzm{m7-a~jI2p2W}&33#WKR2j*3bj zAZgbn1rcI!*pJwUJZMcaOapV};T+R5D_>t<%88bwLd_zDG~FyN zE64!~D^N+plSUqsbde+_=N1wXo{l# z0AC&h?=j$yLGaJeXpP6dg&Oh9280iav54CPs3mLH^15~b-5Vk_y=%r{@{(NC)^i*2LV5> IzfWH7U!osiVgLXD literal 0 HcmV?d00001 diff --git a/__pycache__/gigapixel.cpython-312.pyc b/__pycache__/gigapixel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56083b75a2c55ce11fef640a0735def0dc1c95be GIT binary patch literal 10026 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 { + const gigapixelWidget = node.widgets.find(w => w.name === "gigapixel_exe"); + if (gigapixelWidget && gigapixelWidget.value === "") { + gigapixelWidget.value = gigapixel_setting.value; + } + } + + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function () { + const r = onConfigure ? onConfigure.apply(this, arguments) : undefined; + ensureGigapixel(this); + return r; + }; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + ensureGigapixel(this); + return r; + }; + } + }, +} +app.registerExtension(ext); diff --git a/gigapixel.py b/gigapixel.py new file mode 100644 index 0000000..562d86d --- /dev/null +++ b/gigapixel.py @@ -0,0 +1,228 @@ +import numpy as np +import os +import pprint +import time +import folder_paths +import torch +import subprocess +import json + +from PIL import Image, ImageOps +from typing import Optional +import json + +class GigapixelUpscaleSettings: + @classmethod + def INPUT_TYPES(cls): + return { + 'required': { + 'enabled': (['true', 'false'], {'default': 'true'}), + 'model': ([ + 'Standard', + 'Low Resolution', + 'High Fidelity', + 'Very Compressed', + 'Art & CG', + 'Lines' + ], {'default': 'Standard'}), + 'scale': ('FLOAT', {'default': 2.0, 'min': 1, 'max': 16, 'round': False}), + 'sharpen': ('FLOAT', {'default': 1, 'min': 1, 'max': 100, 'round': False, 'display': 'Sharpen Strength'}), + 'denoise': ('FLOAT', {'default': 1, 'min': 1, 'max': 100, 'round': False, 'display': 'Denoise Strength'}), + 'compression': ('FLOAT', {'default': 67, 'min': 1, 'max': 100, 'round': False, 'display': 'Compression'}), + }, + 'optional': {}, + } + + RETURN_TYPES = ('GigapixelUpscaleSettings',) + RETURN_NAMES = ('upscale_settings',) + FUNCTION = 'init' + CATEGORY = 'image' + OUTPUT_NODE = False + OUTPUT_IS_LIST = (False,) + + def init(self, enabled, model, scale, sharpen, denoise, compression): + self.enabled = str(True).lower() == enabled.lower() + self.model = model + self.scale = scale + self.sharpen = sharpen + self.denoise = denoise + self.compression = compression + return (self,) + + +class GigapixelAI: + def __init__(self): + self.this_dir = os.path.dirname(os.path.abspath(__file__)) + self.comfy_dir = os.path.abspath(os.path.join(self.this_dir, '..', '..')) + self.subfolder = 'upscaled' + self.output_dir = os.path.join(self.comfy_dir, 'temp') + self.prefix = 'gigapixel' + + @classmethod + def INPUT_TYPES(cls): + return { + 'required': { + 'images': ('IMAGE',), + }, + 'optional': { + 'gigapixel_exe': ('STRING', { + 'default': '', + }), + 'upscale': ('GigapixelUpscaleSettings',), + }, + "hidden": {} + } + + RETURN_TYPES = ('STRING', 'STRING', 'IMAGE') + RETURN_NAMES = ('settings', 'image_paths', 'IMAGE') + FUNCTION = 'upscale_image' + CATEGORY = 'image' + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True, True, True) + + def save_image(self, img, output_dir, filename): + if not os.path.exists(output_dir): + os.makedirs(output_dir) + file_path = os.path.join(output_dir, filename) + img.save(file_path) + return file_path + + def load_image(self, image): + image_path = folder_paths.get_annotated_filepath(image) + i = Image.open(image_path) + i = ImageOps.exif_transpose(i) + image = i.convert('RGB') + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + return image + + def upscale_image(self, images, gigapixel_exe=None, + upscale: Optional[GigapixelUpscaleSettings]=None): + now_millis = int(time.time() * 1000) + prefix = '%s-%d' % (self.prefix, now_millis) + + batch_output_dir = os.path.join(self.output_dir, self.subfolder, f'batch_{now_millis}') + os.makedirs(batch_output_dir, exist_ok=True) + + upscaled_images = [] + upscale_settings = [] + upscale_image_paths = [] + + count = 0 + for image in images: + count += 1 + i = 255.0 * image.cpu().numpy() + img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + img_file = self.save_image( + img, self.output_dir, '%s-%d.png' % (prefix, count) + ) + + self.output_dir = batch_output_dir + + (settings, output_image_paths) = self.gigapixel_upscale(img_file, gigapixel_exe, upscale) + + for output_path in output_image_paths: + upscaled_image = self.load_image(output_path) + upscaled_images.append(upscaled_image) + upscale_settings.append(settings) + upscale_image_paths.append(output_path) + + return (upscale_settings, upscale_image_paths, upscaled_images) + + def gigapixel_upscale(self, img_file, gigapixel_exe=None, + upscale: Optional[GigapixelUpscaleSettings]=None): + if not os.path.exists(gigapixel_exe): + raise ValueError(f'Gigapixel AI not found: {gigapixel_exe}') + + model_mapping = { + 'Art & CG': 'art', + 'Lines': 'lines', + 'Very Compressed': 'very compressed', + 'High Fidelity': 'hf', + 'Low Resolution': 'lowres', + 'Standard': 'std', + 'Text & Shapes': 'text' + } + + target_dir = self.output_dir + os.makedirs(target_dir, exist_ok=True) + + gigapixel_args = [gigapixel_exe] + + if upscale and upscale.enabled: + gigapixel_args.extend(['--scale', str(upscale.scale)]) + + if upscale.model in model_mapping: + gigapixel_args.extend(['--model', model_mapping[upscale.model]]) + + gigapixel_args.extend(['-i', img_file]) + + gigapixel_args.extend(['-o', target_dir]) + + if upscale.denoise > 1: + gigapixel_args.extend(['--dn', str(upscale.denoise)]) + + if upscale.sharpen > 1: + gigapixel_args.extend(['--sh', str(upscale.sharpen)]) + + if upscale.compression < 100: + gigapixel_args.extend(['--cm', str(upscale.compression)]) + else: + gigapixel_args.extend([ + '--scale', '2', + '-i', img_file, + '-o', target_dir + ]) + + try: + print(f"执行命令: {' '.join(gigapixel_args)}") + + result = subprocess.run( + gigapixel_args, + capture_output=True, + text=True, + timeout=600, + check=True + ) + + print("Gigapixel running:") + print(result.stdout) + + output_images = [ + os.path.join(target_dir, f) + for f in os.listdir(target_dir) + if f.endswith(('.png', '.jpg', '.jpeg', '.tif', '.tiff')) + ] + + settings = { + 'scale': upscale.scale if upscale else 2, + 'model': upscale.model if upscale else 'Standard', + 'denoise': upscale.denoise if upscale else 1, + 'sharpen': upscale.sharpen if upscale else 1, + 'compression': upscale.compression if upscale else 67 + } + settings_json = json.dumps(settings, indent=2).replace('"', "'") + + return (settings_json, output_images) + + except subprocess.TimeoutExpired: + print("Gigapixel timeout") + raise + except subprocess.CalledProcessError as e: + print(f"Gigapixel CLI error code: {e.returncode}") + print(f"STDOUT: {e.stdout}") + print(f"STDERR: {e.stderr}") + raise + except Exception as e: + print(f"error while propcessing: {e}") + raise + +NODE_CLASS_MAPPINGS = { + 'GigapixelAI': GigapixelAI, + 'GigapixelUpscaleSettings': GigapixelUpscaleSettings, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + 'GigapixelAI': 'Gigapixel AI', + 'GigapixelUpscaleSettings': 'Gigapixel Upscale Settings', +} diff --git a/web/js/gigapixel.js b/web/js/gigapixel.js new file mode 100644 index 0000000..a81140f --- /dev/null +++ b/web/js/gigapixel.js @@ -0,0 +1,40 @@ +import { app } from "../../scripts/app.js"; + +let gigapixel_setting; +const id = "comfy.gigapixel"; +const ext = { + name: id, + async setup(app) { + gigapixel_setting = app.ui.settings.addSetting({ + id, + name: "Gigapixel AI (gigapixel.exe)", + defaultValue: "C:\\Program Files\\Topaz Labs LLC\\Topaz Gigapixel AI\\gigapixel.exe", + type: "string", + }); + }, + async beforeRegisterNodeDef(nodeType, nodeData, _app) { + if (nodeData.name === 'GigapixelAI') { + const ensureGigapixel = async (node) => { + const gigapixelWidget = node.widgets.find(w => w.name === "gigapixel_exe"); + if (gigapixelWidget && gigapixelWidget.value === "") { + gigapixelWidget.value = gigapixel_setting.value; + } + } + + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function () { + const r = onConfigure ? onConfigure.apply(this, arguments) : undefined; + ensureGigapixel(this); + return r; + }; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + ensureGigapixel(this); + return r; + }; + } + }, +} +app.registerExtension(ext);