diff --git a/.changeset/smooth-dragons-ask.md b/.changeset/smooth-dragons-ask.md new file mode 100644 index 0000000000000..2cf61fdf9e985 --- /dev/null +++ b/.changeset/smooth-dragons-ask.md @@ -0,0 +1,7 @@ +--- +"@gradio/app": minor +"@gradio/image": minor +"gradio": minor +--- + +feat:Allow setting sketch color default diff --git a/CHANGELOG.md b/CHANGELOG.md index 787e710be263d..0ae0f01f14230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Added autofocus argument to Textbox by [@aliabid94](https://github.com/aliabid94) in [PR 4978](https://github.com/gradio-app/gradio/pull/4978) - The `gr.ChatInterface` UI now converts the "Submit" button to a "Stop" button in ChatInterface while streaming, which can be used to pause generation. By [@abidlabs](https://github.com/abidlabs) in [PR 4971](https://github.com/gradio-app/gradio/pull/4971). - Add a `border_color_accent_subdued` theme variable to add a subdued border color to accented items. This is used by chatbot user messages. Set the value of this variable in `Default` theme to `*primary_200`. By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4989](https://github.com/gradio-app/gradio/pull/4989) +- Add default sketch color argument `brush_color`. Also, masks drawn on images are now slightly translucent (and mask color can also be set via brush_color). By [@aliabid94](https://github.com/aliabid94) in [PR 4979](https://github.com/gradio-app/gradio/pull/4979) ### Bug Fixes: diff --git a/gradio/components/image.py b/gradio/components/image.py index 8862ea3ee3770..96481d5c156ea 100644 --- a/gradio/components/image.py +++ b/gradio/components/image.py @@ -81,6 +81,8 @@ def __init__( elem_classes: list[str] | str | None = None, mirror_webcam: bool = True, brush_radius: float | None = None, + brush_color: str = "#000000", + mask_opacity: float = 0.7, show_share_button: bool | None = None, **kwargs, ): @@ -109,9 +111,13 @@ def __init__( elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. mirror_webcam: If True webcam will be mirrored. Default is True. brush_radius: Size of the brush for Sketch. Default is None which chooses a sensible default + brush_color: Color of the brush for Sketch as hex string. Default is "#000000". + mask_opacity: Opacity of mask drawn on image, as a value between 0 and 1. show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise. """ self.brush_radius = brush_radius + self.brush_color = brush_color + self.mask_opacity = mask_opacity self.mirror_webcam = mirror_webcam valid_types = ["numpy", "pil", "filepath"] if type not in valid_types: @@ -178,6 +184,8 @@ def get_config(self): "streaming": self.streaming, "mirror_webcam": self.mirror_webcam, "brush_radius": self.brush_radius, + "brush_color": self.brush_color, + "mask_opacity": self.mask_opacity, "selectable": self.selectable, "show_share_button": self.show_share_button, "show_download_button": self.show_download_button, @@ -198,6 +206,8 @@ def update( interactive: bool | None = None, visible: bool | None = None, brush_radius: float | None = None, + brush_color: str | None = None, + mask_opacity: float | None = None, show_share_button: bool | None = None, ): return { @@ -213,6 +223,8 @@ def update( "visible": visible, "value": value, "brush_radius": brush_radius, + "brush_color": brush_color, + "mask_opacity": mask_opacity, "show_share_button": show_share_button, "__type__": "update", } @@ -276,6 +288,10 @@ def preprocess( if self.tool == "sketch" and self.source in ["upload", "webcam"]: mask_im = processing_utils.decode_base64_to_image(mask) + + if mask_im.mode == "RGBA": # whiten any opaque pixels in the mask + alpha_data = mask_im.getchannel("A").convert("L") + mask_im = _Image.merge("RGB", [alpha_data, alpha_data, alpha_data]) return { "image": self._format_image(im), "mask": self._format_image(mask_im), diff --git a/gradio/templates.py b/gradio/templates.py index 63e509806ee90..42ebb1a2d7b01 100644 --- a/gradio/templates.py +++ b/gradio/templates.py @@ -68,6 +68,7 @@ def __init__( elem_id: str | None = None, mirror_webcam: bool = True, brush_radius: float | None = None, + brush_color: str = "#000000", **kwargs, ): super().__init__( @@ -86,6 +87,7 @@ def __init__( elem_id=elem_id, mirror_webcam=mirror_webcam, brush_radius=brush_radius, + brush_color=brush_color, **kwargs, ) @@ -115,6 +117,7 @@ def __init__( elem_id: str | None = None, mirror_webcam: bool = True, brush_radius: float | None = None, + brush_color: str = "#000000", **kwargs, ): super().__init__( @@ -133,6 +136,7 @@ def __init__( elem_id=elem_id, mirror_webcam=mirror_webcam, brush_radius=brush_radius, + brush_color=brush_color, **kwargs, ) @@ -162,6 +166,7 @@ def __init__( elem_id: str | None = None, mirror_webcam: bool = True, brush_radius: float | None = None, + brush_color: str = "#000000", **kwargs, ): super().__init__( @@ -180,6 +185,7 @@ def __init__( elem_id=elem_id, mirror_webcam=mirror_webcam, brush_radius=brush_radius, + brush_color=brush_color, **kwargs, ) @@ -209,6 +215,7 @@ def __init__( elem_id: str | None = None, mirror_webcam: bool = True, brush_radius: float | None = None, + brush_color: str = "#000000", **kwargs, ): super().__init__( @@ -227,6 +234,7 @@ def __init__( elem_id=elem_id, mirror_webcam=mirror_webcam, brush_radius=brush_radius, + brush_color=brush_color, **kwargs, ) @@ -256,6 +264,7 @@ def __init__( elem_id: str | None = None, mirror_webcam: bool = True, brush_radius: float | None = None, + brush_color: str = "#000000", **kwargs, ): super().__init__( @@ -274,6 +283,7 @@ def __init__( elem_id=elem_id, mirror_webcam=mirror_webcam, brush_radius=brush_radius, + brush_color=brush_color, **kwargs, ) @@ -303,6 +313,7 @@ def __init__( elem_id: str | None = None, mirror_webcam: bool = True, brush_radius: float | None = None, + brush_color: str = "#000000", **kwargs, ): super().__init__( @@ -321,6 +332,7 @@ def __init__( elem_id=elem_id, mirror_webcam=mirror_webcam, brush_radius=brush_radius, + brush_color=brush_color, **kwargs, ) diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py index fb16480c6816f..b8796d49d9af3 100644 --- a/gradio/test_data/blocks_configs.py +++ b/gradio/test_data/blocks_configs.py @@ -54,6 +54,8 @@ "type": "image", "props": { "image_mode": "RGB", + "brush_color": "#000000", + "mask_opacity": 0.7, "source": "upload", "tool": "editor", "streaming": False, @@ -127,6 +129,8 @@ "type": "image", "props": { "image_mode": "RGB", + "brush_color": "#000000", + "mask_opacity": 0.7, "source": "upload", "tool": "editor", "streaming": False, @@ -375,6 +379,8 @@ "type": "image", "props": { "image_mode": "RGB", + "brush_color": "#000000", + "mask_opacity": 0.7, "source": "upload", "tool": "editor", "streaming": False, @@ -448,6 +454,8 @@ "type": "image", "props": { "image_mode": "RGB", + "brush_color": "#000000", + "mask_opacity": 0.7, "source": "upload", "tool": "editor", "streaming": False, @@ -699,6 +707,8 @@ "type": "image", "props": { "image_mode": "RGB", + "brush_color": "#000000", + "mask_opacity": 0.7, "source": "upload", "streaming": False, "mirror_webcam": True, @@ -750,6 +760,8 @@ "type": "image", "props": { "image_mode": "RGB", + "brush_color": "#000000", + "mask_opacity": 0.7, "source": "upload", "tool": "editor", "streaming": False, diff --git a/js/app/src/components/Image/Image.svelte b/js/app/src/components/Image/Image.svelte index 47b1efe8b09bd..1cd5a675286be 100644 --- a/js/app/src/components/Image/Image.svelte +++ b/js/app/src/components/Image/Image.svelte @@ -25,6 +25,8 @@ export let mirror_webcam: boolean; export let shape: [number, number]; export let brush_radius: number; + export let brush_color: string; + export let mask_opacity: number; export let selectable = false; export let container = true; export let scale: number | null = null; @@ -78,11 +80,13 @@ {:else} { @@ -243,9 +241,6 @@ lines = _lines; ctx.drawing.drawImage(canvas.temp, 0, 0, width, height); - if (mode === "mask") { - ctx.mask.drawImage(canvas.temp_fake, 0, 0, width, height); - } if (lines.length == 0) { dispatch("clear"); @@ -270,7 +265,7 @@ return JSON.stringify({ lines: lines, width: canvas_width, - height: canvas_height + height: canvas_height, }); }; @@ -280,16 +275,9 @@ draw_points({ points: _points, brush_color, - brush_radius + brush_radius, + mask: mode === "mask", }); - - if (mode === "mask") { - draw_fake_points({ - points: _points, - brush_color, - brush_radius - }); - } }); saveLine({ brush_color, brush_radius }); @@ -351,15 +339,14 @@ const container_dimensions = { height: container_height, - width: container_height * (dimensions.width / dimensions.height) + width: container_height * (dimensions.width / dimensions.height), }; await Promise.all([ set_canvas_size(canvas.interface, dimensions, container_dimensions), set_canvas_size(canvas.drawing, dimensions, container_dimensions), set_canvas_size(canvas.temp, dimensions, container_dimensions), - set_canvas_size(canvas.temp_fake, dimensions, container_dimensions), - set_canvas_size(canvas.mask, dimensions, container_dimensions, false) + set_canvas_size(canvas.mask, dimensions, container_dimensions, false), ]); if (!brush_radius) { @@ -418,7 +405,7 @@ return { x: ((clientX - rect.left) / rect.width) * width, - y: ((clientY - rect.top) / rect.height) * height + y: ((clientY - rect.top) / rect.height) * height, }; }; @@ -434,69 +421,39 @@ draw_points({ points: points, brush_color, - brush_radius + brush_radius, + mask: mode === "mask", }); - - if (mode === "mask") { - draw_fake_points({ - points: points, - brush_color, - brush_radius - }); - } } mouse_has_moved = true; }; - let draw_points = ({ points, brush_color, brush_radius }) => { - if (!points || points.length < 2) return; - ctx.temp.lineJoin = "round"; - ctx.temp.lineCap = "round"; - - ctx.temp.strokeStyle = brush_color; - ctx.temp.lineWidth = brush_radius; - if (!points || points.length < 2) return; - let p1 = points[0]; - let p2 = points[1]; - ctx.temp.moveTo(p2.x, p2.y); - ctx.temp.beginPath(); - for (var i = 1, len = points.length; i < len; i++) { - var midPoint = mid_point(p1, p2); - ctx.temp.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); - p1 = points[i]; - p2 = points[i + 1]; - } - - ctx.temp.lineTo(p1.x, p1.y); - ctx.temp.stroke(); - }; - - let draw_fake_points = ({ points, brush_color, brush_radius }) => { + let draw_points = ({ points, brush_color, brush_radius, mask }) => { if (!points || points.length < 2) return; + let target_ctx = mask ? ctx.mask : ctx.temp; + target_ctx.lineJoin = "round"; + target_ctx.lineCap = "round"; - ctx.temp_fake.lineJoin = "round"; - ctx.temp_fake.lineCap = "round"; - ctx.temp_fake.strokeStyle = "#fff"; - ctx.temp_fake.lineWidth = brush_radius; + target_ctx.strokeStyle = brush_color; + target_ctx.lineWidth = brush_radius; let p1 = points[0]; let p2 = points[1]; - ctx.temp_fake.moveTo(p2.x, p2.y); - ctx.temp_fake.beginPath(); + target_ctx.moveTo(p2.x, p2.y); + target_ctx.beginPath(); for (var i = 1, len = points.length; i < len; i++) { var midPoint = mid_point(p1, p2); - ctx.temp_fake.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + target_ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); p1 = points[i]; p2 = points[i + 1]; } - ctx.temp_fake.lineTo(p1.x, p1.y); - ctx.temp_fake.stroke(); + target_ctx.lineTo(p1.x, p1.y); + target_ctx.stroke(); }; let save_mask_line = () => { if (points.length < 1) return; points.length = 0; - ctx.mask.drawImage(canvas.temp_fake, 0, 0, width, height); trigger_on_change(); }; @@ -507,7 +464,7 @@ lines.push({ points: points.slice(), brush_color: brush_color, - brush_radius + brush_radius, }); if (mode !== "mask") { @@ -540,15 +497,7 @@ ctx.temp.fillRect(0, 0, width, height); if (mode === "mask") { - ctx.temp_fake.clearRect( - 0, - 0, - canvas.temp_fake.width, - canvas.temp_fake.height - ); - ctx.mask.clearRect(0, 0, width, height); - ctx.mask.fillStyle = "#000"; - ctx.mask.fillRect(0, 0, width, height); + ctx.mask.clearRect(0, 0, canvas.mask.width, canvas.mask.height); } } @@ -587,7 +536,7 @@ export function get_image_data() { return mode === "mask" - ? canvas.mask.toDataURL("image/jpg") + ? canvas.mask.toDataURL("image/png") : canvas.drawing.toDataURL("image/jpg"); } @@ -603,10 +552,11 @@ Start drawing {/if} - {#each canvas_types as { name, zIndex }} + {#each canvas_types as { name, zIndex, opacity }}