V 2.0.0 - Rasterix 3
This commit is contained in:
+13
-6
@@ -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),)
|
||||
@@ -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")
|
||||
|
||||
@@ -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})
|
||||
Reference in New Issue
Block a user