diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 801e1ec..b79bf8e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -ko_fi: haoming \ No newline at end of file +ko_fi: haoming diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d5e54..1d46db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### v2.2.4 - 2024 Aug.28 +- Optimization *(`internal`)* +- Improve Color **Accuracy** ~~Slightly~~ + ### v2.2.3 - 2024 Aug.27 - Lib *(`internal`)* diff --git a/README.md b/README.md index a173aa9..bb37322 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ allowing you to adjust the brightness, contrast, and color of the generations. After installing this Extension, you will see a new section in both **txt2img** and **img2img** tabs. Refer to the parameters and sample images below and play around with the values. -**Note:** Since this modifies the underlying latent noise, the composition may change drastically. +> **Note:** Since this modifies the underlying latent noise, the composition may change drastically #### Parameters -- **Enable:** Turn on/off this Extension +- **Enable:** Turn on / off this Extension - **Alt:** Modify an alternative Tensor instead, causing the effects to be significantly stronger - **Brightness:** Adjust the overall brightness of the image - **Contrast:** Adjust the overall contrast of the image diff --git a/javascript/vec_cc.js b/javascript/vec_cc.js index 1cb11c5..c13b867 100644 --- a/javascript/vec_cc.js +++ b/javascript/vec_cc.js @@ -1,7 +1,11 @@ class VectorscopeCC { - static dot = { 'txt': null, 'img': null }; + static dot = { 'txt': undefined, 'img': undefined }; + /** + * @param {number} r @param {number} g @param {number} b + * @param {string} mode "txt" | "img" + */ static updateCursor(r, g, b, mode) { const mag = Math.abs(r) + Math.abs(g) + Math.abs(b); var condX, condY; @@ -18,6 +22,11 @@ class VectorscopeCC { this.dot[mode].style.top = `calc(50% + ${condY - 12}px)`; } + /** + * @param {HTMLImageElement} wheel + * @param {HTMLInputElement[]} sliders + * @param {HTMLImageElement} dot + */ static registerPicker(wheel, sliders, dot) { ['mousemove', 'click'].forEach((event) => { wheel.addEventListener(event, (e) => { @@ -35,20 +44,7 @@ class VectorscopeCC { const x = ((e.clientX - rect.left) - 100.0) / 25; const y = ((e.clientY - rect.top) - 100.0) / 25; - const zeta = Math.atan(y / x); - var degree = 0; - - if (x >= 0) { - if (y >= 0) - degree = zeta * 180 / Math.PI; - else - degree = 360 + zeta * 180 / Math.PI; - } - else if (x < 0) { - degree = 180 + zeta * 180 / Math.PI; - } - - var r = -(0.00077 * (433 * x * degree + 750 * y * degree) / degree); + var r = -0.077 * (4.33 * x + 7.5 * y); var g = y / 0.866 + r; var b = x + 0.5 * r + 0.5 * g; diff --git a/lib_cc/callback.py b/lib_cc/callback.py index 17709ae..85ee383 100644 --- a/lib_cc/callback.py +++ b/lib_cc/callback.py @@ -1,5 +1,5 @@ from modules.sd_samplers_kdiffusion import KDiffusionSampler -from modules import script_callbacks, devices +from modules.script_callbacks import on_script_unloaded from functools import wraps from random import random import torch @@ -45,19 +45,19 @@ class NoiseMethods: """ noise = NoiseMethods.zeros(latent) if use_zero else NoiseMethods.ones(latent) - batchSize, c, w, h = noise.shape + b, c, w, h = noise.shape - device = devices.get_optimal_device() + device = latent.device upsampler = torch.nn.Upsample(size=(w, h), mode="bilinear").to(device) - for b in range(batchSize): + for batch in range(b): for i in range(iterations): r = random() * 2 + 2 wn = max(1, int(w / (r**i))) hn = max(1, int(h / (r**i))) - noise[b] += ( + noise[batch] += ( upsampler(torch.randn(1, c, hn, wn).to(device)) * discount**i )[0] @@ -67,10 +67,10 @@ class NoiseMethods: return noise / noise.std() -def RGB_2_CbCr(r: float, g: float, b: float) -> float: +def RGB_2_CbCr(r: float, g: float, b: float) -> tuple[float, float]: """Convert RGB channels into YCbCr for SDXL""" - cb = -0.15 * r - 0.29 * g + 0.44 * b - cr = 0.44 * r - 0.37 * g - 0.07 * b + cb = -0.17 * r - 0.33 * g + 0.5 * b + cr = 0.5 * r - 0.41 * g - 0.08 * b return cb, cr @@ -94,7 +94,8 @@ def cc_callback(self, d): mode = str(self.vec_cc["mode"]) method = str(self.vec_cc["method"]) - source = d[mode] + source: torch.Tensor = d[mode] + target: torch.Tensor = None if "Straight" in method: target = d[mode].detach().clone() @@ -175,4 +176,4 @@ def restore_callback(): KDiffusionSampler.callback_state = original_callback -script_callbacks.on_script_unloaded(restore_callback) +on_script_unloaded(restore_callback) diff --git a/lib_cc/colorpicker.py b/lib_cc/colorpicker.py index 1a0cccb..4eafd23 100644 --- a/lib_cc/colorpicker.py +++ b/lib_cc/colorpicker.py @@ -7,17 +7,17 @@ DOT = os.path.join(scripts.basedir(), "scripts", "dot.png") def create_colorpicker(is_img: bool): - m = "img" if is_img else "txt" + m: str = "img" if is_img else "txt" gr.Image( - WHEEL, + value=WHEEL, interactive=False, container=False, elem_id=f"cc-colorwheel-{m}", ) gr.Image( - DOT, + value=DOT, interactive=False, container=False, elem_id=f"cc-temp-{m}", diff --git a/lib_cc/style.py b/lib_cc/style.py index 8666b8d..e7adc28 100644 --- a/lib_cc/style.py +++ b/lib_cc/style.py @@ -11,7 +11,7 @@ EMPTY_STYLE = {"styles": {}, "deleted": {}} class StyleManager: def __init__(self): - self.STYLE_SHEET = None + self.STYLE_SHEET: dict = None def load_styles(self): if os.path.isfile(STYLE_FILE): @@ -27,10 +27,10 @@ class StyleManager: return self.list_style() - def list_style(self): + def list_style(self) -> list[str]: return list(self.STYLE_SHEET["styles"].keys()) - def get_style(self, style_name: str): + def get_style(self, style_name: str) -> tuple[bool | str | float]: style: dict = self.STYLE_SHEET["styles"].get(style_name, None) if not style: diff --git a/lib_cc/xyz.py b/lib_cc/xyz.py index 2a05269..90560af 100644 --- a/lib_cc/xyz.py +++ b/lib_cc/xyz.py @@ -13,6 +13,7 @@ def grid_reference(): def xyz_support(cache: dict): + def apply_field(field): def _(p, x, xs): cache.update({field: x}) diff --git a/scripts/Vectorscope.png b/scripts/Vectorscope.png deleted file mode 100644 index 1a8a45f..0000000 Binary files a/scripts/Vectorscope.png and /dev/null differ diff --git a/scripts/cc.py b/scripts/cc.py index 94b1c5b..2303125 100644 --- a/scripts/cc.py +++ b/scripts/cc.py @@ -11,7 +11,7 @@ import gradio as gr import lib_cc -VERSION = "v2.2.3" +VERSION = "2.2.4" style_manager = StyleManager() @@ -19,6 +19,7 @@ style_manager.load_styles() class VectorscopeCC(scripts.Script): + def __init__(self): self.xyzCache = {} xyz_support(self.xyzCache) @@ -30,11 +31,11 @@ class VectorscopeCC(scripts.Script): return scripts.AlwaysVisible def ui(self, is_img2img): - mode = "img" if is_img2img else "txt" - m = f'"{mode}"' + mode: str = "img" if is_img2img else "txt" + m: str = f'"{mode}"' with gr.Accordion( - f"Vectorscope CC {VERSION}", elem_id=f"vec-cc-{mode}", open=False + f"Vectorscope CC v{VERSION}", elem_id=f"vec-cc-{mode}", open=False ): with gr.Row(): @@ -151,13 +152,14 @@ class VectorscopeCC(scripts.Script): label="Noise Settings", value="Straight Abs.", ) + scaling = gr.Radio( ["Flat", "Cos", "Sin", "1 - Cos", "1 - Sin"], label="Scaling Settings", value="Flat", ) - comps = ( + comps: tuple[gr.components.Component] = ( latent, bri, con, @@ -297,49 +299,40 @@ class VectorscopeCC(scripts.Script): enable = self.xyzCache["Enable"].lower().strip() == "true" if not enable: - if "Enable" not in self.xyzCache.keys(): - if len(self.xyzCache) > 0: + if len(self.xyzCache) > 0: + if "Enable" not in self.xyzCache.keys(): print("\n[Vec.CC] x [X/Y/Z Plot] Extension is not Enabled!\n") - self.xyzCache.clear() KDiffusionSampler.vec_cc = {"enable": False} return p if "Random" in self.xyzCache.keys(): + print("[X/Y/Z Plot] x [Vec.CC] Randomize is Enabled.") if len(self.xyzCache) > 1: - print( - "\n[X/Y/Z Plot] x [Vec.CC] Randomize is Enabled.\nSome settings will not apply!\n" - ) - else: - print("\n[X/Y/Z Plot] x [Vec.CC] Randomize is Enabled.\n") + print("Some parameters will not apply!") cc_seed = int(seeds[0]) if doRN else None - for k, v in self.xyzCache.items(): - match k: - case "Alt": - latent = self.xyzCache["Alt"].lower().strip() == "true" - case "Brightness": - bri = float(v) - case "Contrast": - con = float(v) - case "Saturation": - sat = float(v) - case "R": - r = float(v) - case "G": - g = float(v) - case "B": - b = float(v) - case "DoHR": - doHR = self.xyzCache["DoHR"].lower().strip() == "true" - case "Method": - method = str(v) - case "Scaling": - scaling = str(v) - case "Random": - cc_seed = int(v) + if "Alt" in self.xyzCache.keys(): + latent = self.xyzCache["Alt"].lower().strip() == "true" + + if "DoHR" in self.xyzCache.keys(): + doHR = self.xyzCache["DoHR"].lower().strip() == "true" + + if "Random" in self.xyzCache.keys(): + cc_seed = int(self.xyzCache["Random"]) + + bri = float(self.xyzCache.get("Brightness", bri)) + con = float(self.xyzCache.get("Contrast", con)) + sat = float(self.xyzCache.get("Saturation", sat)) + + r = float(self.xyzCache.get("R", r)) + g = float(self.xyzCache.get("G", g)) + b = float(self.xyzCache.get("B", b)) + + method = str(self.xyzCache.get("Method", method)) + scaling = str(self.xyzCache.get("Scaling", scaling)) self.xyzCache.clear() @@ -347,11 +340,7 @@ class VectorscopeCC(scripts.Script): KDiffusionSampler.vec_cc = {"enable": False} return p - steps: int = p.steps - # is img2img & do full steps - if not hasattr(p, "enable_hr") and not shared.opts.img2img_fix_steps: - if getattr(p, "denoising_strength", 1.0) < 1.0: - steps = int(steps * getattr(p, "denoising_strength", 1.0) + 1.0) + steps: int = getattr(p, "firstpass_steps", None) or p.steps if cc_seed: seed(cc_seed) @@ -373,19 +362,24 @@ class VectorscopeCC(scripts.Script): print(f"B:\t\t{b}\n") if getattr(shared.opts, "cc_metadata", True): - p.extra_generation_params["Vec CC Enabled"] = enable - p.extra_generation_params["Vec CC Alt"] = latent - p.extra_generation_params["Vec CC Brightness"] = bri - p.extra_generation_params["Vec CC Contrast"] = con - p.extra_generation_params["Vec CC Saturation"] = sat - p.extra_generation_params["Vec CC R"] = r - p.extra_generation_params["Vec CC G"] = g - p.extra_generation_params["Vec CC B"] = b - p.extra_generation_params["Vec CC Noise"] = method - p.extra_generation_params["Vec CC Proc HrF"] = doHR - p.extra_generation_params["Vec CC Proc Ade"] = doAD - p.extra_generation_params["Vec CC Scaling"] = scaling - p.extra_generation_params["Vec CC Version"] = VERSION + p.extra_generation_params.update( + { + "Vec CC Enabled": enable, + "Vec CC Alt": latent, + "Vec CC Brightness": bri, + "Vec CC Contrast": con, + "Vec CC Saturation": sat, + "Vec CC R": r, + "Vec CC G": g, + "Vec CC B": b, + "Vec CC Noise": method, + "Vec CC Proc HrF": doHR, + "Vec CC Proc Ade": doAD, + "Vec CC Seed Randomize": doRN, + "Vec CC Scaling": scaling, + "Vec CC Version": VERSION, + } + ) bri /= steps con /= steps @@ -394,7 +388,7 @@ class VectorscopeCC(scripts.Script): g /= steps b /= steps - mode = "x" if latent else "denoised" + mode: str = "x" if latent else "denoised" KDiffusionSampler.vec_cc = { "enable": True, diff --git a/scripts/cc_hdr.py b/scripts/cc_hdr.py index 3156a91..0e7dd55 100644 --- a/scripts/cc_hdr.py +++ b/scripts/cc_hdr.py @@ -7,16 +7,15 @@ import cv2 as cv # https://docs.opencv.org/4.8.0/d2/df0/tutorial_py_hdr.html -def merge_HDR(imgs: list, path: str, depth: str, fmt: str, gamma: float): +def merge_HDR(imgs: list, path: str, depth: str, fmt: str, gamma: float) -> np.ndarray: import datetime import math import os output_folder = os.path.join(path, "hdr") - if not os.path.exists(output_folder): - os.makedirs(output_folder) + os.makedirs(output_folder, exist_ok=True) - imgs_np = [np.array(img, dtype=np.uint8) for img in imgs] + imgs_np = [np.asarray(img).astype(np.uint8) for img in imgs] merge = cv.createMergeMertens() hdr = merge.process(imgs_np) @@ -39,8 +38,11 @@ def merge_HDR(imgs: list, path: str, depth: str, fmt: str, gamma: float): rgb, ) + return ldr + class VectorHDR(scripts.Script): + def title(self): return "High Dynamic Range" @@ -49,14 +51,14 @@ class VectorHDR(scripts.Script): def ui(self, is_img2img): with gr.Row(): - count = gr.Slider(label="Brackets", minimum=3, maximum=9, step=2, value=7) + count = gr.Slider(label="Brackets", minimum=3, maximum=9, step=2, value=5) gap = gr.Slider( - label="Gaps", minimum=0.50, maximum=2.50, step=0.25, value=1.50 + label="Gaps", minimum=0.50, maximum=2.50, step=0.25, value=1.25 ) with gr.Accordion( "Merge Options", - elem_id="vec-hdr-" + ("img" if is_img2img else "txt"), + elem_id=f'vec-hdr-{"img" if is_img2img else "txt"}', open=False, ): auto = gr.Checkbox(label="Automatically Merge", value=True) @@ -85,16 +87,7 @@ class VectorHDR(scripts.Script): center = count // 2 p.seed = get_fixed_seed(p.seed) - p.scripts.script("vectorscope cc").xyzCache.update( - { - "Enable": "True", - "Alt": "True", - "Brightness": 0, - "DoHR": "False", - "Method": "Ones", - "Scaling": "1 - Cos", - } - ) + p.scripts.script("vectorscope cc").xyzCache.update({"Enable": "False"}) baseline = process_images(p) pc = copy(p) @@ -109,7 +102,14 @@ class VectorHDR(scripts.Script): continue pc.scripts.script("vectorscope cc").xyzCache.update( - {"Brightness": brackets[it]} + { + "Enable": "True", + "Alt": "True", + "Brightness": brackets[it], + "DoHR": "False", + "Method": "Ones", + "Scaling": "1 - Cos", + } ) proc = process_images(pc) @@ -117,13 +117,12 @@ class VectorHDR(scripts.Script): if not auto: baseline.images = imgs - return baseline - else: - merge_HDR(imgs, p.outpath_samples, depth, fmt, gamma) - return baseline + baseline.images = [merge_HDR(imgs, p.outpath_samples, depth, fmt, gamma)] + + return baseline -def brightness_brackets(count, gap): +def brightness_brackets(count: int, gap: int) -> list[int]: half = count // 2 return [gap * (i - half) for i in range(count)] diff --git a/scripts/vectorscope.png b/scripts/vectorscope.png new file mode 100644 index 0000000..4736789 Binary files /dev/null and b/scripts/vectorscope.png differ