From e9ff542fe622bab3eca5c22d26469b9f5ab2edef Mon Sep 17 00:00:00 2001 From: "DESKTOP-TVBJISQ\\Primere" Date: Wed, 18 Mar 2026 19:43:30 +0100 Subject: [PATCH] V 2.0.0 - Rasterix 4 --- Nodes/Dashboard.py | 26 +++++- __init__.py | 2 + components/images/isgen_detect_ext_full.py | 99 ++++++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 components/images/isgen_detect_ext_full.py diff --git a/Nodes/Dashboard.py b/Nodes/Dashboard.py index 9302646..dafed3a 100644 --- a/Nodes/Dashboard.py +++ b/Nodes/Dashboard.py @@ -55,6 +55,7 @@ from ComfyUI_ExtraModels.Sana.loader import load_sana from ComfyUI_ExtraModels.VAE.conf import vae_conf from ComfyUI_ExtraModels.VAE.loader import EXVAE import numpy as np +from PIL import Image import difflib import datetime from ..components import llm_enhancer @@ -2221,4 +2222,27 @@ class PrimereRasterix: if ai_detection: 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 + return (utility.image_to_tensor(pil_img),) + + +class PrimereRasterixGrain: + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("IMAGE",) + FUNCTION = "primere_rasterix_grain" + CATEGORY = TREE_DASHBOARD + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE", {"forceInput": True}), + "grain_intensity": ("FLOAT", {"default": 6.5, "min": 0.0, "max": 30.0, "step": 0.5}), + } + } + + def primere_rasterix_grain(self, image, grain_intensity): + if grain_intensity == 0: + return (image,) + pil_img = utility.tensor_to_image(image) + arr = isgen_detect_ext_full.add_film_grain(np.array(pil_img), intensity=grain_intensity) + return (utility.image_to_tensor(Image.fromarray(arr)),) \ No newline at end of file diff --git a/__init__.py b/__init__.py index 40a380a..3d965b5 100644 --- a/__init__.py +++ b/__init__.py @@ -59,6 +59,7 @@ NODE_CLASS_MAPPINGS = { "PrimereModelKeyword": Dashboard.PrimereModelKeyword, "PrimereUpscaleModel": Dashboard.PrimereUpscaleModel, "PrimereRasterix": Dashboard.PrimereRasterix, + "PrimereRasterixGrain": Dashboard.PrimereRasterixGrain, "PrimerePrompt": Inputs.PrimereDoublePrompt, "PrimereStyleLoader": Inputs.PrimereStyleLoader, @@ -139,6 +140,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "PrimereConceptDataTuple": "Primere Concept Tuple", "PrimereUpscaleModel": "Primere Upscale Models", "PrimereRasterix": "Primere Rasterix (The ToneLab)", + "PrimereRasterixGrain": "Primere Rasterix Grain", "PrimerePrompt": "Primere Prompt", "PrimereStyleLoader": "Primere Styles", diff --git a/components/images/isgen_detect_ext_full.py b/components/images/isgen_detect_ext_full.py new file mode 100644 index 0000000..001db2c --- /dev/null +++ b/components/images/isgen_detect_ext_full.py @@ -0,0 +1,99 @@ +import numpy as np +import io +from PIL import Image +from PIL.ImageFilter import UnsharpMask +from scipy.ndimage import uniform_filter + + +def add_film_grain(arr: np.ndarray, intensity: float = 6.5) -> np.ndarray: + arr = arr.astype(np.float32) + luminance = np.mean(arr, axis=-1, keepdims=True) / 255.0 + grain = np.random.normal(0, intensity, arr.shape[:2])[..., np.newaxis] + grain *= (0.4 + 0.8 * (1 - luminance)) + color_shift = np.random.normal(0, intensity * 0.3, (1, 1, 3)) + return np.clip(arr + grain * color_shift, 0, 255).astype(np.uint8) + + +def perturb_frequency(arr: np.ndarray, strength: float = 0.019) -> np.ndarray: + arr = arr.astype(np.float32) + result = np.zeros_like(arr) + for c in range(3): + f = np.fft.fft2(arr[:, :, c]) + fshift = np.fft.fftshift(f) + rows, cols = fshift.shape + y, x = np.ogrid[:rows, :cols] + dist = np.sqrt((y - rows//2)**2 + (x - cols//2)**2) + dist_norm = np.clip(dist / (max(rows, cols) / 2), 0, 1) + high_freq_mask = dist_norm ** 2 + noise = np.random.normal(0, strength, fshift.shape) * high_freq_mask + fshift_pert = fshift + noise + 1j * noise + ch_pert = np.real(np.fft.ifft2(np.fft.ifftshift(fshift_pert))) + result[:, :, c] = ch_pert + return np.clip(result, 0, 255).astype(np.uint8) + + +def adjust_local_variance(arr: np.ndarray, strength: float = 0.32) -> np.ndarray: + arr = arr.astype(np.float32) + mean = uniform_filter(arr, size=5, mode='reflect') + var = uniform_filter(arr**2, size=5, mode='reflect') - mean**2 + std_local = np.sqrt(np.clip(var, 1e-8, None)) + noise = np.random.normal(0, strength, arr.shape) + noise *= (std_local / (np.mean(std_local) + 1e-5)) + return np.clip(arr + noise, 0, 255).astype(np.uint8) + + +def add_chromatic_aberration(arr: np.ndarray, strength: float = 1.2) -> np.ndarray: + arr = arr.copy().astype(np.float32) + shift = int(strength) + arr[:,:,0] = np.roll(arr[:,:,0], -shift//2, axis=1) + arr[:,:,2] = np.roll(arr[:,:,2], shift//2, axis=1) + return np.clip(arr, 0, 255).astype(np.uint8) + + +def add_vignette(arr: np.ndarray, strength: float = 0.18) -> np.ndarray: + h, w = arr.shape[:2] + y, x = np.ogrid[:h, :w] + dist = np.sqrt((x - w/2)**2 + (y - h/2)**2) + max_dist = np.sqrt((w/2)**2 + (h/2)**2) + vignette = 1 - strength * (dist / max_dist)**2 + vignette = np.clip(vignette[..., np.newaxis], 0.75, 1.0) + return np.clip(arr * vignette, 0, 255).astype(np.uint8) + + +def apply_jpeg_cycles(img: Image.Image, quality: int = 92, cycles: int = 3) -> Image.Image: + for _ in range(cycles): + buf = io.BytesIO() + img.save(buf, "JPEG", quality=quality, optimize=True, subsampling=0) + buf.seek(0) + img = Image.open(buf).convert("RGB") + return img + + +def bypass_ai_detector( + image: Image.Image, + grain_intensity: float = 6.5, + freq_strength: float = 0.019, + variance_strength: float = 0.32, + ca_strength: float = 1.2, + vignette_strength: float = 0.18, + unsharp_percent: int = 38, + jpeg_quality: int = 92, + jpeg_cycles: int = 3, +) -> Image.Image: + + img = image.convert("RGB") + arr = np.array(img, dtype=np.float32) + + arr = add_film_grain(arr, intensity=grain_intensity) + arr = perturb_frequency(arr, strength=freq_strength) + arr = adjust_local_variance(arr, strength=variance_strength) + arr = add_chromatic_aberration(arr, strength=ca_strength) + arr = add_vignette(arr, strength=vignette_strength) + + edited = Image.fromarray(arr.astype(np.uint8)) + edited = edited.filter(UnsharpMask(radius=0.75, percent=unsharp_percent, threshold=0)) + + if jpeg_cycles > 0: + edited = apply_jpeg_cycles(edited, quality=jpeg_quality, cycles=jpeg_cycles) + + return edited