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