diff --git a/Nodes/Dashboard.py b/Nodes/Dashboard.py index f7146fb..9302646 100644 --- a/Nodes/Dashboard.py +++ b/Nodes/Dashboard.py @@ -1,4 +1,5 @@ import math +import json from ..components.tree import TREE_DASHBOARD from ..components.tree import PRIMERE_ROOT from server import PromptServer @@ -2189,7 +2190,7 @@ class PrimereRasterix: "unsharp_percent": ("INT", {"default": 38, "min": 0, "max": 150, "step": 1}), "jpeg_quality": ("INT", {"default": 95, "min": 60, "max": 100, "step": 1}), "jpeg_cycles": ("INT", {"default": 3, "min": 0, "max": 6, "step": 1}), - } + }, } def primere_rasterix(self, image, auto_normalize, auto_levels_threshold, shade_level, shade_radius, brightness, contrast, use_legacy, color_balance_cyan_red, color_balance_magenta_green, color_balance_yellow_blue, color_balance_tone, color_balance_preserve_luminosity, hue_saturation_channel, hue_saturation_hue, hue_saturation_saturation, hue_saturation_lightness, hue_saturation_vibrance, ai_detection, grain_intensity, freq_strength, variance_strength, ca_strength, vignette_strength, unsharp_percent, jpeg_quality, jpeg_cycles): @@ -2201,17 +2202,23 @@ class PrimereRasterix: if brightness != 0 or contrast != 0: pil_img = img_brightness_contrast.img_brightness_contrast(image=pil_img, brightness=brightness, contrast=contrast, use_legacy=use_legacy) - if color_balance_cyan_red != 0 or color_balance_magenta_green != 0 or color_balance_yellow_blue != 0: - pil_img = img_color_balance.img_color_balance(image=pil_img, cyan_red=color_balance_cyan_red, magenta_green=color_balance_magenta_green, yellow_blue=color_balance_yellow_blue, tone=color_balance_tone, preserve_luminosity=color_balance_preserve_luminosity) + rasterix_json_path = os.path.join(PRIMERE_ROOT, 'front_end', 'rasterix.json') + rasterix_data = utility.json2tuple(rasterix_json_path) or {} - if hue_saturation_hue != 0 or hue_saturation_saturation != 0 or hue_saturation_lightness != 0 or hue_saturation_vibrance != 0: - pil_img = img_hue_saturation.img_hue_saturation(image=pil_img, channel=hue_saturation_channel, hue=hue_saturation_hue, saturation=hue_saturation_saturation, lightness=hue_saturation_lightness, vibrance=hue_saturation_vibrance) + cb_data = rasterix_data.get('color_balance', {}) + for tone, vals in cb_data.items(): + if vals.get('cyan_red', 0) != 0 or vals.get('magenta_green', 0) != 0 or vals.get('yellow_blue', 0) != 0: + pil_img = img_color_balance.img_color_balance(image=pil_img, cyan_red=vals['cyan_red'], magenta_green=vals['magenta_green'], yellow_blue=vals['yellow_blue'], tone=tone, preserve_luminosity=color_balance_preserve_luminosity) + + hs_data = rasterix_data.get('hue_saturation', {}) + if hs_data: + pil_img = img_hue_saturation.img_hue_saturation(image=pil_img, channels_data=hs_data) shade_radius = None if shade_radius == 0 else shade_radius if shade_level != 0: pil_img = img_shade_level.img_shade_level(image=pil_img, shade_level=shade_level, radius=shade_radius) if ai_detection: - pil_img = detect_ext_full.bypass_ai_detector(image=pil_img, grain_intensity=grain_intensity, freq_strength=freq_strength, variance_strength=variance_strength, ca_strength=ca_strength, vignette_strength=vignette_strength, unsharp_percent=unsharp_percent, jpeg_quality=jpeg_quality, jpeg_cycles=jpeg_cycles) + pil_img = isgen_detect_ext_full.bypass_ai_detector(image=pil_img, grain_intensity=grain_intensity, freq_strength=freq_strength, variance_strength=variance_strength, ca_strength=ca_strength, vignette_strength=vignette_strength, unsharp_percent=unsharp_percent, jpeg_quality=jpeg_quality, jpeg_cycles=jpeg_cycles) return (utility.image_to_tensor(pil_img),) \ No newline at end of file diff --git a/components/images/img_hue_saturation.py b/components/images/img_hue_saturation.py index f4b787a..356907c 100644 --- a/components/images/img_hue_saturation.py +++ b/components/images/img_hue_saturation.py @@ -2,17 +2,14 @@ import numpy as np from PIL import Image -def img_hue_saturation(image: Image.Image, channel: str = 'master', hue: float = 0, saturation: float = 0, lightness: float = 0, vibrance: float = 0,) -> Image.Image: - VALID_CHANNELS = {'master', 'r', 'g', 'b'} - channel = channel.strip().lower() - if channel not in VALID_CHANNELS: - raise ValueError(f"channel must be one of {VALID_CHANNELS}, got '{channel}'") +def img_hue_saturation(image: Image.Image, channels_data: dict) -> Image.Image: + VALID_CHANNELS = {'master', 'r', 'g', 'b'} + CHANNEL_CENTRES = {'r': 0.0, 'g': 120.0, 'b': 240.0} img = image.convert("RGB") arr = np.array(img, dtype=np.float32) / 255.0 R, G, B = arr[:,:,0], arr[:,:,1], arr[:,:,2] - Cmax = np.maximum(np.maximum(R, G), B) Cmin = np.minimum(np.minimum(R, G), B) delta = Cmax - Cmin @@ -26,50 +23,47 @@ def img_hue_saturation(image: Image.Image, channel: str = 'master', hue: float = with np.errstate(invalid='ignore', divide='ignore'): s = np.where(Cmax == 0, 0.0, delta / Cmax) - v = Cmax - CHANNEL_CENTRES = {'r': 0.0, 'g': 120.0, 'b': 240.0} + total_hue = np.zeros_like(h) + total_sat = np.zeros_like(s) + total_lightness = np.zeros_like(h) + total_vibrance = np.zeros_like(h) - if channel == 'master': - mask = np.ones(h.shape, dtype=np.float32) - else: - centre = CHANNEL_CENTRES[channel] - diff = np.abs(((h - centre + 180) % 360) - 180) - mask = np.where(diff <= 45, 1.0, - np.where(diff <= 75, 1.0 - (diff - 45) / 30.0, 0.0)) - mask = mask.astype(np.float32) + for ch, params in channels_data.items(): + if ch not in VALID_CHANNELS: + continue - if hue != 0: - h_new = (h + hue * mask) % 360.0 - else: - h_new = h.copy() - - s_new = s.copy() - if saturation != 0: - sat_delta = saturation / 100.0 - if sat_delta >= 0: - s_new = s_new + mask * sat_delta * (1.0 - s_new) + if ch == 'master': + mask = np.ones(h.shape, dtype=np.float32) else: - s_new = s_new + mask * sat_delta * s_new - s_new = np.clip(s_new, 0.0, 1.0) + centre = CHANNEL_CENTRES[ch] + diff = np.abs(((h - centre + 180) % 360) - 180) + mask = np.where(diff <= 45, 1.0, + np.where(diff <= 75, 1.0 - (diff - 45) / 30.0, 0.0)).astype(np.float32) - if vibrance != 0: - vib_strength = vibrance / 100.0 + total_hue += mask * params.get('hue', 0) + total_sat += mask * (params.get('saturation', 0) / 100.0) + total_lightness += mask * (params.get('lightness', 0) / 100.0) + total_vibrance += mask * (params.get('vibrance', 0) / 100.0) - skin_diff = np.abs(((h_new - 25.0 + 180) % 360) - 180) - skin_mask = np.where(skin_diff <= 35.0, 1.0, - np.where(skin_diff <= 55.0, 1.0 - (skin_diff - 35.0) / 20.0, - 0.0)).astype(np.float32) - skin_protection = 1.0 - skin_mask + h_new = (h + total_hue) % 360.0 - if vib_strength >= 0: - weight = (1.0 - s_new) * mask * skin_protection - s_new = s_new + weight * vib_strength - else: - weight = s_new * mask * skin_protection - s_new = s_new + weight * vib_strength - s_new = np.clip(s_new, 0.0, 1.0) + s_new = np.where(total_sat >= 0, + s + total_sat * (1.0 - s), + s + total_sat * s) + s_new = np.clip(s_new, 0.0, 1.0) + + skin_diff = np.abs(((h_new - 25.0 + 180) % 360) - 180) + skin_mask = np.where(skin_diff <= 35.0, 1.0, + np.where(skin_diff <= 55.0, 1.0 - (skin_diff - 35.0) / 20.0, + 0.0)).astype(np.float32) + skin_protection = 1.0 - skin_mask + + s_new += np.where(total_vibrance >= 0, + (1.0 - s_new) * skin_protection * total_vibrance, + s_new * skin_protection * total_vibrance) + s_new = np.clip(s_new, 0.0, 1.0) h6 = h_new / 60.0 i = np.floor(h6).astype(np.int32) % 6 @@ -87,13 +81,11 @@ def img_hue_saturation(image: Image.Image, channel: str = 'master', hue: float = np.where(i==3, v, np.where(i==4, v, q))))), ], axis=-1) - if lightness != 0: - L = lightness / 100.0 - mask3 = mask[:, :, np.newaxis] - if L > 0: - rgb_sectors = rgb_sectors + mask3 * L * (1.0 - rgb_sectors) - else: - rgb_sectors = rgb_sectors + mask3 * L * rgb_sectors + if np.any(total_lightness != 0): + L3 = total_lightness[:, :, np.newaxis] + rgb_sectors = np.where(L3 > 0, + rgb_sectors + L3 * (1.0 - rgb_sectors), + rgb_sectors + L3 * rgb_sectors) result = np.clip(rgb_sectors, 0.0, 1.0) return Image.fromarray((result * 255).astype(np.uint8), mode="RGB") diff --git a/components/primereserver.py b/components/primereserver.py index 858ad6d..544c042 100644 --- a/components/primereserver.py +++ b/components/primereserver.py @@ -543,6 +543,28 @@ async def primere_model_concept_save(request): json_path = os.path.join(PRIMERE_ROOT, 'front_end', 'model_concept.json') existing = utility.json2tuple(json_path) or {} existing[concept] = data + with open(json_path, 'w', encoding='utf-8') as f: + json.dump(existing, f, indent=2) + return web.json_response({"success": True}) + +routes19 = PromptServer.instance.routes +@routes19.get('/primere_rasterix_read') +async def primere_rasterix_read(request): + json_path = os.path.join(PRIMERE_ROOT, 'front_end', 'rasterix.json') + data = utility.json2tuple(json_path) or {} + return web.json_response(data) + +routes20 = PromptServer.instance.routes +@routes20.post('/primere_rasterix_save') +async def primere_rasterix_save(request): + post = await request.json() + section = post.get('section') + data = post.get('data') + if not section or data is None: + return web.json_response({"success": False}, status=400) + json_path = os.path.join(PRIMERE_ROOT, 'front_end', 'rasterix.json') + existing = utility.json2tuple(json_path) or {} + existing[section] = data with open(json_path, 'w', encoding='utf-8') as f: json.dump(existing, f, indent=2) return web.json_response({"success": True}) \ No newline at end of file