Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from PIL import Image | |
| import numpy as np | |
| import uuid | |
| def editor_to_mask(value, invert=False): | |
| """ | |
| Convert an ImageEditor EditorValue to a white-on-black mask. | |
| value: dict with keys: 'background', 'layers', 'composite' (Gradio 4/5 ImageEditor) | |
| Strategy: | |
| - Create an empty (black) mask the size of the background (or composite if background is None) | |
| - For each layer, use its alpha channel as painted region; OR it to the mask | |
| """ | |
| if not value: | |
| return None, None | |
| bg = value.get("background") | |
| layers = value.get("layers") or [] | |
| comp = value.get("composite") | |
| # Determine canvas size | |
| ref_img = bg or comp | |
| if ref_img is None: | |
| return None, None | |
| if not isinstance(ref_img, Image.Image): | |
| ref_img = Image.fromarray(ref_img) | |
| w, h = ref_img.size | |
| mask_acc = np.zeros((h, w), dtype=np.uint8) | |
| for layer in layers: | |
| if layer is None: | |
| continue | |
| if not isinstance(layer, Image.Image): | |
| layer = Image.fromarray(layer) | |
| # Ensure RGBA to get alpha; if no alpha, treat non-black as painted | |
| if layer.mode != "RGBA": | |
| layer = layer.convert("RGBA") | |
| la = np.array(layer)[:, :, 3] # alpha | |
| # Any non-zero alpha counts as painted | |
| mask_acc = np.maximum(mask_acc, (la > 0).astype(np.uint8) * 255) | |
| if invert: | |
| mask_acc = 255 - mask_acc | |
| out = Image.fromarray(mask_acc, mode="L") | |
| fname = f"mask_{uuid.uuid4().hex}.png" | |
| out.save(fname) | |
| return out, fname | |
| with gr.Blocks(css="footer {visibility:hidden}") as demo: | |
| gr.Markdown("# 🖌️ Quick Mask Maker (Gradio 5)\nUpload an image, draw on the canvas (white brush), then export a white-on-black PNG mask.") | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| brush_size = gr.Slider(5, 120, value=40, step=1, label="Brush radius") | |
| editor = gr.ImageEditor( | |
| label="Draw mask on its own layer (preselected).", | |
| type="pil", | |
| image_mode="RGBA", | |
| height=520, | |
| # Force a single "Mask" layer, disable adding extra layers | |
| layers=gr.LayerOptions(allow_additional_layers=False, layers=["Mask"]), | |
| # Force a white-only brush so painted area clearly becomes the mask | |
| brush=gr.Brush(default_size=40, colors=["#FFFFFF"], default_color="#FFFFFF", color_mode="fixed"), | |
| eraser=gr.Eraser(default_size=60), | |
| ) | |
| invert = gr.Checkbox(False, label="Invert mask (white background)") | |
| gen = gr.Button("Generate Mask", variant="primary") | |
| with gr.Column(scale=2): | |
| out_mask = gr.Image(label="Mask Preview (PNG)", type="pil", image_mode="L") | |
| file_dl = gr.File(label="Download Mask") | |
| # Wire brush size slider to editor | |
| def _set_brush(r): | |
| return gr.update(brush=gr.Brush(default_size=int(r), colors=["#FFFFFF"], default_color="#FFFFFF", color_mode="fixed")) | |
| brush_size.change(_set_brush, inputs=brush_size, outputs=editor) | |
| gen.click(fn=editor_to_mask, inputs=[editor, invert], outputs=[out_mask, file_dl]) | |
| if __name__ == "__main__": | |
| demo.launch() |