import gradio as gr from PIL import Image import numpy as np from modules import shared, scripts_manager, processing, masking """ Automatic Color Inpaint Script for SD.NEXT - SD & SDXL Support Author: Artheriax Credits: SD.NEXT team for script template Version: v1 Contributions: A new script to automatically inpaint colors in images using Stable Diffusion, Stable Diffusion XL or Flux. """ ## Config # script title supported_models = ['sd','sdxl', 'flux'] title = 'Automatic Color Inpaint' # is script available in txt2img tab txt2img = False # is script available in img2img tab img2img = True ### Script definition class Script(scripts_manager.Script): def title(self): return title def show(self, is_img2img): return img2img if is_img2img else txt2img # Define UI for pipeline def ui(self, _is_img2img): with gr.Row(): gr.HTML("  ACI: Automatic Color Inpaint
") with gr.Row(): color_picker = gr.ColorPicker( label="ACI: Color to Mask", value="#04F404", # Default to green screen green # info="Pick the color you want to mask and inpaint." ) tolerance_slider = gr.Slider( minimum=0, maximum=100, step=1, value=65, label="ACI: Color tolerance", ) denoising_slider = gr.Slider( minimum=0.01, maximum=1, step=0.01, value=0.9, label="ACI: Denoising strength", ) with gr.Row(): dilate_slider = gr.Slider( minimum=0, maximum=1, step=0.01, value=0.0, label="ACI: Mask dilate", # info="(Recommended value = 2 to remove leftovers at edges)" ) erode_slider = gr.Slider( minimum=0, maximum=1, step=0.01, value=0, label="ACI: Mask erode", # info="(Recommended value = 0 for sharpness)" ) blur_slider = gr.Slider( minimum=0, maximum=1, step=0.01, value=0.15, label="ACI: Mask blur", # info="(Recommended value = 0 for sharpness)" ) return [color_picker, tolerance_slider, dilate_slider, erode_slider, blur_slider, denoising_slider] # Run pipeline def run(self, p: processing.StableDiffusionProcessing, *args): # pylint: disable=arguments-differ if shared.sd_model_type not in supported_models: shared.log.warning(f'MoD: class={shared.sd_model.__class__.__name__} model={shared.sd_model_type} required={supported_models}') return None if not hasattr(p, 'init_images') or p.init_images is None or len(p.init_images) == 0: return None color_to_mask_hex, mask_tolerance, mask_dilate, mask_erode, mask_blur, inpaint_denoising_strength = args # Convert hex color to RGB tuple (0-255) color_to_mask_rgb = tuple(int(color_to_mask_hex[i:i+2], 16) for i in (1, 3, 5)) shared.log.debug(f'ACI: rgb={color_to_mask_rgb} tolerance={mask_tolerance} dilate={mask_dilate} erode={mask_erode} blur={mask_blur} denoise={inpaint_denoising_strength}') # Create Color Mask using vectorized operations init_image = p.init_images[0].convert("RGB") image_np = np.array(init_image) # Calculate Euclidean distance for all pixels at once diff = np.linalg.norm(image_np.astype(np.int16) - np.array(color_to_mask_rgb, dtype=np.int16), axis=2) calc_tolerance = (diff.max() - diff.min()) * mask_tolerance/100 mask_np = (diff <= calc_tolerance).astype(np.uint8) * 255 mask_image = Image.fromarray(mask_np).convert("L") # If an inpaint mask is already provided from the UI, combine it with the color mask if p.image_mask: combined_mask = Image.composite( Image.new("L", mask_image.size, "white"), p.image_mask.convert("L"), mask_image ) p.image_mask = combined_mask else: p.image_mask = mask_image # override inpaint parameters p.inpaint_full_res = False # always use full res p.denoising_strength = inpaint_denoising_strength p.mask_blur = None # do not use legacy mask blur p.inpaint_full_res_padding = None # do not use legacy mask blur masking.opts.mask_blur = mask_blur # new masking params triggers masking.py:run_mask masking.opts.mask_erode = mask_erode masking.opts.mask_dilate = mask_dilate return None