V 2.0.0 - Rasterix 3

This commit is contained in:
DESKTOP-TVBJISQ\Primere
2026-03-18 19:18:08 +01:00
parent ccc76efe44
commit 4a136dbcce
3 changed files with 76 additions and 55 deletions
+13 -6
View File
@@ -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),)
+41 -49
View File
@@ -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")
+22
View File
@@ -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})